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

api-platform / core / 20545070147

27 Dec 2025 10:15PM UTC coverage: 28.855% (+3.7%) from 25.192%
20545070147

push

github

soyuka
ci: upgrade to phpunit 12

Remove soyuka/phpunit fork from all composer.json files and upgrade to
PHPUnit 12.2. Update CI workflow to install PHPUnit before other steps
and configure MongoDB conditional execution. Migrate tests from Prophecy
to PHPUnit native mocking in FieldsBuilderTest and Symfony event listener
tests. Remove unused dataprovider and fix warnings.

0 of 84 new or added lines in 8 files covered. (0.0%)

534 existing lines in 34 files now uncovered.

16760 of 58083 relevant lines covered (28.86%)

78.25 hits per line

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

1.54
/src/Serializer/Mapping/Loader/PropertyMetadataLoader.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\Serializer\Mapping\Loader;
15

16
use ApiPlatform\Metadata\ApiProperty;
17
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
18
use Illuminate\Database\Eloquent\Model;
19
use Symfony\Component\Serializer\Attribute\Context;
20
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
21
use Symfony\Component\Serializer\Attribute\Groups;
22
use Symfony\Component\Serializer\Attribute\Ignore;
23
use Symfony\Component\Serializer\Attribute\MaxDepth;
24
use Symfony\Component\Serializer\Attribute\SerializedName;
25
use Symfony\Component\Serializer\Attribute\SerializedPath;
26
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
27
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
28
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
29
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
30
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
31
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
32

33
/**
34
 * Loader for PHP attributes using ApiProperty.
35
 */
36
final class PropertyMetadataLoader implements LoaderInterface
37
{
38
    public function __construct(
39
        private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
40
        private readonly ?NameConverterInterface $nameConverter = null,
41
    ) {
42
    }
12✔
43

44
    public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
45
    {
46
        // It's very weird to grab Eloquent's properties in that case as they're never serialized
47
        // the Serializer makes a call on the abstract class, let's save some unneeded work with a condition
48
        if (Model::class === $classMetadata->getName()) {
×
49
            return false;
×
50
        }
51

52
        $refl = $classMetadata->getReflectionClass();
×
53
        $attributes = [];
×
54
        $classGroups = [];
×
55
        $classContextAnnotation = null;
×
56

57
        foreach ($refl->getAttributes(ApiProperty::class) as $clAttr) {
×
58
            $this->addAttributeMetadata($clAttr->newInstance(), $attributes);
×
59
        }
60

61
        $attributesMetadata = $classMetadata->getAttributesMetadata();
×
62

63
        foreach ($refl->getAttributes() as $a) {
×
64
            // Skip attributes whose classes don't exist (e.g., optional dependencies like MongoDB ODM)
65
            if (!class_exists($a->getName()) && !interface_exists($a->getName())) {
×
66
                continue;
×
67
            }
68

69
            $attribute = $a->newInstance();
×
70

UNCOV
71
            if ($attribute instanceof DiscriminatorMap) {
×
UNCOV
72
                $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
×
73
                    method_exists($attribute, 'getTypeProperty') ? $attribute->getTypeProperty() : $attribute->typeProperty,
×
74
                    method_exists($attribute, 'getMapping') ? $attribute->getMapping() : $attribute->mapping
×
UNCOV
75
                ));
×
76
                continue;
×
77
            }
78

79
            if ($attribute instanceof Groups) {
×
80
                $classGroups = method_exists($attribute, 'getGroups') ? $attribute->getGroups() : $attribute->groups;
×
81

UNCOV
82
                continue;
×
83
            }
84

85
            if ($attribute instanceof Context) {
×
86
                $classContextAnnotation = $attribute;
×
87
            }
88
        }
89

90
        foreach ($refl->getProperties() as $reflProperty) {
×
91
            foreach ($reflProperty->getAttributes(ApiProperty::class) as $propAttr) {
×
92
                $this->addAttributeMetadata($propAttr->newInstance()->withProperty($reflProperty->name), $attributes);
×
93
            }
94
        }
95

