Added support to define differnet not-found redirects per domain

This commit is contained in:
Alejandro Celaya
2021-07-21 09:28:21 +02:00
parent 2054784a4a
commit 4d48482d1e
14 changed files with 398 additions and 107 deletions

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Config;
interface NotFoundRedirectConfigInterface
{
public function invalidShortUrlRedirect(): ?string;
public function hasInvalidShortUrlRedirect(): bool;
public function regular404Redirect(): ?string;
public function hasRegular404Redirect(): bool;
public function baseUrlRedirect(): ?string;
public function hasBaseUrlRedirect(): bool;
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Config;
use Psr\Http\Message\ResponseInterface;
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
{
public function __construct(private RedirectResponseHelperInterface $redirectResponseHelper)
{
}
public function resolveRedirectResponse(
NotFoundType $notFoundType,
NotFoundRedirectConfigInterface $config
): ?ResponseInterface {
return match (true) {
$notFoundType->isBaseUrl() && $config->hasBaseUrlRedirect() =>
// @phpstan-ignore-next-line Create custom PHPStan rule
$this->redirectResponseHelper->buildRedirectResponse($config->baseUrlRedirect()),
$notFoundType->isRegularNotFound() && $config->hasRegular404Redirect() =>
// @phpstan-ignore-next-line Create custom PHPStan rule
$this->redirectResponseHelper->buildRedirectResponse($config->regular404Redirect()),
$notFoundType->isInvalidShortUrl() && $config->hasInvalidShortUrlRedirect() =>
// @phpstan-ignore-next-line Create custom PHPStan rule
$this->redirectResponseHelper->buildRedirectResponse($config->invalidShortUrlRedirect()),
default => null,
};
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Config;
use Psr\Http\Message\ResponseInterface;
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
interface NotFoundRedirectResolverInterface
{
public function resolveRedirectResponse(
NotFoundType $notFoundType,
NotFoundRedirectConfigInterface $config
): ?ResponseInterface;
}

View File

@@ -54,10 +54,15 @@ class DomainService implements DomainServiceInterface
return $domain;
}
public function getOrCreate(string $authority): Domain
public function findByAuthority(string $authority): ?Domain
{
$repo = $this->em->getRepository(Domain::class);
$domain = $repo->findOneBy(['authority' => $authority]) ?? new Domain($authority);
return $repo->findOneBy(['authority' => $authority]);
}
public function getOrCreate(string $authority): Domain
{
$domain = $this->findByAuthority($authority) ?? new Domain($authority);
$this->em->persist($domain);
$this->em->flush();

View File

@@ -22,4 +22,6 @@ interface DomainServiceInterface
public function getDomain(string $domainId): Domain;
public function getOrCreate(string $authority): Domain;
public function findByAuthority(string $authority): ?Domain;
}

View File

@@ -6,9 +6,14 @@ namespace Shlinkio\Shlink\Core\Entity;
use JsonSerializable;
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
use Shlinkio\Shlink\Core\Config\NotFoundRedirectConfigInterface;
class Domain extends AbstractEntity implements JsonSerializable
class Domain extends AbstractEntity implements JsonSerializable, NotFoundRedirectConfigInterface
{
private ?string $baseUrlRedirect = null;
private ?string $regular404Redirect = null;
private ?string $invalidShortUrlRedirect = null;
public function __construct(private string $authority)
{
}
@@ -22,4 +27,34 @@ class Domain extends AbstractEntity implements JsonSerializable
{
return $this->getAuthority();
}
public function invalidShortUrlRedirect(): ?string
{
return $this->invalidShortUrlRedirect;
}
public function hasInvalidShortUrlRedirect(): bool
{
return $this->invalidShortUrlRedirect !== null;
}
public function regular404Redirect(): ?string
{
return $this->regular404Redirect;
}
public function hasRegular404Redirect(): bool
{
return $this->regular404Redirect !== null;
}
public function baseUrlRedirect(): ?string
{
return $this->baseUrlRedirect;
}
public function hasBaseUrlRedirect(): bool
{
return $this->baseUrlRedirect !== null;
}
}

View File

@@ -8,15 +8,17 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Config\NotFoundRedirectResolverInterface;
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
use Shlinkio\Shlink\Core\Options;
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
class NotFoundRedirectHandler implements MiddlewareInterface
{
public function __construct(
private Options\NotFoundRedirectOptions $redirectOptions,
private RedirectResponseHelperInterface $redirectResponseHelper
private NotFoundRedirectResolverInterface $redirectResolver,
private DomainServiceInterface $domainService,
) {
}
@@ -24,26 +26,17 @@ class NotFoundRedirectHandler implements MiddlewareInterface
{
/** @var NotFoundType $notFoundType */
$notFoundType = $request->getAttribute(NotFoundType::class);
$authority = $request->getUri()->getAuthority();
$domainSpecificRedirect = $this->resolveDomainSpecificRedirect($authority, $notFoundType);
if ($notFoundType->isBaseUrl() && $this->redirectOptions->hasBaseUrlRedirect()) {
// @phpstan-ignore-next-line Create custom PHPStan rule
return $this->redirectResponseHelper->buildRedirectResponse($this->redirectOptions->getBaseUrlRedirect());
}
return $domainSpecificRedirect
?? $this->redirectResolver->resolveRedirectResponse($notFoundType, $this->redirectOptions)
?? $handler->handle($request);
}
if ($notFoundType->isRegularNotFound() && $this->redirectOptions->hasRegular404Redirect()) {
return $this->redirectResponseHelper->buildRedirectResponse(
// @phpstan-ignore-next-line Create custom PHPStan rule
$this->redirectOptions->getRegular404Redirect(),
);
}
if ($notFoundType->isInvalidShortUrl() && $this->redirectOptions->hasInvalidShortUrlRedirect()) {
return $this->redirectResponseHelper->buildRedirectResponse(
// @phpstan-ignore-next-line Create custom PHPStan rule
$this->redirectOptions->getInvalidShortUrlRedirect(),
);
}
return $handler->handle($request);
private function resolveDomainSpecificRedirect(string $authority, NotFoundType $notFoundType): ?ResponseInterface
{
$domain = $this->domainService->findByAuthority($authority);
return $domain === null ? null : $this->redirectResolver->resolveRedirectResponse($notFoundType, $domain);
}
}

View File

@@ -5,14 +5,15 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Options;
use Laminas\Stdlib\AbstractOptions;
use Shlinkio\Shlink\Core\Config\NotFoundRedirectConfigInterface;
class NotFoundRedirectOptions extends AbstractOptions
class NotFoundRedirectOptions extends AbstractOptions implements NotFoundRedirectConfigInterface
{
private ?string $invalidShortUrl = null;
private ?string $regular404 = null;
private ?string $baseUrl = null;
public function getInvalidShortUrlRedirect(): ?string
public function invalidShortUrlRedirect(): ?string
{
return $this->invalidShortUrl;
}
@@ -28,7 +29,7 @@ class NotFoundRedirectOptions extends AbstractOptions
return $this;
}
public function getRegular404Redirect(): ?string
public function regular404Redirect(): ?string
{
return $this->regular404;
}
@@ -44,7 +45,7 @@ class NotFoundRedirectOptions extends AbstractOptions
return $this;
}
public function getBaseUrlRedirect(): ?string
public function baseUrlRedirect(): ?string
{
return $this->baseUrl;
}