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

api-platform / core / 17054069864

18 Aug 2025 10:27PM UTC coverage: 21.952% (+0.2%) from 21.769%
17054069864

Pull #7151

github

web-flow
Merge 0da010d8d into 6491bfc7a
Pull Request #7151: fix: 7119 parameter array shape uses invalid syntax

11524 of 52497 relevant lines covered (21.95%)

11.86 hits per line

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

0.0
/src/Doctrine/Odm/PropertyInfo/DoctrineExtractor.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\Doctrine\Odm\PropertyInfo;
15

16
use Doctrine\Common\Collections\Collection;
17
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDbClassMetadata;
18
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
19
use Doctrine\Persistence\Mapping\ClassMetadata;
20
use Doctrine\Persistence\Mapping\MappingException;
21
use Doctrine\Persistence\ObjectManager;
22
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
23
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
24
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
25
use Symfony\Component\PropertyInfo\Type as LegacyType;
26
use Symfony\Component\TypeInfo\Type;
27
use Symfony\Component\TypeInfo\TypeIdentifier;
28

29
/**
30
 * Extracts data using Doctrine MongoDB ODM metadata.
31
 *
32
 * @author Kévin Dunglas <dunglas@gmail.com>
33
 * @author Alan Poulain <contact@alanpoulain.eu>
34
 */
35
final class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
36
{
37
    public function __construct(private readonly ObjectManager $objectManager)
38
    {
39
    }
×
40

41
    /**
42
     * {@inheritdoc}
43
     *
44
     * @param string $class
45
     *
46
     * @return string[]|null
47
     */
48
    public function getProperties($class, array $context = []): ?array
49
    {
50
        if (null === $metadata = $this->getMetadata($class)) {
×
51
            return null;
×
52
        }
53

54
        return $metadata->getFieldNames();
×
55
    }
56

57
    public function getType(string $class, string $property, array $context = []): ?Type
58
    {
59
        if (null === $metadata = $this->getMetadata($class)) {
×
60
            return null;
×
61
        }
62

63
        if ($metadata->hasAssociation($property)) {
×
64
            /** @var class-string|null */
65
            $class = $metadata->getAssociationTargetClass($property);
×
66

67
            if (null === $class) {
×
68
                return null;
×
69
            }
70

71
            if ($metadata->isSingleValuedAssociation($property)) {
×
72
                $nullable = $metadata instanceof MongoDbClassMetadata && $metadata->isNullable($property);
×
73

74
                return $nullable ? Type::nullable(Type::object($class)) : Type::object($class);
×
75
            }
76

77
            return Type::collection(Type::object(Collection::class), Type::object($class), Type::int());
×
78
        }
79

80
        if (!$metadata->hasField($property)) {
×
81
            return null;
×
82
        }
83

84
        $typeOfField = $metadata->getTypeOfField($property);
×
85

86
        if (!$typeIdentifier = $this->getTypeIdentifier($typeOfField)) {
×
87
            return null;
×
88
        }
89

90
        $nullable = $metadata instanceof MongoDbClassMetadata && $metadata->isNullable($property);
×
91
        $enumType = null;
×
92

93
        if (null !== $enumClass = $metadata instanceof MongoDbClassMetadata ? $metadata->getFieldMapping($property)['enumType'] ?? null : null) {
×
94
            $enumType = $nullable ? Type::nullable(Type::enum($enumClass)) : Type::enum($enumClass);
×
95
        }
96

97
        $builtinType = $nullable ? Type::nullable(Type::builtin($typeIdentifier)) : Type::builtin($typeIdentifier);
×
98

99
        $type = match ($typeOfField) {
×
100
            MongoDbType::DATE => Type::object(\DateTime::class),
×
101
            MongoDbType::DATE_IMMUTABLE => Type::object(\DateTimeImmutable::class),
×
102
            MongoDbType::HASH => Type::array(),
×
103
            MongoDbType::COLLECTION => Type::list(),
×
104
            MongoDbType::INT, MongoDbType::INTEGER, MongoDbType::STRING => $enumType ? $enumType : $builtinType,
×
105
            default => $builtinType,
×
106
        };
×
107

108
        return $nullable ? Type::nullable($type) : $type;
×
109
    }
110

111
    /**
112
     * {@inheritdoc}
113
     *
114
     * // deprecated since 4.2 use "getType" instead
115
     *
116
     * @param string $class
117
     * @param string $property
118
     *
119
     * @return LegacyType[]|null
120
     */
121
    public function getTypes($class, $property, array $context = []): ?array
122
    {
123
        trigger_deprecation('api-platform/core', '4.2', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class);
×
124

125
        if (null === $metadata = $this->getMetadata($class)) {
×
126
            return null;
×
127
        }
128

129
        if ($metadata->hasAssociation($property)) {
×
130
            /** @var class-string|null */
131
            $class = $metadata->getAssociationTargetClass($property);
×
132

133
            if (null === $class) {
×
134
                return null;
×
135
            }
136

137
            if ($metadata->isSingleValuedAssociation($property)) {
×
138
                $nullable = $metadata instanceof MongoDbClassMetadata && $metadata->isNullable($property);
×
139

140
                return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $class)];
×
141
            }
142

143
            $collectionKeyType = LegacyType::BUILTIN_TYPE_INT;
×
144

145
            return [
×
146
                new LegacyType(
×
147
                    LegacyType::BUILTIN_TYPE_OBJECT,
×
148
                    false,
×
149
                    Collection::class,
×
150
                    true,
×
151
                    new LegacyType($collectionKeyType),
×
152
                    new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, $class)
×
153
                ),
×
154
            ];
