/vendor/guzzle/parser/Guzzle/Parser/UriTemplate/UriTemplate.php

https://bitbucket.org/nbravo777/repo_ratchet · PHP · 243 lines · 164 code · 34 blank · 45 comment · 48 complexity · 0cc7bfe8c1cfa6c9a41a3e4dbb73edae MD5 · raw file

  1. <?php
  2. namespace Guzzle\Parser\UriTemplate;
  3. /**
  4. * Expands URI templates using an array of variables
  5. *
  6. * @link http://tools.ietf.org/html/draft-gregorio-uritemplate-08
  7. */
  8. class UriTemplate implements UriTemplateInterface
  9. {
  10. /** @var string URI template */
  11. private $template;
  12. /** @var array Variables to use in the template expansion */
  13. private $variables;
  14. /** @var string Regex used to parse expressions */
  15. private static $regex = '/\{([^\}]+)\}/';
  16. /** @var array Hash for quick operator lookups */
  17. private static $operatorHash = array(
  18. '+' => true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true
  19. );
  20. /** @var array Delimiters */
  21. private static $delims = array(
  22. ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='
  23. );
  24. /** @var array Percent encoded delimiters */
  25. private static $delimsPct = array(
  26. '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
  27. '%3B', '%3D'
  28. );
  29. public function expand($template, array $variables)
  30. {
  31. $this->template = $template;
  32. $this->variables = $variables;
  33. // Check to ensure that the preg_* function is needed
  34. if (false === strpos($this->template, '{')) {
  35. return $this->template;
  36. }
  37. return preg_replace_callback(self::$regex, array($this, 'expandMatch'), $this->template);
  38. }
  39. /**
  40. * Parse an expression into parts
  41. *
  42. * @param string $expression Expression to parse
  43. *
  44. * @return array Returns an associative array of parts
  45. */
  46. private function parseExpression($expression)
  47. {
  48. // Check for URI operators
  49. $operator = '';
  50. if (isset(self::$operatorHash[$expression[0]])) {
  51. $operator = $expression[0];
  52. $expression = substr($expression, 1);
  53. }
  54. $values = explode(',', $expression);
  55. foreach ($values as &$value) {
  56. $value = trim($value);
  57. $varspec = array();
  58. $substrPos = strpos($value, ':');
  59. if ($substrPos) {
  60. $varspec['value'] = substr($value, 0, $substrPos);
  61. $varspec['modifier'] = ':';
  62. $varspec['position'] = (int) substr($value, $substrPos + 1);
  63. } elseif (substr($value, -1) == '*') {
  64. $varspec['modifier'] = '*';
  65. $varspec['value'] = substr($value, 0, -1);
  66. } else {
  67. $varspec['value'] = (string) $value;
  68. $varspec['modifier'] = '';
  69. }
  70. $value = $varspec;
  71. }
  72. return array(
  73. 'operator' => $operator,
  74. 'values' => $values
  75. );
  76. }
  77. /**
  78. * Process an expansion
  79. *
  80. * @param array $matches Matches met in the preg_replace_callback
  81. *
  82. * @return string Returns the replacement string
  83. */
  84. private function expandMatch(array $matches)
  85. {
  86. static $rfc1738to3986 = array(
  87. '+' => '%20',
  88. '%7e' => '~'
  89. );
  90. $parsed = self::parseExpression($matches[1]);
  91. $replacements = array();
  92. $prefix = $parsed['operator'];
  93. $joiner = $parsed['operator'];
  94. $useQueryString = false;
  95. if ($parsed['operator'] == '?') {
  96. $joiner = '&';
  97. $useQueryString = true;
  98. } elseif ($parsed['operator'] == '&') {
  99. $useQueryString = true;
  100. } elseif ($parsed['operator'] == '#') {
  101. $joiner = ',';
  102. } elseif ($parsed['operator'] == ';') {
  103. $useQueryString = true;
  104. } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') {
  105. $joiner = ',';
  106. $prefix = '';
  107. }
  108. foreach ($parsed['values'] as $value) {
  109. if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) {
  110. continue;
  111. }
  112. $variable = $this->variables[$value['value']];
  113. $actuallyUseQueryString = $useQueryString;
  114. $expanded = '';
  115. if (is_array($variable)) {
  116. $isAssoc = $this->isAssoc($variable);
  117. $kvp = array();
  118. foreach ($variable as $key => $var) {
  119. if ($isAssoc) {
  120. $key = rawurlencode($key);
  121. $isNestedArray = is_array($var);
  122. } else {
  123. $isNestedArray = false;
  124. }
  125. if (!$isNestedArray) {
  126. $var = rawurlencode($var);
  127. if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
  128. $var = $this->decodeReserved($var);
  129. }
  130. }
  131. if ($value['modifier'] == '*') {
  132. if ($isAssoc) {
  133. if ($isNestedArray) {
  134. // Nested arrays must allow for deeply nested structures
  135. $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986);
  136. } else {
  137. $var = $key . '=' . $var;
  138. }
  139. } elseif ($key > 0 && $actuallyUseQueryString) {
  140. $var = $value['value'] . '=' . $var;
  141. }
  142. }
  143. $kvp[$key] = $var;
  144. }
  145. if (empty($variable)) {
  146. $actuallyUseQueryString = false;
  147. } elseif ($value['modifier'] == '*') {
  148. $expanded = implode($joiner, $kvp);
  149. if ($isAssoc) {
  150. // Don't prepend the value name when using the explode modifier with an associative array
  151. $actuallyUseQueryString = false;
  152. }
  153. } else {
  154. if ($isAssoc) {
  155. // When an associative array is encountered and the explode modifier is not set, then the
  156. // result must be a comma separated list of keys followed by their respective values.
  157. foreach ($kvp as $k => &$v) {
  158. $v = $k . ',' . $v;
  159. }
  160. }
  161. $expanded = implode(',', $kvp);
  162. }
  163. } else {
  164. if ($value['modifier'] == ':') {
  165. $variable = substr($variable, 0, $value['position']);
  166. }
  167. $expanded = rawurlencode($variable);
  168. if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
  169. $expanded = $this->decodeReserved($expanded);
  170. }
  171. }
  172. if ($actuallyUseQueryString) {
  173. if (!$expanded && $joiner != '&') {
  174. $expanded = $value['value'];
  175. } else {
  176. $expanded = $value['value'] . '=' . $expanded;
  177. }
  178. }
  179. $replacements[] = $expanded;
  180. }
  181. $ret = implode($joiner, $replacements);
  182. if ($ret && $prefix) {
  183. return $prefix . $ret;
  184. }
  185. return $ret;
  186. }
  187. /**
  188. * Determines if an array is associative
  189. *
  190. * @param array $array Array to check
  191. *
  192. * @return bool
  193. */
  194. private function isAssoc(array $array)
  195. {
  196. return (bool) count(array_filter(array_keys($array), 'is_string'));
  197. }
  198. /**
  199. * Removes percent encoding on reserved characters (used with + and # modifiers)
  200. *
  201. * @param string $string String to fix
  202. *
  203. * @return string
  204. */
  205. private function decodeReserved($string)
  206. {
  207. return str_replace(self::$delimsPct, self::$delims, $string);
  208. }
  209. }