96
        foreach ($refl->getMethods() as $reflMethod) {
×
97
            foreach ($reflMethod->getAttributes(ApiProperty::class) as $methodAttr) {
×
98
                $this->addAttributeMetadata($methodAttr->newInstance()->withProperty($reflMethod->getName()), $attributes);
×
99
            }
100
        }
101

102
        foreach ($this->propertyNameCollectionFactory->create($classMetadata->getName()) as $propertyName) {
×
103
            if (!isset($attributesMetadata[$propertyName])) {
×
UNCOV
104
                $attributesMetadata[$propertyName] = new AttributeMetadata($propertyName);
×
UNCOV
105
                $classMetadata->addAttributeMetadata($attributesMetadata[$propertyName]);
×
106
            }
107

UNCOV
108
            foreach ($classGroups as $group) {
×
UNCOV
109
                $attributesMetadata[$propertyName]->addGroup($group);
×
110
            }
111

UNCOV
112
            if ($classContextAnnotation) {
×
UNCOV
113
                $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$propertyName]);
×
114
            }
115

UNCOV
116
            if (!isset($attributes[$propertyName])) {
×
117
                continue;
×
118
            }
119

120
            $attributeMetadata = $attributesMetadata[$propertyName];
×
121

122
            // This code is adapted from Symfony\Component\Serializer\Mapping\Loader\AttributeLoader
123
            foreach ($attributes[$propertyName] as $attr) {
×
UNCOV
124
                if ($attr instanceof Groups) {
×
UNCOV
125
                    $groups = method_exists($attr, 'getGroups') ? $attr->getGroups() : $attr->groups;
×
UNCOV
126
                    foreach ($groups as $group) {
×
127
                        $attributeMetadata->addGroup($group);
×
128
                    }
129
                    continue;
×
130
                }
131

132
                match (true) {
UNCOV
133
                    $attr instanceof MaxDepth => $attributeMetadata->setMaxDepth(method_exists($attr, 'getMaxDepth') ? $attr->getMaxDepth() : $attr->maxDepth),
×
UNCOV
134
                    $attr instanceof SerializedName => $attributeMetadata->setSerializedName(method_exists($attr, 'getSerializedName') ? $attr->getSerializedName() : $attr->serializedName),
×
UNCOV
135
                    $attr instanceof SerializedPath => $attributeMetadata->setSerializedPath(method_exists($attr, 'getSerializedPath') ? $attr->getSerializedPath() : $attr->serializedPath),
×
UNCOV
136
                    $attr instanceof Ignore => $attributeMetadata->setIgnore(true),
×
137
                    $attr instanceof Context => $this->setAttributeContextsForGroups($attr, $attributeMetadata),
×
UNCOV
138
                    default => null,
×
139
                };
140
            }
141
        }
142

UNCOV
143
        return true;
×
144
    }
145

146
    /**
147
     * @param array<string, array<mixed>> $attributes
148
     */
149
    private function addAttributeMetadata(ApiProperty $attribute, array &$attributes): void
150
    {
UNCOV
151
        if (($prop = $this->nameConverter?->denormalize($attribute->getProperty()) ?? $attribute->getProperty()) && ($value = $attribute->getSerialize())) {
×
152
            $attributes[$prop] = $value;
×
153
        }
154
    }
155

156
    private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void
157
    {
158
        $context = method_exists($annotation, 'getContext') ? $annotation->getContext() : $annotation->context;
×
UNCOV
159
        $groups = method_exists($annotation, 'getGroups') ? $annotation->getGroups() : $annotation->groups;
×
UNCOV
160
        $normalizationContext = method_exists($annotation, 'getNormalizationContext') ? $annotation->getNormalizationContext() : $annotation->normalizationContext;
×
161
        $denormalizationContext = method_exists($annotation, 'getDenormalizationContext') ? $annotation->getDenormalizationContext() : $annotation->denormalizationContext;
×
162

UNCOV
163
        if ($normalizationContext || $context) {
×
UNCOV
164
            $attributeMetadata->setNormalizationContextForGroups($normalizationContext ?: $context, $groups);
×
165
        }
166

UNCOV
167
        if ($denormalizationContext || $context) {
×
UNCOV
168
            $attributeMetadata->setDenormalizationContextForGroups($denormalizationContext ?: $context, $groups);
×
169
        }
170
    }
171
}
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