1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Metadata;
6
7use JMS\Serializer\Exception\InvalidMetadataException;
8use Metadata\PropertyMetadata as BasePropertyMetadata;
9
10class PropertyMetadata extends BasePropertyMetadata
11{
12    public const ACCESS_TYPE_PROPERTY = 'property';
13    public const ACCESS_TYPE_PUBLIC_METHOD = 'public_method';
14
15    /**
16     * @var string
17     */
18    public $sinceVersion;
19    /**
20     * @var string
21     */
22    public $untilVersion;
23    /**
24     * @var string[]
25     */
26    public $groups;
27    /**
28     * @var string
29     */
30    public $serializedName;
31    /**
32     * @var array
33     */
34    public $type;
35
36    /**
37     * @var bool
38     */
39    public $xmlCollection = false;
40
41    /**
42     * @var bool
43     */
44    public $xmlCollectionInline = false;
45
46    /**
47     * @var bool
48     */
49    public $xmlCollectionSkipWhenEmpty = true;
50
51    /**
52     * @var string
53     */
54    public $xmlEntryName;
55
56    /**
57     * @var string
58     */
59    public $xmlEntryNamespace;
60
61    /**
62     * @var string
63     */
64    public $xmlKeyAttribute;
65
66    /**
67     * @var bool
68     */
69    public $xmlAttribute = false;
70
71    /**
72     * @var bool
73     */
74    public $xmlValue = false;
75
76    /**
77     * @var string
78     */
79    public $xmlNamespace;
80
81    /**
82     * @var bool
83     */
84    public $xmlKeyValuePairs = false;
85
86    /**
87     * @var bool
88     */
89    public $xmlElementCData = true;
90
91    /**
92     * @var string
93     */
94    public $getter;
95
96    /**
97     * @var string
98     */
99    public $setter;
100
101    /**
102     * @var bool
103     */
104    public $inline = false;
105
106    /**
107     * @var bool
108     */
109    public $skipWhenEmpty = false;
110
111    /**
112     * @var bool
113     */
114    public $readOnly = false;
115
116    /**
117     * @var bool
118     */
119    public $xmlAttributeMap = false;
120
121    /**
122     * @var int|null
123     */
124    public $maxDepth = null;
125
126    /**
127     * @var string
128     */
129    public $excludeIf = null;
130
131    /**
132     * @internal
133     *
134     * @var bool
135     */
136    public $forceReflectionAccess = false;
137
138    public function __construct(string $class, string $name)
139    {
140        parent::__construct($class, $name);
141
142        try {
143            $class = $this->getReflection()->getDeclaringClass();
144            $this->forceReflectionAccess = $class->isInternal() || $class->getProperty($name)->isStatic();
145        } catch (\ReflectionException $e) {
146        }
147    }
148
149    private function getReflection(): \ReflectionProperty
150    {
151        return new \ReflectionProperty($this->class, $this->name);
152    }
153
154    public function setAccessor(string $type, ?string $getter = null, ?string $setter = null): void
155    {
156        if (self::ACCESS_TYPE_PUBLIC_METHOD === $type) {
157            $class = $this->getReflection()->getDeclaringClass();
158
159            if (empty($getter)) {
160                if ($class->hasMethod('get' . $this->name) && $class->getMethod('get' . $this->name)->isPublic()) {
161                    $getter = 'get' . $this->name;
162                } elseif ($class->hasMethod('is' . $this->name) && $class->getMethod('is' . $this->name)->isPublic()) {
163                    $getter = 'is' . $this->name;
164                } elseif ($class->hasMethod('has' . $this->name) && $class->getMethod('has' . $this->name)->isPublic()) {
165                    $getter = 'has' . $this->name;
166                } else {
167                    throw new InvalidMetadataException(sprintf('There is neither a public %s method, nor a public %s method, nor a public %s method in class %s. Please specify which public method should be used for retrieving the value of the property %s.', 'get' . ucfirst($this->name), 'is' . ucfirst($this->name), 'has' . ucfirst($this->name), $this->class, $this->name));
168                }
169            }
170
171            if (empty($setter) && !$this->readOnly) {
172                if ($class->hasMethod('set' . $this->name) && $class->getMethod('set' . $this->name)->isPublic()) {
173                    $setter = 'set' . $this->name;
174                } else {
175                    throw new InvalidMetadataException(sprintf('There is no public %s method in class %s. Please specify which public method should be used for setting the value of the property %s.', 'set' . ucfirst($this->name), $this->class, $this->name));
176                }
177            }
178        }
179
180        $this->getter = $getter;
181        $this->setter = $setter;
182    }
183
184    public function setType(array $type): void
185    {
186        $this->type = $type;
187    }
188
189    public static function isCollectionList(?array $type = null): bool
190    {
191        return is_array($type)
192            && 'array' === $type['name']
193            && isset($type['params'][0])
194            && !isset($type['params'][1]);
195    }
196
197    public static function isCollectionMap(?array $type = null): bool
198    {
199        return is_array($type)
200            && 'array' === $type['name']
201            && isset($type['params'][0])
202            && isset($type['params'][1]);
203    }
204
205    /**
206     * @return string
207     *
208     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
209     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint
210     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation
211     */
212    public function serialize()
213    {
214        return serialize([
215            $this->sinceVersion,
216            $this->untilVersion,
217            $this->groups,
218            $this->serializedName,
219            $this->type,
220            $this->xmlCollection,
221            $this->xmlCollectionInline,
222            $this->xmlEntryName,
223            $this->xmlKeyAttribute,
224            $this->xmlAttribute,
225            $this->xmlValue,
226            $this->xmlNamespace,
227            $this->xmlKeyValuePairs,
228            $this->xmlElementCData,
229            $this->getter,
230            $this->setter,
231            $this->inline,
232            $this->readOnly,
233            $this->xmlAttributeMap,
234            $this->maxDepth,
235            parent::serialize(),
236            'xmlEntryNamespace' => $this->xmlEntryNamespace,
237            'xmlCollectionSkipWhenEmpty' => $this->xmlCollectionSkipWhenEmpty,
238            'excludeIf' => $this->excludeIf,
239            'skipWhenEmpty' => $this->skipWhenEmpty,
240            'forceReflectionAccess' => $this->forceReflectionAccess,
241        ]);
242    }
243
244    /**
245     * @param string $str
246     *
247     * @return void
248     *
249     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
250     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint
251     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation
252     */
253    public function unserialize($str)
254    {
255        $parentStr = $this->unserializeProperties($str);
256        parent::unserialize($parentStr);
257    }
258
259    protected function unserializeProperties(string $str): string
260    {
261        $unserialized = unserialize($str);
262        [
263            $this->sinceVersion,
264            $this->untilVersion,
265            $this->groups,
266            $this->serializedName,
267            $this->type,
268            $this->xmlCollection,
269            $this->xmlCollectionInline,
270            $this->xmlEntryName,
271            $this->xmlKeyAttribute,
272            $this->xmlAttribute,
273            $this->xmlValue,
274            $this->xmlNamespace,
275            $this->xmlKeyValuePairs,
276            $this->xmlElementCData,
277            $this->getter,
278            $this->setter,
279            $this->inline,
280            $this->readOnly,
281            $this->xmlAttributeMap,
282            $this->maxDepth,
283            $parentStr,
284        ] = $unserialized;
285
286        if (isset($unserialized['xmlEntryNamespace'])) {
287            $this->xmlEntryNamespace = $unserialized['xmlEntryNamespace'];
288        }
289        if (isset($unserialized['xmlCollectionSkipWhenEmpty'])) {
290            $this->xmlCollectionSkipWhenEmpty = $unserialized['xmlCollectionSkipWhenEmpty'];
291        }
292        if (isset($unserialized['excludeIf'])) {
293            $this->excludeIf = $unserialized['excludeIf'];
294        }
295        if (isset($unserialized['skipWhenEmpty'])) {
296            $this->skipWhenEmpty = $unserialized['skipWhenEmpty'];
297        }
298        if (isset($unserialized['forceReflectionAccess'])) {
299            $this->forceReflectionAccess = $unserialized['forceReflectionAccess'];
300        }
301
302        return $parentStr;
303    }
304}
305