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