JsonLocation.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <?php
  2. namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
  3. use GuzzleHttp\Command\Guzzle\Parameter;
  4. use GuzzleHttp\Command\Result;
  5. use GuzzleHttp\Command\ResultInterface;
  6. use Psr\Http\Message\ResponseInterface;
  7. /**
  8. * Extracts elements from a JSON document.
  9. */
  10. class JsonLocation extends AbstractLocation
  11. {
  12. /** @var array The JSON document being visited */
  13. private $json = [];
  14. /**
  15. * Set the name of the location
  16. *
  17. * @param string $locationName
  18. */
  19. public function __construct($locationName = 'json')
  20. {
  21. parent::__construct($locationName);
  22. }
  23. /**
  24. * @return \GuzzleHttp\Command\ResultInterface
  25. */
  26. public function before(
  27. ResultInterface $result,
  28. ResponseInterface $response,
  29. Parameter $model
  30. ) {
  31. $body = (string) $response->getBody();
  32. $body = $body ?: '{}';
  33. $this->json = \GuzzleHttp\json_decode($body, true);
  34. // relocate named arrays, so that they have the same structure as
  35. // arrays nested in objects and visit can work on them in the same way
  36. if ($model->getType() === 'array' && ($name = $model->getName())) {
  37. $this->json = [$name => $this->json];
  38. }
  39. return $result;
  40. }
  41. /**
  42. * @return ResultInterface
  43. */
  44. public function after(
  45. ResultInterface $result,
  46. ResponseInterface $response,
  47. Parameter $model
  48. ) {
  49. // Handle additional, undefined properties
  50. $additional = $model->getAdditionalProperties();
  51. if (!($additional instanceof Parameter)) {
  52. return $result;
  53. }
  54. // Use the model location as the default if one is not set on additional
  55. $addLocation = $additional->getLocation() ?: $model->getLocation();
  56. if ($addLocation == $this->locationName) {
  57. foreach ($this->json as $prop => $val) {
  58. if (!isset($result[$prop])) {
  59. // Only recurse if there is a type specified
  60. $result[$prop] = $additional->getType()
  61. ? $this->recurse($additional, $val)
  62. : $val;
  63. }
  64. }
  65. }
  66. $this->json = [];
  67. return $result;
  68. }
  69. /**
  70. * @return Result|ResultInterface
  71. */
  72. public function visit(
  73. ResultInterface $result,
  74. ResponseInterface $response,
  75. Parameter $param
  76. ) {
  77. $name = $param->getName();
  78. $key = $param->getWireName();
  79. // Check if the result should be treated as a list
  80. if ($param->getType() == 'array') {
  81. // Treat as javascript array
  82. if ($name) {
  83. // name provided, store it under a key in the array
  84. $subArray = isset($this->json[$key]) ? $this->json[$key] : null;
  85. $result[$name] = $this->recurse($param, $subArray);
  86. } else {
  87. // top-level `array` or an empty name
  88. $result = new Result(array_merge(
  89. $result->toArray(),
  90. $this->recurse($param, $this->json)
  91. ));
  92. }
  93. } elseif (isset($this->json[$key])) {
  94. $result[$name] = $this->recurse($param, $this->json[$key]);
  95. }
  96. return $result;
  97. }
  98. /**
  99. * Recursively process a parameter while applying filters
  100. *
  101. * @param Parameter $param API parameter being validated
  102. * @param mixed $value Value to process.
  103. *
  104. * @return mixed|null
  105. */
  106. private function recurse(Parameter $param, $value)
  107. {
  108. if (!is_array($value)) {
  109. return $param->filter($value);
  110. }
  111. $result = [];
  112. $type = $param->getType();
  113. if ($type == 'array') {
  114. $items = $param->getItems();
  115. foreach ($value as $val) {
  116. $result[] = $this->recurse($items, $val);
  117. }
  118. } elseif ($type == 'object' && !isset($value[0])) {
  119. // On the above line, we ensure that the array is associative and
  120. // not numerically indexed
  121. if ($properties = $param->getProperties()) {
  122. foreach ($properties as $property) {
  123. $key = $property->getWireName();
  124. if (array_key_exists($key, $value)) {
  125. $result[$property->getName()] = $this->recurse(
  126. $property,
  127. $value[$key]
  128. );
  129. // Remove from the value so that AP can later be handled
  130. unset($value[$key]);
  131. }
  132. }
  133. }
  134. // Only check additional properties if everything wasn't already
  135. // handled
  136. if ($value) {
  137. $additional = $param->getAdditionalProperties();
  138. if ($additional === null || $additional === true) {
  139. // Merge the JSON under the resulting array
  140. $result += $value;
  141. } elseif ($additional instanceof Parameter) {
  142. // Process all child elements according to the given schema
  143. foreach ($value as $prop => $val) {
  144. $result[$prop] = $this->recurse($additional, $val);
  145. }
  146. }
  147. }
  148. }
  149. return $param->filter($result);
  150. }
  151. }