UriTemplate.php

Namespace

Guzzle\Parser\UriTemplate

File

drupal/core/vendor/guzzle/parser/Guzzle/Parser/UriTemplate/UriTemplate.php
View source
<?php

namespace Guzzle\Parser\UriTemplate;


/**
 * Expands URI templates using an array of variables
 *
 * @link http://tools.ietf.org/html/draft-gregorio-uritemplate-08
 */
class UriTemplate implements UriTemplateInterface {

  /**
   * @var string URI template
   */
  private $template;

  /**
   * @var array Variables to use in the template expansion
   */
  private $variables;

  /**
   * @var string Regex used to parse expressions
   */
  private static $regex = '/\\{([^\\}]+)\\}/';

  /**
   * @var array Hash for quick operator lookups
   */
  private static $operatorHash = array(
    '+' => true,
    '#' => true,
    '.' => true,
    '/' => true,
    ';' => true,
    '?' => true,
    '&' => true,
  );

  /**
   * @var array Delimiters
   */
  private static $delims = array(
    ':',
    '/',
    '?',
    '#',
    '[',
    ']',
    '@',
    '!',
    '$',
    '&',
    '\'',
    '(',
    ')',
    '*',
    '+',
    ',',
    ';',
    '=',
  );

  /**
   * @var array Percent encoded delimiters
   */
  private static $delimsPct = array(
    '%3A',
    '%2F',
    '%3F',
    '%23',
    '%5B',
    '%5D',
    '%40',
    '%21',
    '%24',
    '%26',
    '%27',
    '%28',
    '%29',
    '%2A',
    '%2B',
    '%2C',
    '%3B',
    '%3D',
  );

  /**
   * {@inheritdoc}
   */
  public function expand($template, array $variables) {
    $this->template = $template;
    $this->variables = $variables;

    // Check to ensure that the preg_* function is needed
    if (false === strpos($this->template, '{')) {
      return $this->template;
    }
    return preg_replace_callback(self::$regex, array(
      $this,
      'expandMatch',
    ), $this->template);
  }

  /**
   * Parse an expression into parts
   *
   * @param string $expression Expression to parse
   *
   * @return array Returns an associative array of parts
   */
  private function parseExpression($expression) {

    // Check for URI operators
    $operator = '';
    if (isset(self::$operatorHash[$expression[0]])) {
      $operator = $expression[0];
      $expression = substr($expression, 1);
    }
    $values = explode(',', $expression);
    foreach ($values as &$value) {
      $value = trim($value);
      $varspec = array();
      $substrPos = strpos($value, ':');
      if ($substrPos) {
        $varspec['value'] = substr($value, 0, $substrPos);
        $varspec['modifier'] = ':';
        $varspec['position'] = (int) substr($value, $substrPos + 1);
      }
      elseif (substr($value, -1) == '*') {
        $varspec['modifier'] = '*';
        $varspec['value'] = substr($value, 0, -1);
      }
      else {
        $varspec['value'] = (string) $value;
        $varspec['modifier'] = '';
      }
      $value = $varspec;
    }
    return array(
      'operator' => $operator,
      'values' => $values,
    );
  }

  /**
   * Process an expansion
   *
   * @param array $matches Matches met in the preg_replace_callback
   *
   * @return string Returns the replacement string
   */
  private function expandMatch(array $matches) {
    static $rfc1738to3986 = array(
      '+' => '%20',
      '%7e' => '~',
    );
    $parsed = self::parseExpression($matches[1]);
    $replacements = array();
    $prefix = $parsed['operator'];
    $joiner = $parsed['operator'];
    $useQueryString = false;
    if ($parsed['operator'] == '?') {
      $joiner = '&';
      $useQueryString = true;
    }
    elseif ($parsed['operator'] == '&') {
      $useQueryString = true;
    }
    elseif ($parsed['operator'] == '#') {
      $joiner = ',';
    }
    elseif ($parsed['operator'] == ';') {
      $useQueryString = true;
    }
    elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') {
      $joiner = ',';
      $prefix = '';
    }
    foreach ($parsed['values'] as $value) {
      if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) {
        continue;
      }
      $variable = $this->variables[$value['value']];
      $actuallyUseQueryString = $useQueryString;
      $expanded = '';
      if (is_array($variable)) {
        $isAssoc = $this
          ->isAssoc($variable);
        $kvp = array();
        foreach ($variable as $key => $var) {
          if ($isAssoc) {
            $key = rawurlencode($key);
            $isNestedArray = is_array($var);
          }
          else {
            $isNestedArray = false;
          }
          if (!$isNestedArray) {
            $var = rawurlencode($var);
            if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
              $var = $this
                ->decodeReserved($var);
            }
          }
          if ($value['modifier'] == '*') {
            if ($isAssoc) {
              if ($isNestedArray) {

                // Nested arrays must allow for deeply nested structures
                $var = strtr(http_build_query(array(
                  $key => $var,
                )), $rfc1738to3986);
              }
              else {
                $var = $key . '=' . $var;
              }
            }
            elseif ($key > 0 && $actuallyUseQueryString) {
              $var = $value['value'] . '=' . $var;
            }
          }
          $kvp[$key] = $var;
        }
        if (empty($variable)) {
          $actuallyUseQueryString = false;
        }
        else {
          if ($value['modifier'] == '*') {
            $expanded = implode($joiner, $kvp);
            if ($isAssoc) {

              // Don't prepend the value name when using the explode modifier with an associative array
              $actuallyUseQueryString = false;
            }
          }
          else {
            if ($isAssoc) {

              // When an associative array is encountered and the explode modifier is not set, then the
              // result must be a comma separated list of keys followed by their respective values.
              foreach ($kvp as $k => &$v) {
                $v = $k . ',' . $v;
              }
            }
            $expanded = implode(',', $kvp);
          }
        }
      }
      else {
        if ($value['modifier'] == ':') {
          $variable = substr($variable, 0, $value['position']);
        }
        $expanded = rawurlencode($variable);
        if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
          $expanded = $this
            ->decodeReserved($expanded);
        }
      }
      if ($actuallyUseQueryString) {
        if (!$expanded && $joiner != '&') {
          $expanded = $value['value'];
        }
        else {
          $expanded = $value['value'] . '=' . $expanded;
        }
      }
      $replacements[] = $expanded;
    }
    $ret = implode($joiner, $replacements);
    if ($ret && $prefix) {
      return $prefix . $ret;
    }
    return $ret;
  }

  /**
   * Determines if an array is associative
   *
   * @param array $array Array to check
   *
   * @return bool
   */
  private function isAssoc(array $array) {
    return (bool) count(array_filter(array_keys($array), 'is_string'));
  }

  /**
   * Removes percent encoding on reserved characters (used with + and # modifiers)
   *
   * @param string $string String to fix
   *
   * @return string
   */
  private function decodeReserved($string) {
    return str_replace(self::$delimsPct, self::$delims, $string);
  }

}

Classes

Namesort descending Description
UriTemplate Expands URI templates using an array of variables