/vendor/league/fractal/src/Scope.php

https://gitlab.com/dae.nuli/toko · PHP · 423 lines · 177 code · 55 blank · 191 comment · 18 complexity · 392f27a3cf1640526d43d5091bbdacc8 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the League\Fractal package.
  4. *
  5. * (c) Phil Sturgeon <me@philsturgeon.uk>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. **/
  10. namespace League\Fractal;
  11. use InvalidArgumentException;
  12. use League\Fractal\Resource\Collection;
  13. use League\Fractal\Resource\Item;
  14. use League\Fractal\Resource\NullResource;
  15. use League\Fractal\Resource\ResourceInterface;
  16. use League\Fractal\Serializer\SerializerAbstract;
  17. /**
  18. * Scope
  19. *
  20. * The scope class acts as a tracker, relating a specific resource in a specific
  21. * context. For example, the same resource could be attached to multiple scopes.
  22. * There are root scopes, parent scopes and child scopes.
  23. */
  24. class Scope
  25. {
  26. /**
  27. * @var array
  28. */
  29. protected $availableIncludes = [];
  30. /**
  31. * @var string
  32. */
  33. protected $scopeIdentifier;
  34. /**
  35. * @var \League\Fractal\Manager
  36. */
  37. protected $manager;
  38. /**
  39. * @var ResourceInterface
  40. */
  41. protected $resource;
  42. /**
  43. * @var array
  44. */
  45. protected $parentScopes = [];
  46. /**
  47. * Create a new scope instance.
  48. *
  49. * @param Manager $manager
  50. * @param ResourceInterface $resource
  51. * @param string $scopeIdentifier
  52. *
  53. * @return void
  54. */
  55. public function __construct(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null)
  56. {
  57. $this->manager = $manager;
  58. $this->resource = $resource;
  59. $this->scopeIdentifier = $scopeIdentifier;
  60. }
  61. /**
  62. * Embed a scope as a child of the current scope.
  63. *
  64. * @internal
  65. *
  66. * @param string $scopeIdentifier
  67. * @param ResourceInterface $resource
  68. *
  69. * @return \League\Fractal\Scope
  70. */
  71. public function embedChildScope($scopeIdentifier, $resource)
  72. {
  73. return $this->manager->createData($resource, $scopeIdentifier, $this);
  74. }
  75. /**
  76. * Get the current identifier.
  77. *
  78. * @return string
  79. */
  80. public function getScopeIdentifier()
  81. {
  82. return $this->scopeIdentifier;
  83. }
  84. /**
  85. * Get the unique identifier for this scope.
  86. *
  87. * @param string $appendIdentifier
  88. *
  89. * @return string
  90. */
  91. public function getIdentifier($appendIdentifier = null)
  92. {
  93. $identifierParts = array_merge($this->parentScopes, [$this->scopeIdentifier, $appendIdentifier]);
  94. return implode('.', array_filter($identifierParts));
  95. }
  96. /**
  97. * Getter for parentScopes.
  98. *
  99. * @return mixed
  100. */
  101. public function getParentScopes()
  102. {
  103. return $this->parentScopes;
  104. }
  105. /**
  106. * Getter for resource.
  107. *
  108. * @return ResourceInterface
  109. */
  110. public function getResource()
  111. {
  112. return $this->resource;
  113. }
  114. /**
  115. * Getter for manager.
  116. *
  117. * @return \League\Fractal\Manager
  118. */
  119. public function getManager()
  120. {
  121. return $this->manager;
  122. }
  123. /**
  124. * Is Requested.
  125. *
  126. * Check if - in relation to the current scope - this specific segment is allowed.
  127. * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
  128. * scope is a then c is not allowed, even if it is there and potentially transformable.
  129. *
  130. * @internal
  131. *
  132. * @param string $checkScopeSegment
  133. *
  134. * @return bool Returns the new number of elements in the array.
  135. */
  136. public function isRequested($checkScopeSegment)
  137. {
  138. if ($this->parentScopes) {
  139. $scopeArray = array_slice($this->parentScopes, 1);
  140. array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
  141. } else {
  142. $scopeArray = [$checkScopeSegment];
  143. }
  144. $scopeString = implode('.', (array) $scopeArray);
  145. return in_array($scopeString, $this->manager->getRequestedIncludes());
  146. }
  147. /**
  148. * Is Excluded.
  149. *
  150. * Check if - in relation to the current scope - this specific segment should
  151. * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
  152. * then c will not be allowed in the transformation whether it appears in
  153. * the list of default or available, requested includes.
  154. *
  155. * @internal
  156. *
  157. * @param string $checkScopeSegment
  158. *
  159. * @return bool
  160. */
  161. public function isExcluded($checkScopeSegment)
  162. {
  163. if ($this->parentScopes) {
  164. $scopeArray = array_slice($this->parentScopes, 1);
  165. array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
  166. } else {
  167. $scopeArray = [$checkScopeSegment];
  168. }
  169. $scopeString = implode('.', (array) $scopeArray);
  170. return in_array($scopeString, $this->manager->getRequestedExcludes());
  171. }
  172. /**
  173. * Push Parent Scope.
  174. *
  175. * Push a scope identifier into parentScopes
  176. *
  177. * @internal
  178. *
  179. * @param string $identifierSegment
  180. *
  181. * @return int Returns the new number of elements in the array.
  182. */
  183. public function pushParentScope($identifierSegment)
  184. {
  185. return array_push($this->parentScopes, $identifierSegment);
  186. }
  187. /**
  188. * Set parent scopes.
  189. *
  190. * @internal
  191. *
  192. * @param string[] $parentScopes Value to set.
  193. *
  194. * @return $this
  195. */
  196. public function setParentScopes($parentScopes)
  197. {
  198. $this->parentScopes = $parentScopes;
  199. return $this;
  200. }
  201. /**
  202. * Convert the current data for this scope to an array.
  203. *
  204. * @return array
  205. */
  206. public function toArray()
  207. {
  208. list($rawData, $rawIncludedData) = $this->executeResourceTransformers();
  209. $serializer = $this->manager->getSerializer();
  210. $data = $this->serializeResource($serializer, $rawData);
  211. // If the serializer wants the includes to be side-loaded then we'll
  212. // serialize the included data and merge it with the data.
  213. if ($serializer->sideloadIncludes()) {
  214. $includedData = $serializer->includedData($this->resource, $rawIncludedData);
  215. // If the serializer wants to inject additional information
  216. // about the included resources, it can do so now.
  217. $data = $serializer->injectData($data, $rawIncludedData);
  218. if ($this->isRootScope()) {
  219. // If the serializer wants to have a final word about all
  220. // the objects that are sideloaded, it can do so now.
  221. $includedData = $serializer->filterIncludes(
  222. $includedData,
  223. $data
  224. );
  225. }
  226. $data = array_merge($data, $includedData);
  227. }
  228. if ($this->resource instanceof Collection) {
  229. if ($this->resource->hasCursor()) {
  230. $pagination = $serializer->cursor($this->resource->getCursor());
  231. } elseif ($this->resource->hasPaginator()) {
  232. $pagination = $serializer->paginator($this->resource->getPaginator());
  233. }
  234. if (! empty($pagination)) {
  235. $this->resource->setMetaValue(key($pagination), current($pagination));
  236. }
  237. }
  238. // Pull out all of OUR metadata and any custom meta data to merge with the main level data
  239. $meta = $serializer->meta($this->resource->getMeta());
  240. return array_merge($data, $meta);
  241. }
  242. /**
  243. * Convert the current data for this scope to JSON.
  244. *
  245. * @return string
  246. */
  247. public function toJson()
  248. {
  249. return json_encode($this->toArray());
  250. }
  251. /**
  252. * Execute the resources transformer and return the data and included data.
  253. *
  254. * @internal
  255. *
  256. * @return array
  257. */
  258. protected function executeResourceTransformers()
  259. {
  260. $transformer = $this->resource->getTransformer();
  261. $data = $this->resource->getData();
  262. $transformedData = $includedData = [];
  263. if ($this->resource instanceof Item) {
  264. list($transformedData, $includedData[]) = $this->fireTransformer($transformer, $data);
  265. } elseif ($this->resource instanceof Collection) {
  266. foreach ($data as $value) {
  267. list($transformedData[], $includedData[]) = $this->fireTransformer($transformer, $value);
  268. }
  269. } elseif ($this->resource instanceof NullResource) {
  270. $transformedData = null;
  271. $includedData = [];
  272. } else {
  273. throw new InvalidArgumentException(
  274. 'Argument $resource should be an instance of League\Fractal\Resource\Item'
  275. .' or League\Fractal\Resource\Collection'
  276. );
  277. }
  278. return [$transformedData, $includedData];
  279. }
  280. /**
  281. * Serialize a resource
  282. *
  283. * @internal
  284. *
  285. * @param SerializerAbstract $serializer
  286. * @param mixed $data
  287. *
  288. * @return array
  289. */
  290. protected function serializeResource(SerializerAbstract $serializer, $data)
  291. {
  292. $resourceKey = $this->resource->getResourceKey();
  293. if ($this->resource instanceof Collection) {
  294. return $serializer->collection($resourceKey, $data);
  295. }
  296. if ($this->resource instanceof Item) {
  297. return $serializer->item($resourceKey, $data);
  298. }
  299. return $serializer->null();
  300. }
  301. /**
  302. * Fire the main transformer.
  303. *
  304. * @internal
  305. *
  306. * @param TransformerAbstract|callable $transformer
  307. * @param mixed $data
  308. *
  309. * @return array
  310. */
  311. protected function fireTransformer($transformer, $data)
  312. {
  313. $includedData = [];
  314. if (is_callable($transformer)) {
  315. $transformedData = call_user_func($transformer, $data);
  316. } else {
  317. $transformer->setCurrentScope($this);
  318. $transformedData = $transformer->transform($data);
  319. }
  320. if ($this->transformerHasIncludes($transformer)) {
  321. $includedData = $this->fireIncludedTransformers($transformer, $data);
  322. $transformedData = $this->manager->getSerializer()->mergeIncludes($transformedData, $includedData);
  323. }
  324. return [$transformedData, $includedData];
  325. }
  326. /**
  327. * Fire the included transformers.
  328. *
  329. * @internal
  330. *
  331. * @param \League\Fractal\TransformerAbstract $transformer
  332. * @param mixed $data
  333. *
  334. * @return array
  335. */
  336. protected function fireIncludedTransformers($transformer, $data)
  337. {
  338. $this->availableIncludes = $transformer->getAvailableIncludes();
  339. return $transformer->processIncludedResources($this, $data) ?: [];
  340. }
  341. /**
  342. * Determine if a transformer has any available includes.
  343. *
  344. * @internal
  345. *
  346. * @param TransformerAbstract|callable $transformer
  347. *
  348. * @return bool
  349. */
  350. protected function transformerHasIncludes($transformer)
  351. {
  352. if (! $transformer instanceof TransformerAbstract) {
  353. return false;
  354. }
  355. $defaultIncludes = $transformer->getDefaultIncludes();
  356. $availableIncludes = $transformer->getAvailableIncludes();
  357. return ! empty($defaultIncludes) || ! empty($availableIncludes);
  358. }
  359. /**
  360. * Check, if this is the root scope.
  361. *
  362. * @return bool
  363. */
  364. protected function isRootScope()
  365. {
  366. return empty($this->parentScopes);
  367. }
  368. }