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