/src/Mappers/Reflection/ReflectionMemberMapper.php

https://gitlab.com/1of0/php-serializer · PHP · 305 lines · 201 code · 46 blank · 58 comment · 48 complexity · 992aa9b873109014af6977a824432c99 MD5 · raw file

  1. <?php
  2. /**
  3. * Copyright (c) 2016 Bernardo van der Wal
  4. * MIT License
  5. *
  6. * Refer to the LICENSE file for the full copyright notice.
  7. */
  8. namespace OneOfZero\Json\Mappers\Reflection;
  9. use OneOfZero\Json\Enums\IncludeStrategy;
  10. use OneOfZero\Json\Helpers\Flags;
  11. use OneOfZero\Json\Helpers\ReflectionHelper;
  12. use OneOfZero\Json\Mappers\AbstractMapperChain;
  13. use OneOfZero\Json\Mappers\AbstractMemberMapper;
  14. use OneOfZero\Json\Mappers\SourceInterface;
  15. use OneOfZero\PhpDocReader\PhpDocReader;
  16. use ReflectionClass;
  17. use ReflectionMethod;
  18. use ReflectionParameter;
  19. use ReflectionProperty;
  20. use Reflector;
  21. /**
  22. * Base implementation of a mapper that maps the serialization metadata for a property or method.
  23. */
  24. class ReflectionMemberMapper extends AbstractMemberMapper
  25. {
  26. /**
  27. * @var PhpDocReader $docReader
  28. */
  29. protected $docReader;
  30. /**
  31. * @param SourceInterface $source
  32. * @param Reflector|ReflectionClass|ReflectionProperty|ReflectionMethod $target
  33. * @param AbstractMapperChain|null $chain
  34. */
  35. public function __construct(
  36. SourceInterface $source = null,
  37. Reflector $target = null,
  38. AbstractMapperChain $chain = null
  39. )
  40. {
  41. parent::__construct($source, $target, $chain);
  42. $this->docReader = new PhpDocReader(true);
  43. }
  44. /**
  45. * {@inheritdoc}
  46. */
  47. public function getSerializedName()
  48. {
  49. // By default assume the target member's name
  50. $name = $this->getTarget()->name;
  51. if ($this->isClassMethod())
  52. {
  53. // For methods with a prefix, trim off prefix, and make the first character is lower case
  54. $name = lcfirst(substr($this->getTarget()->name, strlen($this->getMethodPrefix())));
  55. }
  56. return $name;
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function getType()
  62. {
  63. // Try determining from phpdoc (@var, @return and @param)
  64. if ($this->isClassProperty())
  65. {
  66. $type = $this->findFirstExistingType($this->docReader->getPropertyClasses($this->getTarget()));
  67. if ($type !== null)
  68. {
  69. return $type;
  70. }
  71. }
  72. if ($this->getChain()->getTop()->isGetter())
  73. {
  74. $type = $this->findFirstExistingType($this->docReader->getMethodReturnClasses($this->getTarget()));
  75. if ($type !== null)
  76. {
  77. return $type;
  78. }
  79. }
  80. if ($this->getChain()->getTop()->isSetter())
  81. {
  82. /** @var ReflectionParameter $setter */
  83. list($setter) = $this->getTarget()->getParameters();
  84. $type = $this->findFirstExistingType($this->docReader->getParameterClasses($setter));
  85. if ($type !== null)
  86. {
  87. return $type;
  88. }
  89. }
  90. // Try determining from type reflection type declarations
  91. if ($this->getChain()->getTop()->isGetter())
  92. {
  93. if (version_compare(PHP_VERSION, '7.0.0', '>='))
  94. {
  95. // If PHP 7, try using the return type declaration
  96. if ($this->getTarget()->getReturnType() !== null)
  97. {
  98. return $this->getTarget()->getReturnType();
  99. }
  100. }
  101. }
  102. if ($this->getChain()->getTop()->isSetter())
  103. {
  104. /** @var ReflectionParameter $setter */
  105. list($setter) = $this->getTarget()->getParameters();
  106. if (version_compare(PHP_VERSION, '7.0.0', '>='))
  107. {
  108. // If PHP 7, try using the type declaration from the first method parameter
  109. if ($setter->hasType())
  110. {
  111. return strval($setter->getType());
  112. }
  113. }
  114. // Try PHP 5 compatible type hint from the first method parameter
  115. if ($setter->getClass() !== null)
  116. {
  117. return $setter->getClass()->name;
  118. }
  119. }
  120. return parent::getType();
  121. }
  122. /**
  123. * For an array returns the first item that exists as a class
  124. *
  125. * @param string[] $types
  126. * @return string|null
  127. */
  128. private function findFirstExistingType($types)
  129. {
  130. foreach ($types as $type)
  131. {
  132. if (class_exists($type))
  133. {
  134. return $type;
  135. }
  136. }
  137. return null;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. *
  142. * @codeCoverageIgnore Defers to base
  143. */
  144. public function isArray()
  145. {
  146. return parent::isArray();
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function isGetter()
  152. {
  153. if (!$this->isClassMethod() || !preg_match(self::GETTER_REGEX, $this->getTarget()->name))
  154. {
  155. return false;
  156. }
  157. if (!ReflectionHelper::hasGetterSignature($this->getTarget()))
  158. {
  159. return false;
  160. }
  161. $strategy = $this->getChain()->getConfiguration()->defaultMemberInclusionStrategy;
  162. if ($this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::PUBLIC_GETTERS))
  163. {
  164. return true;
  165. }
  166. if (!$this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::NON_PUBLIC_GETTERS))
  167. {
  168. return true;
  169. }
  170. return parent::isGetter();
  171. }
  172. /**
  173. * {@inheritdoc}
  174. */
  175. public function isSetter()
  176. {
  177. if (!$this->isClassMethod() || !preg_match(self::SETTER_REGEX, $this->getTarget()->name))
  178. {
  179. return false;
  180. }
  181. if (!ReflectionHelper::hasSetterSignature($this->getTarget()))
  182. {
  183. return false;
  184. }
  185. $strategy = $this->getChain()->getConfiguration()->defaultMemberInclusionStrategy;
  186. if ($this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::PUBLIC_SETTERS))
  187. {
  188. return true;
  189. }
  190. if (!$this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::NON_PUBLIC_SETTERS))
  191. {
  192. return true;
  193. }
  194. return parent::isSetter();
  195. }
  196. /**
  197. * {@inheritdoc}
  198. */
  199. public function isSerializable()
  200. {
  201. if ($this->isClassMethod() && !$this->getChain()->getTop()->isGetter())
  202. {
  203. return false;
  204. }
  205. return parent::isSerializable();
  206. }
  207. /**
  208. * {@inheritdoc}
  209. */
  210. public function isDeserializable()
  211. {
  212. if ($this->isClassMethod() && !$this->getChain()->getTop()->isSetter())
  213. {
  214. return false;
  215. }
  216. return parent::isDeserializable();
  217. }
  218. /**
  219. * {@inheritdoc}
  220. */
  221. public function isIncluded()
  222. {
  223. $strategy = $this->getChain()->getConfiguration()->defaultMemberInclusionStrategy;
  224. if ($this->isClassProperty())
  225. {
  226. if ($this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::PUBLIC_PROPERTIES))
  227. {
  228. return true;
  229. }
  230. if (!$this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::NON_PUBLIC_PROPERTIES))
  231. {
  232. return true;
  233. }
  234. }
  235. if ($this->getChain()->getTop()->isGetter())
  236. {
  237. if ($this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::PUBLIC_GETTERS))
  238. {
  239. return true;
  240. }
  241. if (!$this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::NON_PUBLIC_GETTERS))
  242. {
  243. return true;
  244. }
  245. }
  246. if ($this->getChain()->getTop()->isSetter())
  247. {
  248. if ($this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::PUBLIC_SETTERS))
  249. {
  250. return true;
  251. }
  252. if (!$this->getTarget()->isPublic() && Flags::has($strategy, IncludeStrategy::NON_PUBLIC_SETTERS))
  253. {
  254. return true;
  255. }
  256. }
  257. return parent::isIncluded();
  258. }
  259. }