2424use Symfony \Component \PropertyInfo \PropertyAccessExtractorInterface ;
2525use Symfony \Component \PropertyInfo \PropertyListExtractorInterface ;
2626use Symfony \Component \PropertyInfo \PropertyTypeExtractorInterface ;
27- use Symfony \Component \PropertyInfo \Type ;
27+ use Symfony \Component \PropertyInfo \Type as LegacyType ;
28+ use Symfony \Component \TypeInfo \Type ;
29+ use Symfony \Component \TypeInfo \TypeIdentifier ;
2830
2931/**
3032 * Extracts data using Doctrine ORM and ODM metadata.
@@ -55,8 +57,110 @@ public function getProperties(string $class, array $context = []): ?array
5557 return $ properties ;
5658 }
5759
60+ public function getType (string $ class , string $ property , array $ context = []): ?Type
61+ {
62+ if (null === $ metadata = $ this ->getMetadata ($ class )) {
63+ return null ;
64+ }
65+
66+ if ($ metadata ->hasAssociation ($ property )) {
67+ $ class = $ metadata ->getAssociationTargetClass ($ property );
68+
69+ if ($ metadata ->isSingleValuedAssociation ($ property )) {
70+ if ($ metadata instanceof ClassMetadata) {
71+ $ associationMapping = $ metadata ->getAssociationMapping ($ property );
72+ $ nullable = $ this ->isAssociationNullable ($ associationMapping );
73+ } else {
74+ $ nullable = false ;
75+ }
76+
77+ return $ nullable ? Type::nullable (Type::object ($ class )) : Type::object ($ class );
78+ }
79+
80+ $ collectionKeyType = TypeIdentifier::INT ;
81+
82+ if ($ metadata instanceof ClassMetadata) {
83+ $ associationMapping = $ metadata ->getAssociationMapping ($ property );
84+
85+ if (self ::getMappingValue ($ associationMapping , 'indexBy ' )) {
86+ $ subMetadata = $ this ->entityManager ->getClassMetadata (self ::getMappingValue ($ associationMapping , 'targetEntity ' ));
87+
88+ // Check if indexBy value is a property
89+ $ fieldName = self ::getMappingValue ($ associationMapping , 'indexBy ' );
90+ if (null === ($ typeOfField = $ subMetadata ->getTypeOfField ($ fieldName ))) {
91+ $ fieldName = $ subMetadata ->getFieldForColumn (self ::getMappingValue ($ associationMapping , 'indexBy ' ));
92+ // Not a property, maybe a column name?
93+ if (null === ($ typeOfField = $ subMetadata ->getTypeOfField ($ fieldName ))) {
94+ // Maybe the column name is the association join column?
95+ $ associationMapping = $ subMetadata ->getAssociationMapping ($ fieldName );
96+
97+ $ indexProperty = $ subMetadata ->getSingleAssociationReferencedJoinColumnName ($ fieldName );
98+ $ subMetadata = $ this ->entityManager ->getClassMetadata (self ::getMappingValue ($ associationMapping , 'targetEntity ' ));
99+
100+ // Not a property, maybe a column name?
101+ if (null === ($ typeOfField = $ subMetadata ->getTypeOfField ($ indexProperty ))) {
102+ $ fieldName = $ subMetadata ->getFieldForColumn ($ indexProperty );
103+ $ typeOfField = $ subMetadata ->getTypeOfField ($ fieldName );
104+ }
105+ }
106+ }
107+
108+ if (!$ collectionKeyType = $ this ->getTypeIdentifier ($ typeOfField )) {
109+ return null ;
110+ }
111+ }
112+ }
113+
114+ return Type::collection (Type::object (Collection::class), Type::object ($ class ), Type::builtin ($ collectionKeyType ));
115+ }
116+
117+ if ($ metadata instanceof ClassMetadata && isset ($ metadata ->embeddedClasses [$ property ])) {
118+ return Type::object (self ::getMappingValue ($ metadata ->embeddedClasses [$ property ], 'class ' ));
119+ }
120+
121+ if (!$ metadata ->hasField ($ property )) {
122+ return null ;
123+ }
124+
125+ $ typeOfField = $ metadata ->getTypeOfField ($ property );
126+
127+ if (!$ typeIdentifier = $ this ->getTypeIdentifier ($ typeOfField )) {
128+ return null ;
129+ }
130+
131+ $ nullable = $ metadata instanceof ClassMetadata && $ metadata ->isNullable ($ property );
132+ $ enumType = null ;
133+
134+ if (null !== $ enumClass = self ::getMappingValue ($ metadata ->getFieldMapping ($ property ), 'enumType ' ) ?? null ) {
135+ $ enumType = $ nullable ? Type::nullable (Type::enum ($ enumClass )) : Type::enum ($ enumClass );
136+ }
137+
138+ $ builtinType = $ nullable ? Type::nullable (Type::builtin ($ typeIdentifier )) : Type::builtin ($ typeIdentifier );
139+
140+ return match ($ typeIdentifier ) {
141+ TypeIdentifier::OBJECT => match ($ typeOfField ) {
142+ Types::DATE_MUTABLE , Types::DATETIME_MUTABLE , Types::DATETIMETZ_MUTABLE , 'vardatetime ' , Types::TIME_MUTABLE => $ nullable ? Type::nullable (Type::object (\DateTime::class)) : Type::object (\DateTime::class),
143+ Types::DATE_IMMUTABLE , Types::DATETIME_IMMUTABLE , Types::DATETIMETZ_IMMUTABLE , Types::TIME_IMMUTABLE => $ nullable ? Type::nullable (Type::object (\DateTimeImmutable::class)) : Type::object (\DateTimeImmutable::class),
144+ Types::DATEINTERVAL => $ nullable ? Type::nullable (Type::object (\DateInterval::class)) : Type::object (\DateInterval::class),
145+ default => $ builtinType ,
146+ },
147+ TypeIdentifier::ARRAY => match ($ typeOfField ) {
148+ 'array ' , 'json_array ' => $ enumType ? null : ($ nullable ? Type::nullable (Type::array ()) : Type::array ()),
149+ Types::SIMPLE_ARRAY => $ nullable ? Type::nullable (Type::list ($ enumType ?? Type::string ())) : Type::list ($ enumType ?? Type::string ()),
150+ default => $ builtinType ,
151+ },
152+ TypeIdentifier::INT , TypeIdentifier::STRING => $ enumType ? $ enumType : $ builtinType ,
153+ default => $ builtinType ,
154+ };
155+ }
156+
157+ /**
158+ * @deprecated since Symfony 7.1, use "getType" instead
159+ */
58160 public function getTypes (string $ class , string $ property , array $ context = []): ?array
59161 {
162+ trigger_deprecation ('symfony/property-info ' , '7.1 ' , 'The "%s()" method is deprecated, use "%s::getType()" instead. ' , __METHOD__ , self ::class);
163+
60164 if (null === $ metadata = $ this ->getMetadata ($ class )) {
61165 return null ;
62166 }
@@ -73,10 +177,10 @@ public function getTypes(string $class, string $property, array $context = []):
73177 $ nullable = false ;
74178 }
75179
76- return [new Type (Type ::BUILTIN_TYPE_OBJECT , $ nullable , $ class )];
180+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , $ nullable , $ class )];
77181 }
78182
79- $ collectionKeyType = Type ::BUILTIN_TYPE_INT ;
183+ $ collectionKeyType = LegacyType ::BUILTIN_TYPE_INT ;
80184
81185 if ($ metadata instanceof ClassMetadata) {
82186 $ associationMapping = $ metadata ->getAssociationMapping ($ property );
@@ -104,61 +208,61 @@ public function getTypes(string $class, string $property, array $context = []):
104208 }
105209 }
106210
107- if (!$ collectionKeyType = $ this ->getPhpType ($ typeOfField )) {
211+ if (!$ collectionKeyType = $ this ->getTypeIdentifierLegacy ($ typeOfField )) {
108212 return null ;
109213 }
110214 }
111215 }
112216
113- return [new Type (
114- Type ::BUILTIN_TYPE_OBJECT ,
217+ return [new LegacyType (
218+ LegacyType ::BUILTIN_TYPE_OBJECT ,
115219 false ,
116220 Collection::class,
117221 true ,
118- new Type ($ collectionKeyType ),
119- new Type (Type ::BUILTIN_TYPE_OBJECT , false , $ class )
222+ new LegacyType ($ collectionKeyType ),
223+ new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , false , $ class )
120224 )];
121225 }
122226
123227 if ($ metadata instanceof ClassMetadata && isset ($ metadata ->embeddedClasses [$ property ])) {
124- return [new Type (Type ::BUILTIN_TYPE_OBJECT , false , self ::getMappingValue ($ metadata ->embeddedClasses [$ property ], 'class ' ))];
228+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , false , self ::getMappingValue ($ metadata ->embeddedClasses [$ property ], 'class ' ))];
125229 }
126230
127231 if ($ metadata ->hasField ($ property )) {
128232 $ typeOfField = $ metadata ->getTypeOfField ($ property );
129233
130- if (!$ builtinType = $ this ->getPhpType ($ typeOfField )) {
234+ if (!$ builtinType = $ this ->getTypeIdentifierLegacy ($ typeOfField )) {
131235 return null ;
132236 }
133237
134238 $ nullable = $ metadata instanceof ClassMetadata && $ metadata ->isNullable ($ property );
135239 $ enumType = null ;
136240 if (null !== $ enumClass = self ::getMappingValue ($ metadata ->getFieldMapping ($ property ), 'enumType ' ) ?? null ) {
137- $ enumType = new Type (Type ::BUILTIN_TYPE_OBJECT , $ nullable , $ enumClass );
241+ $ enumType = new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , $ nullable , $ enumClass );
138242 }
139243
140244 switch ($ builtinType ) {
141- case Type ::BUILTIN_TYPE_OBJECT :
245+ case LegacyType ::BUILTIN_TYPE_OBJECT :
142246 switch ($ typeOfField ) {
143247 case Types::DATE_MUTABLE :
144248 case Types::DATETIME_MUTABLE :
145249 case Types::DATETIMETZ_MUTABLE :
146250 case 'vardatetime ' :
147251 case Types::TIME_MUTABLE :
148- return [new Type (Type ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateTime ' )];
252+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateTime ' )];
149253
150254 case Types::DATE_IMMUTABLE :
151255 case Types::DATETIME_IMMUTABLE :
152256 case Types::DATETIMETZ_IMMUTABLE :
153257 case Types::TIME_IMMUTABLE :
154- return [new Type (Type ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateTimeImmutable ' )];
258+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateTimeImmutable ' )];
155259
156260 case Types::DATEINTERVAL :
157- return [new Type (Type ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateInterval ' )];
261+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_OBJECT , $ nullable , 'DateInterval ' )];
158262 }
159263
160264 break ;
161- case Type ::BUILTIN_TYPE_ARRAY :
265+ case LegacyType ::BUILTIN_TYPE_ARRAY :
162266 switch ($ typeOfField ) {
163267 case 'array ' : // DBAL < 4
164268 case 'json_array ' : // DBAL < 3
@@ -167,21 +271,21 @@ public function getTypes(string $class, string $property, array $context = []):
167271 return null ;
168272 }
169273
170- return [new Type (Type ::BUILTIN_TYPE_ARRAY , $ nullable , null , true )];
274+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_ARRAY , $ nullable , null , true )];
171275
172276 case Types::SIMPLE_ARRAY :
173- return [new Type (Type ::BUILTIN_TYPE_ARRAY , $ nullable , null , true , new Type (Type ::BUILTIN_TYPE_INT ), $ enumType ?? new Type (Type ::BUILTIN_TYPE_STRING ))];
277+ return [new LegacyType (LegacyType ::BUILTIN_TYPE_ARRAY , $ nullable , null , true , new LegacyType (LegacyType ::BUILTIN_TYPE_INT ), $ enumType ?? new LegacyType (LegacyType ::BUILTIN_TYPE_STRING ))];
174278 }
175279 break ;
176- case Type ::BUILTIN_TYPE_INT :
177- case Type ::BUILTIN_TYPE_STRING :
280+ case LegacyType ::BUILTIN_TYPE_INT :
281+ case LegacyType ::BUILTIN_TYPE_STRING :
178282 if ($ enumType ) {
179283 return [$ enumType ];
180284 }
181285 break ;
182286 }
183287
184- return [new Type ($ builtinType , $ nullable )];
288+ return [new LegacyType ($ builtinType , $ nullable )];
185289 }
186290
187291 return null ;
@@ -244,20 +348,52 @@ private function isAssociationNullable(array|AssociationMapping $associationMapp
244348 /**
245349 * Gets the corresponding built-in PHP type.
246350 */
247- private function getPhpType (string $ doctrineType ): ?string
351+ private function getTypeIdentifier (string $ doctrineType ): ?TypeIdentifier
352+ {
353+ return match ($ doctrineType ) {
354+ Types::SMALLINT ,
355+ Types::INTEGER => TypeIdentifier::INT ,
356+ Types::FLOAT => TypeIdentifier::FLOAT ,
357+ Types::BIGINT ,
358+ Types::STRING ,
359+ Types::TEXT ,
360+ Types::GUID ,
361+ Types::DECIMAL => TypeIdentifier::STRING ,
362+ Types::BOOLEAN => TypeIdentifier::BOOL ,
363+ Types::BLOB ,
364+ Types::BINARY => TypeIdentifier::RESOURCE ,
365+ 'object ' , // DBAL < 4
366+ Types::DATE_MUTABLE ,
367+ Types::DATETIME_MUTABLE ,
368+ Types::DATETIMETZ_MUTABLE ,
369+ 'vardatetime ' ,
370+ Types::TIME_MUTABLE ,
371+ Types::DATE_IMMUTABLE ,
372+ Types::DATETIME_IMMUTABLE ,
373+ Types::DATETIMETZ_IMMUTABLE ,
374+ Types::TIME_IMMUTABLE ,
375+ Types::DATEINTERVAL => TypeIdentifier::OBJECT ,
376+ 'array ' , // DBAL < 4
377+ 'json_array ' , // DBAL < 3
378+ Types::SIMPLE_ARRAY => TypeIdentifier::ARRAY ,
379+ default => null ,
380+ };
381+ }
382+
383+ private function getTypeIdentifierLegacy (string $ doctrineType ): ?string
248384 {
249385 return match ($ doctrineType ) {
250386 Types::SMALLINT ,
251- Types::INTEGER => Type ::BUILTIN_TYPE_INT ,
252- Types::FLOAT => Type ::BUILTIN_TYPE_FLOAT ,
387+ Types::INTEGER => LegacyType ::BUILTIN_TYPE_INT ,
388+ Types::FLOAT => LegacyType ::BUILTIN_TYPE_FLOAT ,
253389 Types::BIGINT ,
254390 Types::STRING ,
255391 Types::TEXT ,
256392 Types::GUID ,
257- Types::DECIMAL => Type ::BUILTIN_TYPE_STRING ,
258- Types::BOOLEAN => Type ::BUILTIN_TYPE_BOOL ,
393+ Types::DECIMAL => LegacyType ::BUILTIN_TYPE_STRING ,
394+ Types::BOOLEAN => LegacyType ::BUILTIN_TYPE_BOOL ,
259395 Types::BLOB ,
260- Types::BINARY => Type ::BUILTIN_TYPE_RESOURCE ,
396+ Types::BINARY => LegacyType ::BUILTIN_TYPE_RESOURCE ,
261397 'object ' , // DBAL < 4
262398 Types::DATE_MUTABLE ,
263399 Types::DATETIME_MUTABLE ,
@@ -268,10 +404,10 @@ private function getPhpType(string $doctrineType): ?string
268404 Types::DATETIME_IMMUTABLE ,
269405 Types::DATETIMETZ_IMMUTABLE ,
270406 Types::TIME_IMMUTABLE ,
271- Types::DATEINTERVAL => Type ::BUILTIN_TYPE_OBJECT ,
407+ Types::DATEINTERVAL => LegacyType ::BUILTIN_TYPE_OBJECT ,
272408 'array ' , // DBAL < 4
273409 'json_array ' , // DBAL < 3
274- Types::SIMPLE_ARRAY => Type ::BUILTIN_TYPE_ARRAY ,
410+ Types::SIMPLE_ARRAY => LegacyType ::BUILTIN_TYPE_ARRAY ,
275411 default => null ,
276412 };
277413 }
0 commit comments