From 5e6d82be57000ea8b79bd96a60236dc7dceb882c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sat, 9 May 2020 03:56:41 +0200 Subject: [PATCH] path identification wip --- ...antCouldNotBeIdentifiedByPathException.php | 27 +++++ src/Middleware/InitializeTenancyByPath.php | 41 +++++++ src/Resolvers/DomainTenantResolver.php | 3 +- src/Resolvers/PathTenantResolver.php | 27 +++++ src/Tenancy.php | 7 ++ src/helpers.php | 2 +- tests/v3/PathIdentificationTest.php | 111 ++++++++++++++++++ tests/v3/SubdomainIdentificationTest.php | 0 8 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 src/Exceptions/TenantCouldNotBeIdentifiedByPathException.php create mode 100644 src/Middleware/InitializeTenancyByPath.php create mode 100644 src/Resolvers/PathTenantResolver.php create mode 100644 tests/v3/PathIdentificationTest.php create mode 100644 tests/v3/SubdomainIdentificationTest.php diff --git a/src/Exceptions/TenantCouldNotBeIdentifiedByPathException.php b/src/Exceptions/TenantCouldNotBeIdentifiedByPathException.php new file mode 100644 index 00000000..ee870454 --- /dev/null +++ b/src/Exceptions/TenantCouldNotBeIdentifiedByPathException.php @@ -0,0 +1,27 @@ +setSolutionDescription('Did you forget to create a tenant for this path?') + ->setDocumentationLinks([ + 'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v2/creating-tenants/', // todo update link for v3 + ]); + } +} diff --git a/src/Middleware/InitializeTenancyByPath.php b/src/Middleware/InitializeTenancyByPath.php new file mode 100644 index 00000000..7e5c92a8 --- /dev/null +++ b/src/Middleware/InitializeTenancyByPath.php @@ -0,0 +1,41 @@ +tenancy = $tenancy; + $this->resolver = $resolver; + } + + public function handle(Request $request, Closure $next) + { + /** @var Route $route */ + $route = $request->route(); + + // Only initialize tenancy if tenant is the first parameter + // We don't want to initialize tenancy if the tenant is + // simply injected into some route controller action. + if ($route->parameterNames()[0] === 'tenant') { + $this->tenancy->initialize( + $this->resolver->resolve($route) + ); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/src/Resolvers/DomainTenantResolver.php b/src/Resolvers/DomainTenantResolver.php index 2f0c647e..bfba9c5f 100644 --- a/src/Resolvers/DomainTenantResolver.php +++ b/src/Resolvers/DomainTenantResolver.php @@ -4,14 +4,13 @@ namespace Stancl\Tenancy\Resolvers; use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Contracts\TenantResolver; -use Stancl\Tenancy\Database\Models\Domain; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; class DomainTenantResolver implements TenantResolver { public function resolve(...$args): Tenant { - $domain = app(config('tenancy.domain_model'))->where('domain', $args[0])->first(); + $domain = config('tenancy.domain_model')::where('domain', $args[0])->first(); if ($domain) { return $domain->tenant; diff --git a/src/Resolvers/PathTenantResolver.php b/src/Resolvers/PathTenantResolver.php new file mode 100644 index 00000000..cfd68a71 --- /dev/null +++ b/src/Resolvers/PathTenantResolver.php @@ -0,0 +1,27 @@ +parameter('tenant')) { + $route->forgetParameter('tenant'); + + if ($tenant = config('tenancy.tenant_model')::find($id)) { + return $tenant; + } + } + + throw new TenantCouldNotBeIdentifiedByPathException($id); + } +} diff --git a/src/Tenancy.php b/src/Tenancy.php index 24917c24..afd063a1 100644 --- a/src/Tenancy.php +++ b/src/Tenancy.php @@ -13,15 +13,22 @@ class Tenancy /** @var callable|null */ public static $getBootstrappers = null; + /** @var bool */ + public $initialized = false; + public function initialize(Tenant $tenant): void { $this->tenant = $tenant; + $this->initialized = true; + event(new Events\TenancyInitialized($tenant)); } public function end(): void { + $this->initialized = false; + event(new Events\TenancyEnded($this->tenant)); $this->tenant = null; diff --git a/src/helpers.php b/src/helpers.php index 70007826..bcec0434 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -26,7 +26,7 @@ if (! function_exists('tenant')) { return app(Tenant::class); } - return optional(app(Tenant::class))->get($key) ?? null; + return optional(app(Tenant::class))->getAttribute($key) ?? null; } } diff --git a/tests/v3/PathIdentificationTest.php b/tests/v3/PathIdentificationTest.php new file mode 100644 index 00000000..5747faa0 --- /dev/null +++ b/tests/v3/PathIdentificationTest.php @@ -0,0 +1,111 @@ + []]); + } + + public function setUp(): void + { + parent::setUp(); + + Route::group([ + 'prefix' => '/{tenant}', + 'middleware' => InitializeTenancyByPath::class, + ], function () { + Route::get('/foo/{a}/{b}', function ($a, $b) { + return "$a + $b"; + }); + + Route::get('/bar', [TestController::class, 'index']); + }); + } + + /** @test */ + public function tenant_can_be_identified_by_path() + { + Tenant::create([ + 'id' => 'acme', + ]); + + $this->assertFalse(tenancy()->initialized); + + $this + ->get('/acme/foo/abc/xyz'); + + $this->assertTrue(tenancy()->initialized); + $this->assertSame('acme', tenant('id')); + } + + /** @test */ + public function route_actions_dont_get_the_tenant_id() + { + Tenant::create([ + 'id' => 'acme', + ]); + + $this->assertFalse(tenancy()->initialized); + + $this + ->get('/acme/foo/abc/xyz') + ->assertSee('abc + xyz'); + + $this->assertTrue(tenancy()->initialized); + $this->assertSame('acme', tenant('id')); + } + + /** @test */ + public function exception_is_thrown_when_tenant_cannot_be_identified_by_path() + { + // todo the exception assertion doesn't work + $this->expectException(TenantCouldNotBeIdentifiedByPathException::class); + + $this->assertFalse(tenancy()->initialized); + } + + /** @test */ + public function tenancy_is_initialized_prior_to_controller_constructors() + { + // todo same test for domain resolver + + Tenant::create([ + 'id' => 'acme', + ]); + + $this->assertFalse(tenancy()->initialized); + + $this + ->get('/acme/bar') + ->assertSee('foo'); + + // todo make this pass + $this->assertTrue(app('tenancy_was_initialized_in_constructor')); + $this->assertTrue(tenancy()->initialized); + $this->assertSame('acme', tenant('id')); + } +} + +class TestController +{ + public function __construct() + { + app()->instance('tenancy_was_initialized_in_constructor', tenancy()->initialized); + } + + public function index() + { + return 'foo'; + } +} \ No newline at end of file diff --git a/tests/v3/SubdomainIdentificationTest.php b/tests/v3/SubdomainIdentificationTest.php new file mode 100644 index 00000000..e69de29b