[TASK] Improve duplicate exception code check
[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 } elseif (!$forceDirectAccess && ($subject instanceof \SplObjectStorage || $subject instanceof ObjectStorage)) {
85 $subject = iterator_to_array($subject, false);
86 }
87
88 // value get based on data type of $subject (possibly converted above)
89 if ($subject instanceof \ArrayAccess || is_array($subject)) {
90 // isset() is safe; array_key_exists would only be needed to determine
91 // if the value is NULL - and that's already what we return as fallback.
92 if (isset($subject[$propertyName])) {
93 return $subject[$propertyName];
94 }
95 } elseif (is_object($subject)) {
96 if ($forceDirectAccess) {
97 if (property_exists($subject, $propertyName)) {
98 $propertyReflection = new PropertyReflection($subject, $propertyName);
99 return $propertyReflection->getValue($subject);
100 } else {
101 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
102 }
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 } else {
120 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1476109666);
121 }
122 }
123
124 return null;
125 }
126
127 /**
128 * Gets a property path from a given object or array.
129 *
130 * If propertyPath is "bla.blubb", then we first call getProperty($object, 'bla'),
131 * and on the resulting object we call getProperty(..., 'blubb')
132 *
133 * For arrays the keys are checked likewise.
134 *
135 * @param mixed $subject Object or array to get the property path from
136 * @param string $propertyPath
137 *
138 * @return mixed Value of the property
139 */
140 public static function getPropertyPath($subject, $propertyPath)
141 {
142 $propertyPathSegments = explode('.', $propertyPath);
143 try {
144 foreach ($propertyPathSegments as $pathSegment) {
145 $subject = self::getPropertyInternal($subject, $pathSegment);
146 }
147 } catch (Exception\PropertyNotAccessibleException $error) {
148 return null;
149 }
150 return $subject;
151 }
152
153 /**
154 * Set a property for a given object.
155 * Tries to set the property the following ways:
156 * - if target is an array, set value
157 * - if super cow powers should be used, set value through reflection
158 * - if public setter method exists, call it.
159 * - if public property exists, set it directly.
160 * - if the target object is an instance of ArrayAccess, it sets the property
161 * on it without checking if it existed.
162 * - else, return FALSE
163 *
164 * @param mixed &$subject The target object or array
165 * @param string $propertyName Name of the property to set
166 * @param mixed $propertyValue Value of the property
167 * @param bool $forceDirectAccess directly access property using reflection(!)
168 *
169 * @throws \InvalidArgumentException in case $object was not an object or $propertyName was not a string
170 * @return bool TRUE if the property could be set, FALSE otherwise
171 */
172 public static function setProperty(&$subject, $propertyName, $propertyValue, $forceDirectAccess = false)
173 {
174 if (is_array($subject) || ($subject instanceof \ArrayAccess && !$forceDirectAccess)) {
175 $subject[$propertyName] = $propertyValue;
176 return true;
177 }
178 if (!is_object($subject)) {
179 throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368);
180 }
181 if (!is_string($propertyName)) {
182 throw new \InvalidArgumentException('Given property name is not of type string.', 1231178878);
183 }
184 $result = true;
185 if ($forceDirectAccess) {
186 if (property_exists($subject, $propertyName)) {
187 $propertyReflection = new PropertyReflection($subject, $propertyName);
188 $propertyReflection->setAccessible(true);
189 $propertyReflection->setValue($subject, $propertyValue);
190 } else {
191 $subject->{$propertyName} = $propertyValue;
192 }
193 return $result;
194 }
195 $setterMethodName = self::buildSetterMethodName($propertyName);
196 if (is_callable([$subject, $setterMethodName])) {
197 $subject->{$setterMethodName}($propertyValue);
198 } elseif (property_exists($subject, $propertyName)) {
199 $reflection = new PropertyReflection($subject, $propertyName);
200 if ($reflection->isPublic()) {
201 $subject->{$propertyName} = $propertyValue;
202 } else {
203 $result = false;
204 }
205 } else {
206 $result = false;
207 }
208 return $result;
209 }
210
211 /**
212 * Returns an array of properties which can be get with the getProperty()
213 * method.
214 * Includes the following properties:
215 * - which can be get through a public getter method.
216 * - public properties which can be directly get.
217 *
218 * @param object $object Object to receive property names for
219 *
220 * @throws \InvalidArgumentException
221 * @return array Array of all gettable property names
222 */
223 public static function getGettablePropertyNames($object)
224 {
225 if (!is_object($object)) {
226 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301369);
227 }
228 if ($object instanceof \stdClass) {
229 $declaredPropertyNames = array_keys((array)$object);
230 } else {
231 $declaredPropertyNames = array_keys(get_class_vars(get_class($object)));
232 }
233 foreach (get_class_methods($object) as $methodName) {
234 if (is_callable([$object, $methodName])) {
235 if (substr($methodName, 0, 2) === 'is') {
236 $declaredPropertyNames[] = lcfirst(substr($methodName, 2));
237 }
238 if (substr($methodName, 0, 3) === 'get') {
239 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
240 }
241 if (substr($methodName, 0, 3) === 'has') {
242 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
243 }
244 }
245 }
246 $propertyNames = array_unique($declaredPropertyNames);
247 sort($propertyNames);
248 return $propertyNames;
249 }
250
251 /**
252 * Returns an array of properties which can be set with the setProperty()
253 * method.
254 * Includes the following properties:
255 * - which can be set through a public setter method.
256 * - public properties which can be directly set.
257 *
258 * @param object $object Object to receive property names for
259 *
260 * @throws \InvalidArgumentException
261 * @return array Array of all settable property names
262 */
263 public static function getSettablePropertyNames($object)
264 {
265 if (!is_object($object)) {
266 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1264022994);
267 }
268 if ($object instanceof \stdClass) {
269 $declaredPropertyNames = array_keys((array)$object);
270 } else {
271 $declaredPropertyNames = array_keys(get_class_vars(get_class($object)));
272 }
273 foreach (get_class_methods($object) as $methodName) {
274 if (substr($methodName, 0, 3) === 'set' && is_callable([$object, $methodName])) {
275 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
276 }
277 }
278 $propertyNames = array_unique($declaredPropertyNames);
279 sort($propertyNames);
280 return $propertyNames;
281 }
282
283 /**
284 * Tells if the value of the specified property can be set by this Object Accessor.
285 *
286 * @param object $object Object containting the property
287 * @param string $propertyName Name of the property to check
288 *
289 * @throws \InvalidArgumentException
290 * @return bool
291 */
292 public static function isPropertySettable($object, $propertyName)
293 {
294 if (!is_object($object)) {
295 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828920);
296 }
297 if ($object instanceof \stdClass && array_search($propertyName, array_keys(get_object_vars($object))) !== false) {
298 return true;
299 } elseif (array_search($propertyName, array_keys(get_class_vars(get_class($object)))) !== false) {
300 return true;
301 }
302 return is_callable([$object, self::buildSetterMethodName($propertyName)]);
303 }
304
305 /**
306 * Tells if the value of the specified property can be retrieved by this Object Accessor.
307 *
308 * @param object $object Object containting the property
309 * @param string $propertyName Name of the property to check
310 *
311 * @throws \InvalidArgumentException
312 * @return bool
313 */
314 public static function isPropertyGettable($object, $propertyName)
315 {
316 if (!is_object($object)) {
317 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828921);
318 }
319 if ($object instanceof \ArrayAccess && isset($object[$propertyName])) {
320 return true;
321 } elseif ($object instanceof \stdClass && isset($object->$propertyName)) {
322 return true;
323 }
324 if (is_callable([$object, 'get' . ucfirst($propertyName)])) {
325 return true;
326 }
327 if (is_callable([$object, 'has' . ucfirst($propertyName)])) {
328 return true;
329 }
330 if (is_callable([$object, 'is' . ucfirst($propertyName)])) {
331 return true;
332 }
333 if (property_exists($object, $propertyName)) {
334 $propertyReflection = new PropertyReflection($object, $propertyName);
335 return $propertyReflection->isPublic();
336 }
337 return false;
338 }
339
340 /**
341 * Get all properties (names and their current values) of the current
342 * $object that are accessible through this class.
343 *
344 * @param object $object Object to get all properties from.
345 *
346 * @throws \InvalidArgumentException
347 * @return array Associative array of all properties.
348 * @todo What to do with ArrayAccess
349 */
350 public static function getGettableProperties($object)
351 {
352 if (!is_object($object)) {
353 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301370);
354 }
355 $properties = [];
356 foreach (self::getGettablePropertyNames($object) as $propertyName) {
357 $properties[$propertyName] = self::getPropertyInternal($object, $propertyName);
358 }
359 return $properties;
360 }
361
362 /**
363 * Build the setter method name for a given property by capitalizing the
364 * first letter of the property, and prepending it with "set".
365 *
366 * @param string $propertyName Name of the property
367 *
368 * @return string Name of the setter method name
369 */
370 public static function buildSetterMethodName($propertyName)
371 {
372 return 'set' . ucfirst($propertyName);
373 }
374 }