[FEATURE] Support multiple UID in PageRepository::getMenu()
[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\Core\Utility\MathUtility;
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 Exception\PropertyNotAccessibleException
51 * @throws \InvalidArgumentException in case $subject was not an object or $propertyName was not a string
52 * @return mixed Value of the property
53 */
54 static public function getProperty($subject, $propertyName, $forceDirectAccess = FALSE) {
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 $propertyExists = FALSE;
62 $propertyValue = self::getPropertyInternal($subject, $propertyName, $forceDirectAccess, $propertyExists);
63 if ($propertyExists === TRUE) {
64 return $propertyValue;
65 }
66 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject was not accessible.', 1263391473);
67 }
68
69 /**
70 * Gets a property of a given object or array.
71 * This is an internal method that does only limited type checking for performance reasons.
72 * 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.
73 *
74 * @see getProperty()
75 *
76 * @param mixed $subject Object or array to get the property from
77 * @param string $propertyName name of the property to retrieve
78 * @param bool $forceDirectAccess directly access property using reflection(!)
79 * @param bool &$propertyExists (by reference) will be set to TRUE if the specified property exists and is gettable
80 *
81 * @throws Exception\PropertyNotAccessibleException
82 * @return mixed Value of the property
83 * @internal
84 */
85 static public function getPropertyInternal($subject, $propertyName, $forceDirectAccess, &$propertyExists) {
86 if ($subject === NULL || is_scalar($subject)) {
87 return NULL;
88 }
89 $propertyExists = TRUE;
90 if (is_array($subject)) {
91 if (array_key_exists($propertyName, $subject)) {
92 return $subject[$propertyName];
93 }
94 $propertyExists = FALSE;
95 return NULL;
96 }
97 if ($forceDirectAccess === TRUE) {
98 if (property_exists(get_class($subject), $propertyName)) {
99 $propertyReflection = new PropertyReflection(get_class($subject), $propertyName);
100 return $propertyReflection->getValue($subject);
101 } elseif (property_exists($subject, $propertyName)) {
102 return $subject->{$propertyName};
103 } else {
104 throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
105 }
106 }
107 if ($subject instanceof \SplObjectStorage || $subject instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
108 if (MathUtility::canBeInterpretedAsInteger($propertyName)) {
109 $index = 0;
110 foreach ($subject as $value) {
111 if ($index === (int)$propertyName) {
112 return $value;
113 }
114 $index++;
115 }
116 $propertyExists = FALSE;
117 return NULL;
118 }
119 } elseif ($subject instanceof \ArrayAccess && isset($subject[$propertyName])) {
120 return $subject[$propertyName];
121 }
122 $getterMethodName = 'get' . ucfirst($propertyName);
123 if (is_callable(array($subject, $getterMethodName))) {
124 return $subject->{$getterMethodName}();
125 }
126 $getterMethodName = 'is' . ucfirst($propertyName);
127 if (is_callable(array($subject, $getterMethodName))) {
128 return $subject->{$getterMethodName}();
129 }
130 if (is_object($subject) && array_key_exists($propertyName, get_object_vars($subject))) {
131 return $subject->{$propertyName};
132 }
133 $propertyExists = FALSE;
134 return NULL;
135 }
136
137 /**
138 * Gets a property path from a given object or array.
139 *
140 * If propertyPath is "bla.blubb", then we first call getProperty($object, 'bla'),
141 * and on the resulting object we call getProperty(..., 'blubb')
142 *
143 * For arrays the keys are checked likewise.
144 *
145 * @param mixed $subject Object or array to get the property path from
146 * @param string $propertyPath
147 *
148 * @return mixed Value of the property
149 */
150 static public function getPropertyPath($subject, $propertyPath) {
151 $propertyPathSegments = explode('.', $propertyPath);
152 foreach ($propertyPathSegments as $pathSegment) {
153 $propertyExists = FALSE;
154 $subject = self::getPropertyInternal($subject, $pathSegment, FALSE, $propertyExists);
155 if (!$propertyExists || $subject === NULL) {
156 return $subject;
157 }
158 }
159 return $subject;
160 }
161
162 /**
163 * Set a property for a given object.
164 * Tries to set the property the following ways:
165 * - if target is an array, set value
166 * - if super cow powers should be used, set value through reflection
167 * - if public setter method exists, call it.
168 * - if public property exists, set it directly.
169 * - if the target object is an instance of ArrayAccess, it sets the property
170 * on it without checking if it existed.
171 * - else, return FALSE
172 *
173 * @param mixed &$subject The target object or array
174 * @param string $propertyName Name of the property to set
175 * @param mixed $propertyValue Value of the property
176 * @param bool $forceDirectAccess directly access property using reflection(!)
177 *
178 * @throws \InvalidArgumentException in case $object was not an object or $propertyName was not a string
179 * @return bool TRUE if the property could be set, FALSE otherwise
180 */
181 static public function setProperty(&$subject, $propertyName, $propertyValue, $forceDirectAccess = FALSE) {
182 if (is_array($subject)) {
183 $subject[$propertyName] = $propertyValue;
184 return TRUE;
185 }
186 if (!is_object($subject)) {
187 throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368);
188 }
189 if (!is_string($propertyName)) {
190 throw new \InvalidArgumentException('Given property name is not of type string.', 1231178878);
191 }
192 if ($forceDirectAccess === TRUE) {
193 if (property_exists(get_class($subject), $propertyName)) {
194 $propertyReflection = new PropertyReflection(get_class($subject), $propertyName);
195 $propertyReflection->setAccessible(TRUE);
196 $propertyReflection->setValue($subject, $propertyValue);
197 } else {
198 $subject->{$propertyName} = $propertyValue;
199 }
200 } elseif (is_callable(array($subject, $setterMethodName = self::buildSetterMethodName($propertyName)))) {
201 $subject->{$setterMethodName}($propertyValue);
202 } elseif ($subject instanceof \ArrayAccess) {
203 $subject[$propertyName] = $propertyValue;
204 } elseif (array_key_exists($propertyName, get_object_vars($subject))) {
205 $subject->{$propertyName} = $propertyValue;
206 } else {
207 return FALSE;
208 }
209 return TRUE;
210 }
211
212 /**
213 * Returns an array of properties which can be get with the getProperty()
214 * method.
215 * Includes the following properties:
216 * - which can be get through a public getter method.
217 * - public properties which can be directly get.
218 *
219 * @param object $object Object to receive property names for
220 *
221 * @throws \InvalidArgumentException
222 * @return array Array of all gettable property names
223 */
224 static public function getGettablePropertyNames($object) {
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(get_object_vars($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(array($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 }
242 }
243 $propertyNames = array_unique($declaredPropertyNames);
244 sort($propertyNames);
245 return $propertyNames;
246 }
247
248 /**
249 * Returns an array of properties which can be set with the setProperty()
250 * method.
251 * Includes the following properties:
252 * - which can be set through a public setter method.
253 * - public properties which can be directly set.
254 *
255 * @param object $object Object to receive property names for
256 *
257 * @throws \InvalidArgumentException
258 * @return array Array of all settable property names
259 */
260 static public function getSettablePropertyNames($object) {
261 if (!is_object($object)) {
262 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1264022994);
263 }
264 if ($object instanceof \stdClass) {
265 $declaredPropertyNames = array_keys(get_object_vars($object));
266 } else {
267 $declaredPropertyNames = array_keys(get_class_vars(get_class($object)));
268 }
269 foreach (get_class_methods($object) as $methodName) {
270 if (substr($methodName, 0, 3) === 'set' && is_callable(array($object, $methodName))) {
271 $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
272 }
273 }
274 $propertyNames = array_unique($declaredPropertyNames);
275 sort($propertyNames);
276 return $propertyNames;
277 }
278
279 /**
280 * Tells if the value of the specified property can be set by this Object Accessor.
281 *
282 * @param object $object Object containting the property
283 * @param string $propertyName Name of the property to check
284 *
285 * @throws \InvalidArgumentException
286 * @return bool
287 */
288 static public function isPropertySettable($object, $propertyName) {
289 if (!is_object($object)) {
290 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828920);
291 }
292 if ($object instanceof \stdClass && array_search($propertyName, array_keys(get_object_vars($object))) !== FALSE) {
293 return TRUE;
294 } elseif (array_search($propertyName, array_keys(get_class_vars(get_class($object)))) !== FALSE) {
295 return TRUE;
296 }
297 return is_callable(array($object, self::buildSetterMethodName($propertyName)));
298 }
299
300 /**
301 * Tells if the value of the specified property can be retrieved by this Object Accessor.
302 *
303 * @param object $object Object containting the property
304 * @param string $propertyName Name of the property to check
305 *
306 * @throws \InvalidArgumentException
307 * @return bool
308 */
309 static public function isPropertyGettable($object, $propertyName) {
310 if (!is_object($object)) {
311 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828921);
312 }
313 if ($object instanceof \ArrayAccess && isset($object[$propertyName]) === TRUE) {
314 return TRUE;
315 } elseif ($object instanceof \stdClass && array_search($propertyName, array_keys(get_object_vars($object))) !== FALSE) {
316 return TRUE;
317 } elseif ($object instanceof \ArrayAccess && isset($object[$propertyName]) === TRUE) {
318 return TRUE;
319 }
320 if (is_callable(array($object, 'get' . ucfirst($propertyName)))) {
321 return TRUE;
322 }
323 if (is_callable(array($object, 'is' . ucfirst($propertyName)))) {
324 return TRUE;
325 }
326 return array_search($propertyName, array_keys(get_class_vars(get_class($object)))) !== FALSE;
327 }
328
329 /**
330 * Get all properties (names and their current values) of the current
331 * $object that are accessible through this class.
332 *
333 * @param object $object Object to get all properties from.
334 *
335 * @throws \InvalidArgumentException
336 * @return array Associative array of all properties.
337 * @todo What to do with ArrayAccess
338 */
339 static public function getGettableProperties($object) {
340 if (!is_object($object)) {
341 throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301370);
342 }
343 $properties = array();
344 foreach (self::getGettablePropertyNames($object) as $propertyName) {
345 $propertyExists = FALSE;
346 $propertyValue = self::getPropertyInternal($object, $propertyName, FALSE, $propertyExists);
347 if ($propertyExists === TRUE) {
348 $properties[$propertyName] = $propertyValue;
349 }
350 }
351 return $properties;
352 }
353
354 /**
355 * Build the setter method name for a given property by capitalizing the
356 * first letter of the property, and prepending it with "set".
357 *
358 * @param string $propertyName Name of the property
359 *
360 * @return string Name of the setter method name
361 */
362 static public function buildSetterMethodName($propertyName) {
363 return 'set' . ucfirst($propertyName);
364 }
365
366 }