×
155
        }
156

157
        if ($metadata->hasField($property)) {
×
158
            $typeOfField = $metadata->getTypeOfField($property);
×
159
            $nullable = $metadata instanceof MongoDbClassMetadata && $metadata->isNullable($property);
×
160
            $enumType = null;
×
161
            if (null !== $enumClass = $metadata instanceof MongoDbClassMetadata ? $metadata->getFieldMapping($property)['enumType'] ?? null : null) {
×
162
                $enumType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, $enumClass);
×
163
            }
164

165
            switch ($typeOfField) {
166
                case MongoDbType::DATE:
×
167
                    return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, \DateTime::class)];
×
168
                case MongoDbType::DATE_IMMUTABLE:
×
169
                    return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, $nullable, \DateTimeImmutable::class)];
×
170
                case MongoDbType::HASH:
×
171
                    return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true)];
×
172
                case MongoDbType::COLLECTION:
×
173
                    return [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT))];
×
174
                case MongoDbType::INT:
×
175
                case MongoDbType::STRING:
×
176
                    if ($enumType) {
×
177
                        return [$enumType];
×
178
                    }
179
            }
180

181
            $builtinType = $this->getNativeTypeLegacy($typeOfField);
×
182

183
            return $builtinType ? [new LegacyType($builtinType, $nullable)] : null;
×
184
        }
185

186
        return null;
×
187
    }
188

189
    /**
190
     * {@inheritdoc}
191
     *
192
     * @param string $class
193
     * @param string $property
194
     */
195
    public function isReadable($class, $property, array $context = []): ?bool
196
    {
197
        return null;
×
198
    }
199

200
    /**
201
     * {@inheritdoc}
202
     *
203
     * @param string $class
204
     * @param string $property
205
     */
206
    public function isWritable($class, $property, array $context = []): ?bool
207
    {
208
        if (
209
            null === ($metadata = $this->getMetadata($class))
×
210
            || ($metadata instanceof MongoDbClassMetadata && MongoDbClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType)
×
211
            || !\in_array($property, $metadata->getIdentifierFieldNames(), true)
×
212
        ) {
213
            return null;
×
214
        }
215

216
        return false;
×
217
    }
218

219
    private function getMetadata(string $class): ?ClassMetadata
220
    {
221
        try {
222
            return $this->objectManager->getClassMetadata($class);
×
223
        } catch (MappingException) {
×
224
            return null;
×
225
        }
226
    }
227

228
    /**
229
     * Gets the corresponding built-in PHP type identifier.
230
     */
231
    private function getTypeIdentifier(string $doctrineType): ?TypeIdentifier
232
    {
233
        return match ($doctrineType) {
×
234
            MongoDbType::INTEGER, MongoDbType::INT, MongoDbType::INTID, MongoDbType::KEY => TypeIdentifier::INT,
×
235
            MongoDbType::FLOAT => TypeIdentifier::FLOAT,
×
236
            MongoDbType::STRING, MongoDbType::ID, MongoDbType::OBJECTID, MongoDbType::TIMESTAMP, MongoDbType::BINDATA, MongoDbType::BINDATABYTEARRAY, MongoDbType::BINDATACUSTOM, MongoDbType::BINDATAFUNC, MongoDbType::BINDATAMD5, MongoDbType::BINDATAUUID, MongoDbType::BINDATAUUIDRFC4122 => TypeIdentifier::STRING,
×
237
            MongoDbType::BOOLEAN, MongoDbType::BOOL => TypeIdentifier::BOOL,
×
238
            MongoDbType::DATE, MongoDbType::DATE_IMMUTABLE => TypeIdentifier::OBJECT,
×
239
            MongoDbType::HASH, MongoDbType::COLLECTION => TypeIdentifier::ARRAY,
×
240
            default => null,
×
241
        };
×
242
    }
243

244
    private function getNativeTypeLegacy(string $doctrineType): ?string
245
    {
246
        return match ($doctrineType) {
×
247
            MongoDbType::INTEGER, MongoDbType::INT, MongoDbType::INTID, MongoDbType::KEY => LegacyType::BUILTIN_TYPE_INT,
×
248
            MongoDbType::FLOAT => LegacyType::BUILTIN_TYPE_FLOAT,
×
249
            MongoDbType::STRING, MongoDbType::ID, MongoDbType::OBJECTID, MongoDbType::TIMESTAMP, MongoDbType::BINDATA, MongoDbType::BINDATABYTEARRAY, MongoDbType::BINDATACUSTOM, MongoDbType::BINDATAFUNC, MongoDbType::BINDATAMD5, MongoDbType::BINDATAUUID, MongoDbType::BINDATAUUIDRFC4122 => LegacyType::BUILTIN_TYPE_STRING,
×
250
            MongoDbType::BOOLEAN, MongoDbType::BOOL => LegacyType::BUILTIN_TYPE_BOOL,
×
251
            default => null,
×
252
        };
×
253
    }
254
}
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