‪TYPO3CMS  10.4
ObjectAccess.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Symfony\Component\PropertyAccess\PropertyAccess;
21 use Symfony\Component\PropertyAccess\PropertyAccessor;
22 use Symfony\Component\PropertyAccess\PropertyPath;
27 
38 {
42  private static ‪$propertyAccessor;
43 
63  public static function ‪getProperty($subject, string $propertyName, bool $forceDirectAccess = false)
64  {
65  if (!is_object($subject) && !is_array($subject)) {
66  throw new \InvalidArgumentException(
67  '$subject must be an object or array, ' . gettype($subject) . ' given.',
68  1237301367
69  );
70  }
71 
72  return ‪self::getPropertyInternal($subject, $propertyName, $forceDirectAccess);
73  }
74 
90  public static function ‪getPropertyInternal($subject, string $propertyName, bool $forceDirectAccess = false)
91  {
92  if ($forceDirectAccess === true) {
93  trigger_error('Argument $forceDirectAccess will be removed in TYPO3 11.0', E_USER_DEPRECATED);
94  }
95 
96  if (!$forceDirectAccess && ($subject instanceof \SplObjectStorage || $subject instanceof ‪ObjectStorage)) {
97  $subject = iterator_to_array(clone $subject, false);
98  }
99 
100  $propertyPath = new PropertyPath($propertyName);
101 
102  if ($subject instanceof \ArrayAccess) {
103  $accessor = ‪self::createAccessor();
104 
105  // Check if $subject is an instance of \ArrayAccess and therefore maybe has actual accessible properties.
106  if ($accessor->isReadable($subject, $propertyPath)) {
107  return $accessor->getValue($subject, $propertyPath);
108  }
109 
110  // Use array style property path for instances of \ArrayAccess
111  // https://symfony.com/doc/current/components/property_access.html#reading-from-arrays
112 
113  $propertyPath = ‪self::convertToArrayPropertyPath($propertyPath);
114  }
115 
116  if (is_object($subject)) {
117  return ‪self::getObjectPropertyValue($subject, $propertyPath, $forceDirectAccess);
118  }
119 
120  if (is_array($subject)) {
121  return ‪self::getArrayIndexValue($subject, self::convertToArrayPropertyPath($propertyPath));
122  }
123 
124  return null;
125  }
126 
140  public static function ‪getPropertyPath($subject, string $propertyPath)
141  {
142  try {
143  foreach (new PropertyPath($propertyPath) as $pathSegment) {
144  $subject = ‪self::getPropertyInternal($subject, $pathSegment);
145  }
146  } catch (‪PropertyNotAccessibleException $error) {
147  return null;
148  }
149  return $subject;
150  }
151 
171  public static function ‪setProperty(&$subject, string $propertyName, $propertyValue, bool $forceDirectAccess = false): bool
172  {
173  if ($forceDirectAccess === true) {
174  trigger_error('Argument $forceDirectAccess will be removed in TYPO3 11.0', E_USER_DEPRECATED);
175  }
176 
177  if (is_array($subject) || ($subject instanceof \ArrayAccess && !$forceDirectAccess)) {
178  $subject[$propertyName] = $propertyValue;
179  return true;
180  }
181  if (!is_object($subject)) {
182  throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368);
183  }
184 
185  $accessor = ‪self::createAccessor();
186  if ($accessor->isWritable($subject, $propertyName)) {
187  $accessor->setValue($subject, $propertyName, $propertyValue);
188  return true;
189  }
190 
191  if ($forceDirectAccess) {
192  if (property_exists($subject, $propertyName)) {
193  $propertyReflection = new \ReflectionProperty($subject, $propertyName);
194  $propertyReflection->setAccessible(true);
195  $propertyReflection->setValue($subject, $propertyValue);
196  } else {
197  $subject->{$propertyName} = $propertyValue;
198  }
199 
200  return true;
201  }
202 
203  return false;
204  }
205 
218  public static function getGettablePropertyNames(object $object): array
219  {
220  if ($object instanceof \stdClass) {
221  $properties = array_keys((array)$object);
222  sort($properties);
223  return $properties;
224  }
225 
226  $classSchema = GeneralUtility::makeInstance(ReflectionService::class)
227  ->getClassSchema($object);
228 
229  $accessor = self::createAccessor();
230  $propertyNames = array_keys($classSchema->getProperties());
231  $accessiblePropertyNames = array_filter($propertyNames, function ($propertyName) use ($accessor, $object) {
232  return $accessor->isReadable($object, $propertyName);
233  });
234 
235  foreach ($classSchema->getMethods() as $methodName => $methodDefinition) {
236  if (!$methodDefinition->isPublic()) {
237  continue;
238  }
239 
240  foreach ($methodDefinition->getParameters() as $methodParam) {
241  if (!$methodParam->isOptional()) {
242  continue 2;
243  }
244  }
245 
246  if (‪StringUtility::beginsWith($methodName, 'get')) {
247  $accessiblePropertyNames[] = lcfirst(substr($methodName, 3));
248  continue;
249  }
250 
251  if (‪StringUtility::beginsWith($methodName, 'has')) {
252  $accessiblePropertyNames[] = lcfirst(substr($methodName, 3));
253  continue;
254  }
255 
256  if (‪StringUtility::beginsWith($methodName, 'is')) {
257  $accessiblePropertyNames[] = lcfirst(substr($methodName, 2));
258  }
259  }
260 
261  $accessiblePropertyNames = array_unique($accessiblePropertyNames);
262  sort($accessiblePropertyNames);
263  return $accessiblePropertyNames;
264  }
265 
278  public static function getSettablePropertyNames(object $object): array
279  {
280  $accessor = self::createAccessor();
281 
282  if ($object instanceof \stdClass || $object instanceof \ArrayAccess) {
283  $propertyNames = array_keys((array)$object);
284  } else {
285  $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($object);
286 
287  $propertyNames = array_filter(array_keys($classSchema->getProperties()), ‪function ($methodName) use ($accessor, $object) {
288  return $accessor->isWritable($object, $methodName);
289  });
290 
291  $setters = array_filter(array_keys($classSchema->getMethods()), ‪function ($methodName) use ($object) {
292  return ‪StringUtility::beginsWith($methodName, 'set') && is_callable([$object, $methodName]);
293  });
294 
295  foreach ($setters as $setter) {
296  $propertyNames[] = lcfirst(substr($setter, 3));
297  }
298  }
299 
300  $propertyNames = array_unique($propertyNames);
301  sort($propertyNames);
302  return $propertyNames;
303  }
304 
314  public static function isPropertySettable(object $object, $propertyName): bool
315  {
316  if ($object instanceof \stdClass && array_key_exists($propertyName, get_object_vars($object))) {
317  return true;
318  }
319  if (array_key_exists($propertyName, get_class_vars(get_class($object)))) {
320  return true;
321  }
322  return is_callable([$object, 'set' . ucfirst($propertyName)]);
323  }
324 
334  public static function isPropertyGettable($object, $propertyName): bool
335  {
336  if (($object instanceof \ArrayAccess) && !$object->offsetExists($propertyName)) {
337  return false;
338  }
339 
340  if (is_array($object) || $object instanceof \ArrayAccess) {
341  $propertyName = self::wrap($propertyName);
342  }
343 
344  return self::createAccessor()->isReadable($object, $propertyName);
345  }
346 
357  public static function getGettableProperties(object $object): array
358  {
359  $properties = [];
360  foreach (self::getGettablePropertyNames($object) as $propertyName) {
361  $properties[$propertyName] = self::getPropertyInternal($object, $propertyName);
362  }
363  return $properties;
364  }
365 
375  public static function buildSetterMethodName($propertyName): string
376  {
377  trigger_error(__METHOD__ . ' will be removed in TYPO3 11.0', E_USER_DEPRECATED);
378 
379  return 'set' . ucfirst($propertyName);
380  }
381 
385  private static function createAccessor(): PropertyAccessor
386  {
387  if (static::$propertyAccessor === null) {
388  static::$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
389  ->getPropertyAccessor();
390  }
391 
392  return static::$propertyAccessor;
393  }
394 
403  private static function getObjectPropertyValue(object $subject, PropertyPath $propertyPath, bool $forceDirectAccess)
404  {
405  $accessor = self::createAccessor();
406 
407  if ($accessor->isReadable($subject, $propertyPath)) {
408  return $accessor->getValue($subject, $propertyPath);
409  }
410 
411  $propertyName = (string)$propertyPath;
412 
413  if (!$forceDirectAccess) {
414  throw new PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1476109666);
415  }
416 
417  if (!property_exists($subject, $propertyName)) {
418  throw new PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
419  }
420 
421  $propertyReflection = new \ReflectionProperty($subject, $propertyName);
422  $propertyReflection->setAccessible(true);
423  return $propertyReflection->getValue($subject);
424  }
425 
431  private static function getArrayIndexValue(array $subject, PropertyPath $propertyPath)
432  {
433  return self::createAccessor()->getValue($subject, $propertyPath);
434  }
435 
440  private static function convertToArrayPropertyPath(PropertyPath $propertyPath): PropertyPath
441  {
442  $segments = array_map(function ($segment) {
443  return static::wrap($segment);
444  }, $propertyPath->getElements());
445 
446  return new PropertyPath(implode('.', $segments));
447  }
448 
453  private static function wrap(string $segment): string
454  {
455  return '[' . $segment . ']';
456  }
457 }
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\convertToArrayPropertyPath
‪static PropertyPath convertToArrayPropertyPath(PropertyPath $propertyPath)
Definition: ObjectAccess.php:439
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getObjectPropertyValue
‪static mixed getObjectPropertyValue(object $subject, PropertyPath $propertyPath, bool $forceDirectAccess)
Definition: ObjectAccess.php:402
‪TYPO3\CMS\Extbase\function
‪return function(ContainerConfigurator $containerConfigurator, ContainerBuilder $container)
Definition: Services.php:10
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\setProperty
‪static bool setProperty(&$subject, string $propertyName, $propertyValue, bool $forceDirectAccess=false)
Definition: ObjectAccess.php:170
‪TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException
Definition: PropertyNotAccessibleException.php:26
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\createAccessor
‪static PropertyAccessor createAccessor()
Definition: ObjectAccess.php:384
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\wrap
‪static string wrap(string $segment)
Definition: ObjectAccess.php:452
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getGettablePropertyNames
‪static array getGettablePropertyNames(object $object)
Definition: ObjectAccess.php:217
‪TYPO3\CMS\Core\Utility\StringUtility\beginsWith
‪static bool beginsWith($haystack, $needle)
Definition: StringUtility.php:32
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess
Definition: ObjectAccess.php:38
‪TYPO3\CMS\Extbase\Persistence\ObjectStorage
Definition: ObjectStorage.php:28
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\$propertyAccessor
‪static PropertyAccessor $propertyAccessor
Definition: ObjectAccess.php:41
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\isPropertyGettable
‪static bool isPropertyGettable($object, $propertyName)
Definition: ObjectAccess.php:333
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\buildSetterMethodName
‪static string buildSetterMethodName($propertyName)
Definition: ObjectAccess.php:374
‪TYPO3\CMS\Extbase\Reflection
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getGettableProperties
‪static array getGettableProperties(object $object)
Definition: ObjectAccess.php:356
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getSettablePropertyNames
‪static array getSettablePropertyNames(object $object)
Definition: ObjectAccess.php:277
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getArrayIndexValue
‪static mixed getArrayIndexValue(array $subject, PropertyPath $propertyPath)
Definition: ObjectAccess.php:430
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getPropertyPath
‪static mixed getPropertyPath($subject, string $propertyPath)
Definition: ObjectAccess.php:139
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getPropertyInternal
‪static mixed getPropertyInternal($subject, string $propertyName, bool $forceDirectAccess=false)
Definition: ObjectAccess.php:89
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\isPropertySettable
‪static bool isPropertySettable(object $object, $propertyName)
Definition: ObjectAccess.php:313
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:22
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getProperty
‪static mixed getProperty($subject, string $propertyName, bool $forceDirectAccess=false)
Definition: ObjectAccess.php:62