urlResolver = $urlResolver; $this->visitTracker = $visitTracker; $this->appOptions = $appOptions; $this->logger = $logger ?: new NullLogger(); } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $identifier = ShortUrlIdentifier::fromRedirectRequest($request); $query = $request->getQueryParams(); $disableTrackParam = $this->appOptions->getDisableTrackParam(); try { $shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier); if ($this->shouldTrackRequest($request, $query, $disableTrackParam)) { $this->visitTracker->track($shortUrl, Visitor::fromRequest($request)); } return $this->createSuccessResp($this->buildUrlToRedirectTo($shortUrl, $query, $disableTrackParam)); } catch (ShortUrlNotFoundException $e) { $this->logger->warning('An error occurred while tracking short code. {e}', ['e' => $e]); return $this->createErrorResp($request, $handler); } } private function buildUrlToRedirectTo(ShortUrl $shortUrl, array $currentQuery, ?string $disableTrackParam): string { $uri = new Uri($shortUrl->getLongUrl()); $hardcodedQuery = parse_query($uri->getQuery()); if ($disableTrackParam !== null) { unset($currentQuery[$disableTrackParam]); } $mergedQuery = array_merge($hardcodedQuery, $currentQuery); return (string) $uri->withQuery(build_query($mergedQuery)); } private function shouldTrackRequest(ServerRequestInterface $request, array $query, ?string $disableTrackParam): bool { $forwardedMethod = $request->getAttribute(ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE); if ($forwardedMethod === self::METHOD_HEAD) { return false; } return $disableTrackParam === null || ! array_key_exists($disableTrackParam, $query); } abstract protected function createSuccessResp(string $longUrl): ResponseInterface; abstract protected function createErrorResp( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface; }