12
12
namespace Symfony \Component \SerDes \Hook \Serialize ;
13
13
14
14
use Symfony \Component \SerDes \Exception \InvalidArgumentException ;
15
+ use Symfony \Component \SerDes \Type \Type ;
15
16
use Symfony \Component \SerDes \Type \TypeExtractorInterface ;
16
17
use Symfony \Component \SerDes \Type \TypeGenericsHelper ;
18
+ use Symfony \Component \SerDes \Type \UnionType ;
17
19
18
20
/**
19
21
* @author Mathias Arlaud <[email protected] >
@@ -30,10 +32,24 @@ public function __construct(
30
32
$ this ->typeGenericsHelper = new TypeGenericsHelper ();
31
33
}
32
34
33
- public function __invoke (string $ type , string $ accessor , array $ context ): array
35
+ public function __invoke (Type $ type , string $ accessor, array $ properties , array $ context ): array
34
36
{
37
+ $ className = $ type ->className ();
38
+ $ context = $ this ->addGenericParameterTypes ($ type , $ context );
39
+
40
+ $ properties = array_filter ($ properties , fn (string $ name ): bool => $ this ->propertyHasMatchingGroup ($ className , $ name , $ context ), \ARRAY_FILTER_USE_KEY );
41
+
42
+ array_walk ($ properties , function (array &$ property , string $ name ) use ($ className , $ context ): void {
43
+ $ formatter = $ this ->propertyFormatter ($ className , $ name , $ context );
44
+
45
+ $ property ['name ' ] = $ this ->propertyName ($ className , $ name , $ context );
46
+ $ property ['type ' ] = $ this ->propertyType ($ className , $ name , $ formatter , $ context );
47
+ $ property ['accessor ' ] = $ this ->propertyAccessor ($ className , $ name , $ formatter , $ property ['accessor ' ], $ context );
48
+ });
49
+
35
50
return [
36
- 'context ' => $ this ->addGenericParameterTypes ($ type , $ context ),
51
+ 'properties ' => $ properties ,
52
+ 'context ' => $ context ,
37
53
];
38
54
}
39
55
@@ -42,27 +58,117 @@ public function __invoke(string $type, string $accessor, array $context): array
42
58
*
43
59
* @return array<string, mixed>
44
60
*/
45
- private function addGenericParameterTypes (string $ type , array $ context ): array
61
+ private function addGenericParameterTypes (Type $ type , array $ context ): array
46
62
{
47
- $ generics = $ this ->typeGenericsHelper ->extractGenerics ($ type );
63
+ $ className = $ type ->className ();
64
+ $ genericParameterTypes = $ type ->genericParameterTypes ();
48
65
49
- $ genericType = $ generics ['genericType ' ];
50
- $ genericParameters = $ generics ['genericParameters ' ];
66
+ $ templates = $ this ->typeExtractor ->extractTemplateFromClass (new \ReflectionClass ($ className ));
51
67
52
- if (! class_exists ( $ genericType )) {
53
- return $ context ;
68
+ if (\count ( $ templates ) !== \count ( $ genericParameterTypes )) {
69
+ throw new InvalidArgumentException ( sprintf ( ' Given %d generic parameters in "%s", but %d templates are defined in "%s". ' , \count ( $ genericParameterTypes ), ( string ) $ type , \count ( $ templates ), $ className )) ;
54
70
}
55
71
56
- $ templates = $ this ->typeExtractor ->extractTemplateFromClass (new \ReflectionClass ($ genericType ));
72
+ foreach ($ genericParameterTypes as $ i => $ genericParameterType ) {
73
+ $ context ['_symfony ' ]['generic_parameter_types ' ][$ className ][$ templates [$ i ]] = $ genericParameterType ;
74
+ }
75
+
76
+ return $ context ;
77
+ }
57
78
58
- if (\count ($ templates ) !== \count ($ genericParameters )) {
59
- throw new InvalidArgumentException (sprintf ('Given %d generic parameters in "%s", but %d templates are defined in "%s". ' , \count ($ genericParameters ), $ type , \count ($ templates ), $ genericType ));
79
+ /**
80
+ * @param class-string $className
81
+ * @param array<string, mixed> $context
82
+ */
83
+ private function propertyHasMatchingGroup (string $ className , string $ name , array $ context ): bool
84
+ {
85
+ if (!isset ($ context ['groups ' ])) {
86
+ return true ;
60
87
}
61
88
62
- foreach ($ genericParameters as $ i => $ genericParameter ) {
63
- $ context ['_symfony ' ]['generic_parameter_types ' ][$ genericType ][$ templates [$ i ]] = $ genericParameter ;
89
+ foreach ($ context ['groups ' ] as $ group ) {
90
+ if (isset ($ context ['_symfony ' ]['serialize ' ]['property_groups ' ][$ className ][$ name ][$ group ])) {
91
+ return true ;
92
+ }
64
93
}
65
94
66
- return $ context ;
95
+ return false ;
96
+ }
97
+
98
+ /**
99
+ * @param class-string $className
100
+ * @param array<string, mixed> $context
101
+ */
102
+ private function propertyName (string $ className , string $ name , array $ context ): string
103
+ {
104
+ if (isset ($ context ['_symfony ' ]['serialize ' ]['property_name ' ][$ className ][$ name ])) {
105
+ return $ context ['_symfony ' ]['serialize ' ]['property_name ' ][$ className ][$ name ];
106
+ }
107
+
108
+ return $ name ;
109
+ }
110
+
111
+ /**
112
+ * @param class-string $className
113
+ * @param array<string, mixed> $context
114
+ */
115
+ private function propertyFormatter (string $ className , string $ name , array $ context ): ?\ReflectionFunction
116
+ {
117
+ if (isset ($ context ['_symfony ' ]['serialize ' ]['property_formatter ' ][$ className ][$ name ])) {
118
+ return new \ReflectionFunction (\Closure::fromCallable ($ context ['_symfony ' ]['serialize ' ]['property_formatter ' ][$ className ][$ name ]));
119
+ }
120
+
121
+ return null ;
122
+ }
123
+
124
+ /**
125
+ * @param class-string $className
126
+ * @param array<string, mixed> $context
127
+ */
128
+ private function propertyType (string $ className , string $ name , ?\ReflectionFunction $ formatter , array $ context ): Type |UnionType
129
+ {
130
+ $ type = null === $ formatter
131
+ ? $ this ->typeExtractor ->extractFromProperty (new \ReflectionProperty ($ className , $ name ))
132
+ : $ this ->typeExtractor ->extractFromFunctionReturn ($ formatter );
133
+
134
+ // if method doesn't belong to the property class, ignore generic search
135
+ if (null !== $ formatter && $ formatter ->getClosureScopeClass ()?->getName() !== $ className ) {
136
+ return $ type ;
137
+ }
138
+
139
+ if ([] !== ($ genericTypes = $ context ['_symfony ' ]['generic_parameter_types ' ][$ className ] ?? [])) {
140
+ $ type = $ this ->typeGenericsHelper ->replaceGenericTypes ($ type , $ genericTypes );
141
+ }
142
+
143
+ return $ type ;
144
+ }
145
+
146
+ /**
147
+ * @param class-string $className
148
+ * @param array<string, mixed> $context
149
+ */
150
+ private function propertyAccessor (string $ className , string $ name , ?\ReflectionFunction $ formatter , string $ accessor , array $ context ): string
151
+ {
152
+ if (null === $ formatter ) {
153
+ return $ accessor ;
154
+ }
155
+
156
+ if (!$ formatter ->getClosureScopeClass ()?->hasMethod($ formatter ->getName ()) || !$ formatter ->isStatic ()) {
157
+ throw new InvalidArgumentException (sprintf ('Property formatter "%s" must be a static method. ' , sprintf ('%s::$%s ' , $ className , $ name )));
158
+ }
159
+
160
+ if (($ returnType = $ formatter ->getReturnType ()) instanceof \ReflectionNamedType && ('void ' === $ returnType ->getName () || 'never ' === $ returnType ->getName ())) {
161
+ throw new InvalidArgumentException (sprintf ('Return type of property formatter "%s" must not be "void" nor "never". ' , sprintf ('%s::$%s ' , $ className , $ name )));
162
+ }
163
+
164
+ if (null !== ($ contextParameter = $ formatter ->getParameters ()[1 ] ?? null )) {
165
+ $ contextParameterType = $ contextParameter ->getType ();
166
+
167
+ if (!$ contextParameterType instanceof \ReflectionNamedType || 'array ' !== $ contextParameterType ->getName ()) {
168
+ throw new InvalidArgumentException (sprintf ('Second argument of property formatter "%s" must be an array. ' , sprintf ('%s::$%s ' , $ className , $ name )));
169
+ }
170
+ }
171
+
172
+ return sprintf ('%s::%s(%s, $context) ' , $ formatter ->getClosureScopeClass ()->getName (), $ formatter ->getName (), $ accessor );
67
173
}
68
174
}
0 commit comments