Skip to content

Instantly share code, notes, and snippets.

@Vercoutere
Last active November 15, 2018 09:34
Show Gist options
  • Save Vercoutere/c002cfb0040ddb04bf3d to your computer and use it in GitHub Desktop.
Save Vercoutere/c002cfb0040ddb04bf3d to your computer and use it in GitHub Desktop.
Laravel 5 middleware class that sets a context in a multi-tenant app + updated version for easier testing with example testcase.
<?php namespace Kilo\Http\Middleware;
use Closure;
use Route;
use App;
use Kilo\Models\TenantSpecific\Tenant;
class TenantMiddleware {
protected $domain;
protected $tld;
protected $subdomain;
protected $tenant;
protected $full_domain;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$domain = Route::input('domain');
$tld = Route::input('tld');
$subdomain = Route::input('subdomain');
$full_domain = $domain . '.' . $tld;
/**
* Get the Tenant by either domain or subdomain.
* If no Tenant found redirect to app home.
*/
if(isset($domain) && $full_domain != env('APP_DOMAIN')) {
$tenant = Tenant::getByDomain($full_domain);
} elseif (isset($subdomain)) {
$tenant = Tenant::getBySubdomain($subdomain);
} else {
$tenant = NULL;
}
return $this->setContext($tenant) ? $next($request) : redirect()->route('app-home');
}
/**
* Sets the context
* @param Kilo\Models\TenantSpecific\Tenant $tenant
*/
public function setContext($tenant) {
if(!isset($tenant)) {
return false;
}
$context = App::make('Context');
$context->set($tenant);
return true;
}
}
<?php namespace Kilo\Http\Middleware;
/**
* Updated version for easier testability.
* Cheers @adamwathan for helping me wrap my head around this.
*/
use Closure;
use Illuminate\Routing\Redirector as Redirect;
use Kilo\Models\TenantSpecific\Tenant;
use Kilo\Models\TenantSpecific\TenantContext as Context;
class TenantMiddleware {
protected $tenant;
protected $context;
protected $redirect;
public function __construct(Tenant $tenant, Context $context, Redirect $redirect)
{
$this->tenant = $tenant;
$this->context = $context;
$this->redirect = $redirect;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$domain = $request->route('domain');
$tld = $request->route('tld');
$subdomain = $request->route('subdomain');
$full_domain = $domain . '.' . $tld;
$current_tenant = NULL;
// Get the tenant by domain.
if(isset($domain) && $full_domain != env('APP_DOMAIN')) {
$current_tenant = $this->tenant->getByDomain($full_domain);
// Get the tenant by subdomain.
} elseif (isset($subdomain)) {
$current_tenant = $this->tenant->getBySubdomain($subdomain);
}
// Set the context if a tenant is found, else redirect to app home.
return $this->setContext($current_tenant) ? $next($request) : $this->redirect->route('app-home');
}
/**
* Set the context.
* @param Kilo\Models\TenantSpecific\Tenant $current_tenant
* @return Boolean
*/
public function setContext($current_tenant) {
if(!isset($current_tenant)) {
return false;
}
$this->context->set($current_tenant);
return true;
}
}
<?php
/**
* Example testcases written for PhpSpec framework.
*/
namespace spec\Kilo\Http\Middleware;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Kilo\Models\TenantSpecific\Tenant;
use Kilo\Models\TenantSpecific\TenantContext as Context;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector as Redirect;
class TenantMiddlewareSpec extends ObjectBehavior
{
function let(Tenant $tenant, Context $context, Redirect $redirect)
{
$this->beConstructedWith($tenant, $context, $redirect);
}
function it_is_initializable()
{
$this->shouldHaveType('Kilo\Http\Middleware\TenantMiddleware');
}
function it_sets_the_context_by_subdomain(
Request $request,
Tenant $tenant,
Context $context,
Redirect $redirect,
Tenant $matchingTenant
){
$next = function () { return 'next'; };
$request->route('domain')->willReturn(NULL);
$request->route('tld')->willReturn(NULL);
$request->route('subdomain')->willReturn('sub');
$tenant->getBySubdomain('sub')->willReturn($matchingTenant);
// Ready, set, deal with it!
$this->handle($request, $next)->shouldReturn($next($request));
$context->set($matchingTenant)->shouldHaveBeenCalled();
}
function it_sets_the_context_by_domain(
Request $request,
Tenant $tenant,
Context $context,
Redirect $redirect,
Tenant $matchingTenant
){
$next = function () { return 'next'; };
$request->route('domain')->willReturn('domain');
$request->route('tld')->willReturn('tld');
$request->route('subdomain')->willReturn(NULL);
$tenant->getByDomain('domain.tld')->willReturn($matchingTenant);
// Ready, set, deal with it!
$this->handle($request, $next)->shouldReturn($next($request));
$context->set($matchingTenant)->shouldHaveBeenCalled();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment