JsonLocation.php 5.7 KB

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