$urlPatterns Lista de patrones (substring o regex) */ public function __construct( private readonly array $urlPatterns = [] ) {} /** * {@inheritdoc} * * Contexto esperado: * - request_uri: string (URI del request) * - url: string (URL completa, opcional) */ public function matches(array $context): bool { if (!$this->hasValues()) { return false; } $requestUri = $context['request_uri'] ?? ''; $url = $context['url'] ?? ''; if ($requestUri === '' && $url === '') { return false; } foreach ($this->urlPatterns as $pattern) { if ($this->matchesPattern($pattern, $requestUri, $url)) { return true; } } return false; } /** * Evalua si un patron coincide con el request_uri o url */ private function matchesPattern(string $pattern, string $requestUri, string $url): bool { // Detectar si es regex (empieza con /) if ($this->isRegex($pattern)) { return $this->matchesRegex($pattern, $requestUri); } // Substring matching return $this->matchesSubstring($pattern, $requestUri, $url); } /** * Detecta si el patron es una expresion regular */ private function isRegex(string $pattern): bool { // Un patron regex debe empezar con / y terminar con / (posiblemente con flags) return preg_match('#^/.+/[gimsux]*$#', $pattern) === 1; } /** * Evalua coincidencia regex */ private function matchesRegex(string $pattern, string $subject): bool { // Suprimir warnings de regex invalidos $result = @preg_match($pattern, $subject); return $result === 1; } /** * Evalua coincidencia por substring o wildcard * * Soporta wildcards simples: * - `*sct*` coincide con URLs que contengan "sct" * - `*` se convierte a `.*` en regex * - Sin wildcards: busca substring literal */ private function matchesSubstring(string $pattern, string $requestUri, string $url): bool { // Detectar si tiene wildcards (*) if (str_contains($pattern, '*')) { return $this->matchesWildcard($pattern, $requestUri, $url); } // Substring literal if ($requestUri !== '' && str_contains($requestUri, $pattern)) { return true; } if ($url !== '' && str_contains($url, $pattern)) { return true; } return false; } /** * Evalua coincidencia con patron wildcard * * Convierte wildcards (*) a regex (.*) */ private function matchesWildcard(string $pattern, string $requestUri, string $url): bool { // Convertir wildcard a regex: // 1. Escapar caracteres especiales de regex (excepto *) // 2. Convertir * a .* $regexPattern = preg_quote($pattern, '#'); $regexPattern = str_replace('\\*', '.*', $regexPattern); $regexPattern = '#' . $regexPattern . '#i'; if ($requestUri !== '' && preg_match($regexPattern, $requestUri) === 1) { return true; } if ($url !== '' && preg_match($regexPattern, $url) === 1) { return true; } return false; } public function hasValues(): bool { return !empty($this->urlPatterns); } public function serialize(): string { return json_encode($this->urlPatterns, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } /** * @return array */ public function getUrlPatterns(): array { return $this->urlPatterns; } /** * Crea instancia desde JSON */ public static function fromJson(string $json): self { $decoded = json_decode($json, true); if (!is_array($decoded)) { return self::empty(); } // Filtrar valores vacios $patterns = array_filter($decoded, fn($p): bool => is_string($p) && $p !== ''); return new self(array_values($patterns)); } /** * Crea instancia vacia */ public static function empty(): self { return new self([]); } }