Skip to content

Instantly share code, notes, and snippets.

@marcguyer
Created October 18, 2018 21:20
Show Gist options
  • Save marcguyer/4735f8f52121d5e1a50dc6ea6d802efd to your computer and use it in GitHub Desktop.
Save marcguyer/4735f8f52121d5e1a50dc6ea6d802efd to your computer and use it in GitHub Desktop.
PSR-7 PSR-15 middleware to convert subdomain to base path
<?php
declare(strict_types=1);
namespace Api\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Helper\UrlHelper;
use Api\Exception;
class SubdomainToPathMiddleware implements MiddlewareInterface
{
/**
* @var string
*/
private $baseDomain;
/**
* @var UrlHelper
*/
private $urlHelper;
/**
* @param string $baseDomain
* @param UrlHelper $urlHelper
*/
public function __construct(string $baseDomain, UrlHelper $urlHelper)
{
$this->baseDomain = $baseDomain;
$this->urlHelper = $urlHelper;
}
/**
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
*
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$uri = $request->getUri();
$host = $uri->getHost();
// no subdomain or host is something less than baseDomain
if (strlen($host) <= strlen($this->baseDomain)) {
return $handler->handle($request);
}
// if request host does not end in baseDomain, dump
if (0 !== strpos(strrev($host), strrev($this->baseDomain))) {
return $handler->handle($request);
}
$subdomain = substr(
$host,
0,
strlen($host) - strlen($this->baseDomain)
);
// if $subdomain does not end in a dot, something's wrong
if ('.' !== substr($subdomain, -1)) {
throw Exception\RuntimeException(
"Subdomain '$subdomain' does not end with a dot"
);
}
$path = $uri->getPath();
$newBasePath = '/' . str_replace('.', '/', $subdomain);
// request already includes the base path
if (0 === strpos($path, $newBasePath)) {
return $handler->handle($request);
}
$newPath = $newBasePath . $path;
$request = $request->withUri($uri->withPath($newPath));
return $handler->handle($request);
}
}
@marcguyer
Copy link
Author

You'd then write your factory and pass in the UrlHelper and the base domain. Here's mine:

<?php

declare(strict_types=1);

namespace Api\Middleware;

use Psr\Container\ContainerInterface;
use Zend\Expressive\Helper\UrlHelper;

class SubdomainToPathMiddlewareFactory
{
    public function __invoke(ContainerInterface $container): SubdomainToPathMiddleware
    {
        $domain = $container->get('config')['general']['domain'];

        return new SubdomainToPathMiddleware(
            $domain,
            $container->get(UrlHelper::class)
        );
    }
}

... and load it into your pipeline near the beginning for (optionally) specific subdomains:

...
use function Zend\Stratigility\host;
use Api\Middleware\SubdomainToPathMiddleware;

/*
 * Setup middleware pipeline:
 */
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
    // The error handler should be the first (most outer) middleware to catch
    // all Exceptions.
    $app->pipe(ErrorHandler::class);

    // "rewrite" path to start with "/api" when the subdomain is "api"
    $app->pipe(
        host(
            'api.' . $container->get('config')['general']['domain'],
            $factory->lazy(SubdomainToPathMiddleware::class)
        )
    );
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment