36
36
*/
37
37
class GetSetMethodNormalizer extends AbstractObjectNormalizer
38
38
{
39
+ private static $ reflectionCache = [];
39
40
private static $ setterAccessibleCache = [];
40
41
41
42
/**
42
43
* {@inheritdoc}
43
44
*/
44
45
public function supportsNormalization ($ data , ?string $ format = null )
45
46
{
46
- return parent ::supportsNormalization ($ data , $ format ) && $ this ->supports (\get_class ($ data ));
47
+ return parent ::supportsNormalization ($ data , $ format ) && $ this ->supports (\get_class ($ data ), true );
47
48
}
48
49
49
50
/**
50
51
* {@inheritdoc}
51
52
*/
52
53
public function supportsDenormalization ($ data , string $ type , ?string $ format = null )
53
54
{
54
- return parent ::supportsDenormalization ($ data , $ type , $ format ) && $ this ->supports ($ type );
55
+ return parent ::supportsDenormalization ($ data , $ type , $ format ) && $ this ->supports ($ type, false );
55
56
}
56
57
57
58
/**
@@ -63,18 +64,22 @@ public function hasCacheableSupportsMethod(): bool
63
64
}
64
65
65
66
/**
66
- * Checks if the given class has any getter method.
67
+ * Checks if the given class has any getter or setter method.
67
68
*/
68
- private function supports (string $ class ): bool
69
+ private function supports (string $ class, bool $ readAttributes ): bool
69
70
{
70
71
if (null !== $ this ->classDiscriminatorResolver && $ this ->classDiscriminatorResolver ->getMappingForClass ($ class )) {
71
72
return true ;
72
73
}
73
74
74
- $ class = new \ReflectionClass ($ class );
75
- $ methods = $ class ->getMethods (\ReflectionMethod::IS_PUBLIC );
76
- foreach ($ methods as $ method ) {
77
- if ($ this ->isGetMethod ($ method )) {
75
+ if (!isset (self ::$ reflectionCache [$ class ])) {
76
+ self ::$ reflectionCache [$ class ] = new \ReflectionClass ($ class );
77
+ }
78
+
79
+ $ reflection = self ::$ reflectionCache [$ class ];
80
+
81
+ foreach ($ reflection ->getMethods (\ReflectionMethod::IS_PUBLIC ) as $ reflectionMethod ) {
82
+ if ($ readAttributes ? $ this ->isGetMethod ($ reflectionMethod ) : $ this ->isSetMethod ($ reflectionMethod )) {
78
83
return true ;
79
84
}
80
85
}
@@ -95,6 +100,17 @@ private function isGetMethod(\ReflectionMethod $method): bool
95
100
);
96
101
}
97
102
103
+ /**
104
+ * Checks if a method's name matches /^set.+$/ and can be called non-statically with one parameter.
105
+ */
106
+ private function isSetMethod (\ReflectionMethod $ method ): bool
107
+ {
108
+ return !$ method ->isStatic ()
109
+ && (\PHP_VERSION_ID < 80000 || !$ method ->getAttributes (Ignore::class))
110
+ && 1 === $ method ->getNumberOfRequiredParameters ()
111
+ && str_starts_with ($ method ->name , 'set ' );
112
+ }
113
+
98
114
/**
99
115
* {@inheritdoc}
100
116
*/
@@ -124,19 +140,17 @@ protected function extractAttributes(object $object, ?string $format = null, arr
124
140
*/
125
141
protected function getAttributeValue (object $ object , string $ attribute , ?string $ format = null , array $ context = [])
126
142
{
127
- $ ucfirsted = ucfirst ($ attribute );
128
-
129
- $ getter = 'get ' .$ ucfirsted ;
143
+ $ getter = 'get ' .$ attribute ;
130
144
if (method_exists ($ object , $ getter ) && \is_callable ([$ object , $ getter ])) {
131
145
return $ object ->$ getter ();
132
146
}
133
147
134
- $ isser = 'is ' .$ ucfirsted ;
148
+ $ isser = 'is ' .$ attribute ;
135
149
if (method_exists ($ object , $ isser ) && \is_callable ([$ object , $ isser ])) {
136
150
return $ object ->$ isser ();
137
151
}
138
152
139
- $ haser = 'has ' .$ ucfirsted ;
153
+ $ haser = 'has ' .$ attribute ;
140
154
if (method_exists ($ object , $ haser ) && \is_callable ([$ object , $ haser ])) {
141
155
return $ object ->$ haser ();
142
156
}
@@ -149,7 +163,7 @@ protected function getAttributeValue(object $object, string $attribute, ?string
149
163
*/
150
164
protected function setAttributeValue (object $ object , string $ attribute , $ value , ?string $ format = null , array $ context = [])
151
165
{
152
- $ setter = 'set ' .ucfirst ( $ attribute) ;
166
+ $ setter = 'set ' .$ attribute ;
153
167
$ key = \get_class ($ object ).': ' .$ setter ;
154
168
155
169
if (!isset (self ::$ setterAccessibleCache [$ key ])) {
@@ -160,4 +174,48 @@ protected function setAttributeValue(object $object, string $attribute, $value,
160
174
$ object ->$ setter ($ value );
161
175
}
162
176
}
177
+
178
+ protected function isAllowedAttribute ($ classOrObject , string $ attribute , ?string $ format = null , array $ context = [])
179
+ {
180
+ if (!parent ::isAllowedAttribute ($ classOrObject , $ attribute , $ format , $ context )) {
181
+ return false ;
182
+ }
183
+
184
+ $ class = \is_object ($ classOrObject ) ? \get_class ($ classOrObject ) : $ classOrObject ;
185
+
186
+ if (!isset (self ::$ reflectionCache [$ class ])) {
187
+ self ::$ reflectionCache [$ class ] = new \ReflectionClass ($ class );
188
+ }
189
+
190
+ $ reflection = self ::$ reflectionCache [$ class ];
191
+
192
+ if ($ context ['_read_attributes ' ] ?? true ) {
193
+ foreach (['get ' , 'is ' , 'has ' ] as $ getterPrefix ) {
194
+ $ getter = $ getterPrefix .$ attribute ;
195
+ $ reflectionMethod = $ reflection ->hasMethod ($ getter ) ? $ reflection ->getMethod ($ getter ) : null ;
196
+ if ($ reflectionMethod && $ this ->isGetMethod ($ reflectionMethod )) {
197
+ return true ;
198
+ }
199
+ }
200
+
201
+ return false ;
202
+ }
203
+
204
+ $ setter = 'set ' .$ attribute ;
205
+ if ($ reflection ->hasMethod ($ setter ) && $ this ->isSetMethod ($ reflection ->getMethod ($ setter ))) {
206
+ return true ;
207
+ }
208
+
209
+ $ constructor = $ reflection ->getConstructor ();
210
+
211
+ if ($ constructor && $ constructor ->isPublic ()) {
212
+ foreach ($ constructor ->getParameters () as $ parameter ) {
213
+ if ($ parameter ->getName () === $ attribute ) {
214
+ return true ;
215
+ }
216
+ }
217
+ }
218
+
219
+ return false ;
220
+ }
163
221
}
0 commit comments