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