1 <?php
2 
3 declare(strict_types=1);
4 
5 namespace JMS\Serializer\Metadata;
6 
7 use JMS\Serializer\Exception\InvalidMetadataException;
8 use Metadata\PropertyMetadata as BasePropertyMetadata;
9 
10 class 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