I wrote a middleware class that sets a singleton "Context" based upon either a domain or subdomain. This was an untested class and was refactored for testing with some help from @adamwathan (https://gist.github.com/adamwathan/d8dd4ea5337eb3f975b8).
Last active
November 15, 2018 09:34
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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