1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\Accessor; 6 7use JMS\Serializer\DeserializationContext; 8use JMS\Serializer\Exception\ExpressionLanguageRequiredException; 9use JMS\Serializer\Exception\LogicException; 10use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface; 11use JMS\Serializer\Expression\Expression; 12use JMS\Serializer\Expression\ExpressionEvaluatorInterface; 13use JMS\Serializer\Metadata\ExpressionPropertyMetadata; 14use JMS\Serializer\Metadata\PropertyMetadata; 15use JMS\Serializer\Metadata\StaticPropertyMetadata; 16use JMS\Serializer\SerializationContext; 17 18/** 19 * @author Asmir Mustafic <goetas@gmail.com> 20 */ 21final class DefaultAccessorStrategy implements AccessorStrategyInterface 22{ 23 /** 24 * @var callable[] 25 */ 26 private $readAccessors = []; 27 28 /** 29 * @var callable[] 30 */ 31 private $writeAccessors = []; 32 33 /** 34 * @var \ReflectionProperty[] 35 */ 36 private $propertyReflectionCache = []; 37 38 /** 39 * @var ExpressionEvaluatorInterface 40 */ 41 private $evaluator; 42 43 public function __construct(?ExpressionEvaluatorInterface $evaluator = null) 44 { 45 $this->evaluator = $evaluator; 46 } 47 48 49 /** 50 * {@inheritdoc} 51 */ 52 public function getValue(object $object, PropertyMetadata $metadata, SerializationContext $context) 53 { 54 if ($metadata instanceof StaticPropertyMetadata) { 55 return $metadata->getValue(null); 56 } 57 58 if ($metadata instanceof ExpressionPropertyMetadata) { 59 if (null === $this->evaluator) { 60 throw new ExpressionLanguageRequiredException(sprintf('The property %s on %s requires the expression accessor strategy to be enabled.', $metadata->name, $metadata->class)); 61 } 62 63 $variables = ['object' => $object, 'context' => $context, 'property_metadata' => $metadata]; 64 65 if (($metadata->expression instanceof Expression) && ($this->evaluator instanceof CompilableExpressionEvaluatorInterface)) { 66 return $this->evaluator->evaluateParsed($metadata->expression, $variables); 67 } 68 return $this->evaluator->evaluate($metadata->expression, $variables); 69 } 70 71 if (null === $metadata->getter) { 72 if (!isset($this->readAccessors[$metadata->class])) { 73 if (true === $metadata->forceReflectionAccess) { 74 $this->readAccessors[$metadata->class] = function ($o, $name) use ($metadata) { 75 $ref = $this->propertyReflectionCache[$metadata->class][$name] ?? null; 76 if (null === $ref) { 77 $ref = new \ReflectionProperty($metadata->class, $name); 78 $ref->setAccessible(true); 79 $this->propertyReflectionCache[$metadata->class][$name] = $ref; 80 } 81 82 return $ref->getValue($o); 83 }; 84 } else { 85 $this->readAccessors[$metadata->class] = \Closure::bind(static function ($o, $name) { 86 return $o->$name; 87 }, null, $metadata->class); 88 } 89 } 90 91 return $this->readAccessors[$metadata->class]($object, $metadata->name); 92 } 93 94 return $object->{$metadata->getter}(); 95 } 96 97 /** 98 * {@inheritdoc} 99 */ 100 public function setValue(object $object, $value, PropertyMetadata $metadata, DeserializationContext $context): void 101 { 102 if (true === $metadata->readOnly) { 103 throw new LogicException(sprintf('%s on %s is read only.', $metadata->name, $metadata->class)); 104 } 105 106 if (null === $metadata->setter) { 107 if (!isset($this->writeAccessors[$metadata->class])) { 108 if (true === $metadata->forceReflectionAccess) { 109 $this->writeAccessors[$metadata->class] = function ($o, $name, $value) use ($metadata): void { 110 $ref = $this->propertyReflectionCache[$metadata->class][$name] ?? null; 111 if (null === $ref) { 112 $ref = new \ReflectionProperty($metadata->class, $name); 113 $ref->setAccessible(true); 114 $this->propertyReflectionCache[$metadata->class][$name] = $ref; 115 } 116 117 $ref->setValue($o, $value); 118 }; 119 } else { 120 $this->writeAccessors[$metadata->class] = \Closure::bind(static function ($o, $name, $value): void { 121 $o->$name = $value; 122 }, null, $metadata->class); 123 } 124 } 125 126 $this->writeAccessors[$metadata->class]($object, $metadata->name, $value); 127 return; 128 } 129 130 $object->{$metadata->setter}($value); 131 } 132} 133