[TASK] Update php-cs-fixer to 2.5.0
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Reflection / ObjectAccess.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Reflection;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
18
19 /**
20 * Provides methods to call appropriate getter/setter on an object given the
21 * property name. It does this following these rules:
22 * - if the target object is an instance of ArrayAccess, it gets/sets the property
23 * - if public getter/setter method exists, call it.
24 * - if public property exists, return/set the value of it.
25 * - else, throw exception
26 */
27 class ObjectAccess
28 {
29 const ACCESS_GET = 0;
30
31 const ACCESS_SET = 1;
32
33 const ACCESS_PUBLIC = 2;
34
35 /**
36 * Get a property of a given object.
37 * Tries to get the property the following ways:
38 * - if the target is an array, and has this property, we call it.
39 * - if super cow powers should be used, fetch value through reflection
40 * - if public getter method exists, call it.
41 * - if the target object is an instance of ArrayAccess, it gets the property
42 * on it if it exists.
43 * - if public property exists, return the value of it.
44 * - else, throw exception
45 *
46 * @param mixed $subject Object or array to get the property from
47 * @param string $propertyName name of the property to retrieve
48 * @param bool $forceDirectAccess directly access property using reflection(!)
49 *
50 * @throws \InvalidArgumentException in case $subject was not an object or $propertyName was not a string
51 * @return mixed Value of the property
52 */
53 public static function getProperty($subject, $propertyName, $forceDirectAccess = false)
54 {
55 if (!is_object($subject) && !is_array($subject)) {
56 throw new \InvalidArgumentException('$subject must be an object or array, ' . gettype($subject) . ' given.', 1237301367);
57 }
58 if (!is_string($propertyName) && (!is_array($subject) && !$subject instanceof \ArrayAccess)) {
59 throw new \InvalidArgumentException('Given property name is not of type string.', 1231178303);
60 }
61 return self::getPropertyInternal($subject, $propertyName, $forceDirectAccess);
62 }
63
64 /**
65 * Gets a property of a given object or array.
66 * This is an internal method that does only limited type checking for performance reasons.
67 * If you can't make sure that $subject is either of type array or object and $propertyName of type string you should use getProperty() instead.
68 *
69 * @see getProperty()
70 *
71 * @param mixed $subject Object or array to get the property from
72 * @param string $propertyName name of the property to retrieve
73 * @param bool $forceDirectAccess directly access property using reflection(!)
74 *
75 * @throws Exception\PropertyNotAccessibleException
76 * @return mixed Value of the property
77 * @internal
78 */
79 public static function getPropertyInternal($subject, $propertyName, $forceDirectAccess = false)
80 {
81 // type check and conversion of iterator to numerically indexed array
82 if ($subject === null || is_scalar($subject)) {
83 return null;
84 }
85 if (!$forceDirectAccess && ($subject instanceof \SplObjectStorage || $subject instanceof ObjectStorage)) {
86 $subject = iterator_to_array(clone $subject, false);
87 }
88
89 // value get based on data type of $subject (possibly converted above)
90 if (($subject instanceof \ArrayAccess && $subject->offsetExists($propertyName)) || is_array($subject)) {
91 // isset() is safe; array_key_exists would only be needed to determine
92 // if the value is NULL - and that's already what we return as fallback.
93 if (isset($subject[$propertyName])) {
94 return $subject[$propertyName];
95 }
96 } elseif (is_object($subject)) {
97 if ($forceDirectAccess) {
98 if (property_exists($subject, $propertyName)) {
99 $propertyReflection = new PropertyReflection($subject, $propertyName);
100 return $propertyReflection->getValue($subject);
101 }
102 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
103 }
104 $upperCasePropertyName = ucfirst($propertyName);
105 $getterMethodName = 'get' . $upperCasePropertyName;
106 if (is_callable([$subject, $getterMethodName])) {
107 return $subject->{$getterMethodName}();
108 }
109 $getterMethodName = 'is' . $upperCasePropertyName;
110 if (is_callable([$subject, $getterMethodName])) {
111 return $subject->{$getterMethodName}();
112 }
113 $getterMethodName = 'has' . $upperCasePropertyName;
114 if (is_callable([$subject, $getterMethodName])) {
115 return $subject->{$getterMethodName}();
116 }
117 if (property_exists($subject, $propertyName)) {
118 return $subject->{$propertyName};
119 }
120 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1476109666);
121 }
122
123 return null;
124 }
125
126 /**
127 * Gets a property path from a given object or array.
128 *
129 * If propertyPath is "bla.blubb", then we first call getProperty($object, 'bla'),
130 * and on the resulting object we call getProperty(..., 'blubb')
131 *
132 * For arrays the keys are checked likewise.
133 *
134 * @param mixed $subject Object or array to get the property path from
135 * @param string $propertyPath
136 *
137 * @return mixed Value of the property
138 */
139 public static function getPropertyPath($subject, $propertyPath)
140 {
141 $propertyPathSegments = explode('.', $propertyPath);
142 try {
143 foreach ($propertyPathSegments as $pathSegment) {
144 $subject = self::getPropertyInternal($subject, $pathSegment);
145 }
146 } catch (Exception\PropertyNotAccessibleException $error) {
147 return null;
148 }
149 return $subject;
150 }
151
152 /**
153 * Set a property for a given object.
154 * Tries to set the property the following ways:
155 * - if target is an array, set value
156 * - if super cow powers should be used, set value through reflection
157 * - if public setter method exists, call it.
158 * - if public property exists, set it directly.
159 * - if the target object is an instance of ArrayAccess, it sets the property
160 * on it without checking if it existed.
161 * - else, return FALSE
162 *
163 * @param mixed &$subject The target object or array
164 * @param string $propertyName Name of the property to set
165 * @param mixed $propertyValue Value of the property
166 * @param bool $forceDirectAccess directly access property using reflection(!)
167 *
168 * @throws \InvalidArgumentException in case $object was not an object or $propertyName was not a string
169 * @return bool TRUE if the property could be set, FALSE otherwise
170 */
171 public static function setProperty(&$subject, $propertyName, $propertyValue, $forceDirectAccess = false)
172 {
173 if (is_array($subject) || ($subject instanceof \ArrayAccess && !$forceDirectAccess)) {
174 $subject[$propertyName] = $propertyValue;
175 return true;
176 }
177 if (!is_object($subject)) {
178 throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368);
179 }
180 if (!is_string($propertyName)) {
181 throw new \InvalidArgumentException('Given property name is not of type string.', 1231178878);
182 }
183 $result = true;
184 if ($forceDirectAccess) {
185 if (property_exists($subject, $propertyName)) {
186 $propertyReflection = new PropertyReflection($subject, $propertyName);
187 $propertyReflection->setAccessible(true);
188 $propertyReflection->setValue($subject, $propertyValue);
189 } else {
190 $subject->{$propertyName} = $propertyValue;
191 }
192 return $result;
193 }
194 $setterMethodName = self::buildSetterMethodName($propertyName);
195 if (is_callable([$subject, $setterMethodName])) {
196 $subject->{$setterMethodName}($propertyValue);
197 } elseif (property_exists($subject, $propertyName)) {
198 $reflection = new PropertyReflection($subject, $propertyName);
199 if ($reflection->isPublic()) {
200 $subject->{$propertyName} = $propertyValue;
201 } else {
202 $result = false;
203 }
204 } else {
205 $result = false;
206 }
207 return $result;
208 }
209
210 /**
211 * Returns an array of properties which can be get with the getProperty()
212 * method.
213 * Includes the following properties:
214 * - which can be get through a public getter method.
215 * - public properties which can be directly get.
216 *
217 * @param object $object Object to receive property names for
218 *
219 * @throws \InvalidArgumentException
220 * @return array Array of all gettable property names
221 */
222 public static function getGettablePropertyNames($object)
223 {
224 if (!is_object($object)) {
225 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301369);
226 }
227 if ($object instanceof \stdClass) {
228 $properties = array_keys((array)$object);
229 sort($properties);
230 return $properties;
231 }
232
233 $reflection = new \ReflectionClass($object);
234 $declaredPropertyNames = array_map(
235 function (\ReflectionProperty $property) {
236 return $property->getName();
237 },
238 $reflection->getProperties(\ReflectionProperty::IS_PUBLIC)
239 );
240 foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
241 $methodParameters = $method->getParameters();
242 if (!empty($methodParameters)) {
243 foreach ($methodParameters as $parameter) {
244 if (!$parameter->isOptional()) {
245 continue 2;
246 }
247 }
248 }
249 $methodName = $method->getName();
250 if (substr($methodName, 0, 2) === 'is') {
251 $declaredPropertyNames[] = lcfirst(substr($methodName, 2));
252 }
253 if (substr($methodName, 0, 3) === 'get') {
254 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
255 }
256 if (substr($methodName, 0, 3) === 'has') {
257 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
258 }
259 }
260 $propertyNames = array_unique($declaredPropertyNames);
261 sort($propertyNames);
262
263 return $propertyNames;
264 }
265
266 /**
267 * Returns an array of properties which can be set with the setProperty()
268 * method.
269 * Includes the following properties:
270 * - which can be set through a public setter method.
271 * - public properties which can be directly set.
272 *
273 * @param object $object Object to receive property names for
274 *
275 * @throws \InvalidArgumentException
276 * @return array Array of all settable property names
277 */
278 public static function getSettablePropertyNames($object)
279 {
280 if (!is_object($object)) {
281 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1264022994);
282 }
283 if ($object instanceof \stdClass) {
284 $declaredPropertyNames = array_keys((array)$object);
285 } else {
286 $declaredPropertyNames = array_keys(get_class_vars(get_class($object)));
287 }
288 foreach (get_class_methods($object) as $methodName) {
289 if (substr($methodName, 0, 3) === 'set' && is_callable([$object, $methodName])) {
290 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
291 }
292 }
293 $propertyNames = array_unique($declaredPropertyNames);
294 sort($propertyNames);
295 return $propertyNames;
296 }
297
298 /**
299 * Tells if the value of the specified property can be set by this Object Accessor.
300 *
301 * @param object $object Object containting the property
302 * @param string $propertyName Name of the property to check
303 *
304 * @throws \InvalidArgumentException
305 * @return bool
306 */
307 public static function isPropertySettable($object, $propertyName)
308 {
309 if (!is_object($object)) {
310 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828920);
311 }
312 if ($object instanceof \stdClass && array_search($propertyName, array_keys(get_object_vars($object))) !== false) {
313 return true;
314 }
315 if (array_search($propertyName, array_keys(get_class_vars(get_class($object)))) !== false) {
316 return true;
317 }
318 return is_callable([$object, self::buildSetterMethodName($propertyName)]);
319 }
320
321 /**
322 * Tells if the value of the specified property can be retrieved by this Object Accessor.
323 *
324 * @param object $object Object containting the property
325 * @param string $propertyName Name of the property to check
326 *
327 * @throws \InvalidArgumentException
328 * @return bool
329 */
330 public static function isPropertyGettable($object, $propertyName)
331 {
332 if (!is_object($object)) {
333 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828921);
334 }
335 if ($object instanceof \ArrayAccess && isset($object[$propertyName])) {
336 return true;
337 }
338 if ($object instanceof \stdClass && isset($object->$propertyName)) {
339 return true;
340 }
341 if (is_callable([$object, 'get' . ucfirst($propertyName)])) {
342 return true;
343 }
344 if (is_callable([$object, 'has' . ucfirst($propertyName)])) {
345 return true;
346 }
347 if (is_callable([$object, 'is' . ucfirst($propertyName)])) {
348 return true;
349 }
350 if (property_exists($object, $propertyName)) {
351 $propertyReflection = new PropertyReflection($object, $propertyName);
352 return $propertyReflection->isPublic();
353 }
354 return false;
355 }
356
357 /**
358 * Get all properties (names and their current values) of the current
359 * $object that are accessible through this class.
360 *
361 * @param object $object Object to get all properties from.
362 *
363 * @throws \InvalidArgumentException
364 * @return array Associative array of all properties.
365 * @todo What to do with ArrayAccess
366 */
367 public static function getGettableProperties($object)
368 {
369 if (!is_object($object)) {
370 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301370);
371 }
372 $properties = [];
373 foreach (self::getGettablePropertyNames($object) as $propertyName) {
374 $properties[$propertyName] = self::getPropertyInternal($object, $propertyName);
375 }
376 return $properties;
377 }
378
379 /**
380 * Build the setter method name for a given property by capitalizing the
381 * first letter of the property, and prepending it with "set".
382 *
383 * @param string $propertyName Name of the property
384 *
385 * @return string Name of the setter method name
386 */
387 public static function buildSetterMethodName($propertyName)
388 {
389 return 'set' . ucfirst($propertyName);
390 }
391 }