Template.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <?php
  2. /**
  3. * League.Uri (https://uri.thephpleague.com)
  4. *
  5. * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace League\Uri\UriTemplate;
  12. use BackedEnum;
  13. use Deprecated;
  14. use League\Uri\Exceptions\SyntaxError;
  15. use Stringable;
  16. use function array_filter;
  17. use function array_map;
  18. use function array_reduce;
  19. use function array_unique;
  20. use function preg_match_all;
  21. use function preg_replace;
  22. use function str_replace;
  23. use function strpbrk;
  24. use const PREG_SET_ORDER;
  25. /**
  26. * @internal The class exposes the internal representation of a Template and its usage
  27. */
  28. final class Template implements Stringable
  29. {
  30. /**
  31. * Expression regular expression pattern.
  32. */
  33. private const REGEXP_EXPRESSION_DETECTOR = '/(?<expression>\{[^}]*})/x';
  34. /** @var array<Expression> */
  35. private readonly array $expressions;
  36. /** @var array<string> */
  37. public readonly array $variableNames;
  38. private function __construct(public readonly string $value, Expression ...$expressions)
  39. {
  40. $this->expressions = $expressions;
  41. $this->variableNames = array_unique(
  42. array_merge(
  43. ...array_map(
  44. static fn (Expression $expression): array => $expression->variableNames,
  45. $expressions
  46. )
  47. )
  48. );
  49. }
  50. /**
  51. * @throws SyntaxError if the template contains invalid expressions
  52. * @throws SyntaxError if the template contains invalid variable specification
  53. */
  54. public static function new(BackedEnum|Stringable|string $template): self
  55. {
  56. if ($template instanceof BackedEnum) {
  57. $template = $template->value;
  58. }
  59. $template = (string) $template;
  60. /** @var string $remainder */
  61. $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template);
  62. false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.');
  63. preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER);
  64. return new self($template, ...array_values(
  65. array_reduce($founds, function (array $carry, array $found): array {
  66. if (!isset($carry[$found['expression']])) {
  67. $carry[$found['expression']] = Expression::new($found['expression']);
  68. }
  69. return $carry;
  70. }, [])
  71. ));
  72. }
  73. /**
  74. * @throws TemplateCanNotBeExpanded if the variables are invalid
  75. */
  76. public function expand(iterable $variables = []): string
  77. {
  78. if (!$variables instanceof VariableBag) {
  79. $variables = new VariableBag($variables);
  80. }
  81. return $this->expandAll($variables);
  82. }
  83. /**
  84. * @throws TemplateCanNotBeExpanded if the variables are invalid or missing
  85. */
  86. public function expandOrFail(iterable $variables = []): string
  87. {
  88. if (!$variables instanceof VariableBag) {
  89. $variables = new VariableBag($variables);
  90. }
  91. $missing = array_filter($this->variableNames, fn (string $name): bool => !isset($variables[$name]));
  92. if ([] !== $missing) {
  93. throw TemplateCanNotBeExpanded::dueToMissingVariables(...$missing);
  94. }
  95. return $this->expandAll($variables);
  96. }
  97. private function expandAll(VariableBag $variables): string
  98. {
  99. return array_reduce(
  100. $this->expressions,
  101. fn (string $uri, Expression $expr): string => str_replace($expr->value, $expr->expand($variables), $uri),
  102. $this->value
  103. );
  104. }
  105. public function __toString(): string
  106. {
  107. return $this->value;
  108. }
  109. /**
  110. * DEPRECATION WARNING! This method will be removed in the next major point release.
  111. *
  112. * @throws SyntaxError if the template contains invalid expressions
  113. * @throws SyntaxError if the template contains invalid variable specification
  114. * @deprecated Since version 7.0.0
  115. * @codeCoverageIgnore
  116. * @see Template::new()
  117. *
  118. * Create a new instance from a string.
  119. *
  120. */
  121. #[Deprecated(message:'use League\Uri\UriTemplate\Template::new() instead', since:'league/uri:7.0.0')]
  122. public static function createFromString(Stringable|string $template): self
  123. {
  124. return self::new($template);
  125. }
  126. }