Skip to content

Commit c45fe5e

Browse files
authored
refactor: Improve routing (#112)
1 parent d733dc5 commit c45fe5e

15 files changed

+324
-20
lines changed

src/Contracts/IdentityResolver.php

+15
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,24 @@ public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string;
5353
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
5454
*
5555
* @return \Illuminate\Routing\RouteRegistrar
56+
*
57+
* @deprecated Use {@see self::configureRoute()} instead
5658
*/
5759
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar;
5860

61+
/**
62+
* Configure the provided route for the resolver
63+
*
64+
* Configures a provided route to work with itself, adding parameters,
65+
* middleware, and anything else required, besides the default middleware.
66+
*
67+
* @param \Illuminate\Routing\RouteRegistrar $route
68+
* @param \Sprout\Contracts\Tenancy<\Sprout\Contracts\Tenant> $tenancy
69+
*
70+
* @return void
71+
*/
72+
public function configureRoute(RouteRegistrar $route, Tenancy $tenancy): void;
73+
5974
/**
6075
* Perform setup actions for the tenant
6176
*

src/Exceptions/CompatibilityException.php

+12
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,16 @@ public static function make(string $firstType, string $firstName, string $second
2929
{
3030
return new self('Cannot use ' . $firstType . ' [' . $firstName . '] with ' . $secondType . ' [' . $secondName . ']');
3131
}
32+
33+
/**
34+
* Create an exception for incompatible optional middleware usage
35+
*
36+
* @param string $resolver
37+
*
38+
* @return self
39+
*/
40+
public static function optionalMiddleware(string $resolver): self
41+
{
42+
return new self('Cannot use optional tenant middleware with the non-parameter based resolver [' . $resolver . '].');
43+
}
3244
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Sprout\Exceptions;
5+
6+
final class DeprecatedException extends SproutException
7+
{
8+
public static function make(): self
9+
{
10+
return new self('This feature is deprecated');
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Sprout\Http\Middleware;
5+
6+
use Closure;
7+
use Illuminate\Http\Request;
8+
use Sprout\Exceptions\NoTenantFoundException;
9+
use Sprout\Sprout;
10+
use Sprout\Support\ResolutionHelper;
11+
use Sprout\Support\ResolutionHook;
12+
use Symfony\Component\HttpFoundation\Response;
13+
14+
/**
15+
* Sprout Optional Tenant Context Middleware
16+
*
17+
* This piece of middleware has a dual function.
18+
* It marks routes as being multitenanted if resolving during routing, and it
19+
* will resolve tenants if resolving during middleware.
20+
* Unlike {@see \Sprout\Http\Middleware\SproutTenantContextMiddleware}, no
21+
* exception will be thrown if there's no tenant.
22+
*
23+
* @package Core
24+
*/
25+
final class SproutOptionalTenantContextMiddleware
26+
{
27+
/**
28+
* The alias for this middleware
29+
*/
30+
public const ALIAS = 'sprout.tenanted.optional';
31+
32+
/**
33+
* @var \Sprout\Sprout
34+
*/
35+
private Sprout $sprout;
36+
37+
/**
38+
* Create a new instance of the middleware
39+
*
40+
* @param \Sprout\Sprout $sprout
41+
*/
42+
public function __construct(Sprout $sprout)
43+
{
44+
$this->sprout = $sprout;
45+
}
46+
47+
/**
48+
* Handle the request
49+
*
50+
* @param \Illuminate\Http\Request $request
51+
* @param \Closure $next
52+
* @param string ...$options
53+
*
54+
* @return \Symfony\Component\HttpFoundation\Response
55+
*
56+
* @throws \Sprout\Exceptions\NoTenantFoundException
57+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
58+
* @throws \Sprout\Exceptions\MisconfigurationException
59+
*/
60+
public function handle(Request $request, Closure $next, string ...$options): Response
61+
{
62+
[$resolverName, $tenancyName] = ResolutionHelper::parseOptions($options);
63+
64+
if ($this->sprout->supportsHook(ResolutionHook::Middleware)) {
65+
ResolutionHelper::handleResolution(
66+
$request,
67+
ResolutionHook::Middleware,
68+
$this->sprout,
69+
$resolverName,
70+
$tenancyName,
71+
false,
72+
true
73+
);
74+
}
75+
76+
return $next($request);
77+
}
78+
}

src/Http/Resolvers/CookieIdentityResolver.php

+2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string
155155
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
156156
*
157157
* @return \Illuminate\Routing\RouteRegistrar
158+
*
159+
* @deprecated Use {@see self::configureRoute()} instead
158160
*/
159161
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
160162
{

src/Http/Resolvers/HeaderIdentityResolver.php

+20
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string
110110
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
111111
*
112112
* @return \Illuminate\Routing\RouteRegistrar
113+
*
114+
* @deprecated Use {@see self::configureRoute()} instead
113115
*/
114116
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
115117
{
@@ -118,4 +120,22 @@ public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy):
118120
AddTenantHeaderToResponse::class . ':' . $this->getName() . ',' . $tenancy->getName(),
119121
])->group($groupRoutes);
120122
}
123+
124+
/**
125+
* Configure the provided route for the resolver
126+
*
127+
* Configures a provided route to work with itself, adding parameters,
128+
* middleware, and anything else required, besides the default middleware.
129+
*
130+
* @param \Illuminate\Routing\RouteRegistrar $route
131+
* @param \Sprout\Contracts\Tenancy<\Sprout\Contracts\Tenant> $tenancy
132+
*
133+
* @return void
134+
*/
135+
public function configureRoute(RouteRegistrar $route, Tenancy $tenancy): void
136+
{
137+
$route->middleware([
138+
AddTenantHeaderToResponse::class . ':' . $this->getName() . ',' . $tenancy->getName(),
139+
]);
140+
}
121141
}

src/Http/Resolvers/PathIdentityResolver.php

+21
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ public function getSegment(): int
9696
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
9797
*
9898
* @return \Illuminate\Routing\RouteRegistrar
99+
*
100+
* @deprecated Use {@see self::configureRoute()} instead
99101
*/
100102
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
101103
{
@@ -106,6 +108,25 @@ public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy):
106108
)->group($groupRoutes);
107109
}
108110

111+
/**
112+
* Configure the provided route for the resolver
113+
*
114+
* Configures a provided route to work with itself, adding parameters,
115+
* middleware, and anything else required, besides the default middleware.
116+
*
117+
* @param \Illuminate\Routing\RouteRegistrar $route
118+
* @param \Sprout\Contracts\Tenancy<\Sprout\Contracts\Tenant> $tenancy
119+
*
120+
* @return void
121+
*/
122+
public function configureRoute(RouteRegistrar $route, Tenancy $tenancy): void
123+
{
124+
$this->applyParameterPatternMapping(
125+
$route->prefix($this->getRoutePrefix($tenancy)),
126+
$tenancy
127+
);
128+
}
129+
109130
/**
110131
* Get the route prefix including the tenant parameter
111132
*

src/Http/Resolvers/SessionIdentityResolver.php

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string
126126
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
127127
*
128128
* @return \Illuminate\Routing\RouteRegistrar
129+
*
130+
* @deprecated Use {@see self::configureRoute()} instead
129131
*/
130132
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
131133
{

src/Http/Resolvers/SubdomainIdentityResolver.php

+21
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ public function getRouteDomain(Tenancy $tenancy): string
114114
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
115115
*
116116
* @return \Illuminate\Routing\RouteRegistrar
117+
*
118+
* @deprecated Use {@see self::configureRoute()} instead
117119
*/
118120
public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy): RouteRegistrar
119121
{
@@ -124,6 +126,25 @@ public function routes(Router $router, Closure $groupRoutes, Tenancy $tenancy):
124126
)->group($groupRoutes);
125127
}
126128

129+
/**
130+
* Configure the provided route for the resolver
131+
*
132+
* Configures a provided route to work with itself, adding parameters,
133+
* middleware, and anything else required, besides the default middleware.
134+
*
135+
* @param \Illuminate\Routing\RouteRegistrar $route
136+
* @param \Sprout\Contracts\Tenancy<\Sprout\Contracts\Tenant> $tenancy
137+
*
138+
* @return void
139+
*/
140+
public function configureRoute(RouteRegistrar $route, Tenancy $tenancy): void
141+
{
142+
$this->applyParameterPatternMapping(
143+
$route->domain($this->getRouteDomain($tenancy)),
144+
$tenancy
145+
);
146+
}
147+
127148
/**
128149
* Get the route domain with the parameter replaced with the tenant identifier
129150
*

src/Http/RouteCreator.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Sprout\Http;
5+
6+
use Closure;
7+
use Illuminate\Routing\Router;
8+
use Illuminate\Routing\RouteRegistrar;
9+
use Illuminate\Support\Facades\Route;
10+
use Sprout\Contracts\IdentityResolverUsesParameters;
11+
use Sprout\Exceptions\CompatibilityException;
12+
use Sprout\Http\Middleware\SproutOptionalTenantContextMiddleware;
13+
use Sprout\Http\Middleware\SproutTenantContextMiddleware;
14+
use Sprout\Managers\IdentityResolverManager;
15+
use Sprout\Managers\TenancyManager;
16+
17+
final class RouteCreator
18+
{
19+
public static function create(Closure $routes, ?string $resolver = null, ?string $tenancy = null, bool $optional = false): RouteRegistrar
20+
{
21+
// Get the resolver instance first
22+
$resolverInstance = app()->make(IdentityResolverManager::class)->get($resolver);
23+
24+
if ($optional && $resolverInstance instanceof IdentityResolverUsesParameters) {
25+
throw CompatibilityException::optionalMiddleware($resolverInstance->getName());
26+
}
27+
28+
$tenancyInstance = app()->make(TenancyManager::class)->get($tenancy);
29+
$middleware = $optional ? SproutOptionalTenantContextMiddleware::ALIAS : SproutTenantContextMiddleware::ALIAS;
30+
$options = [$resolverInstance->getName(), $tenancyInstance->getName()];
31+
32+
return Route::middleware([$middleware . ':' . implode(',', $options)])
33+
->group(function (Router $router) use ($routes, $resolverInstance, $tenancyInstance) {
34+
$registrar = new RouteRegistrar($router);
35+
36+
$resolverInstance->configureRoute($registrar, $tenancyInstance);
37+
38+
$registrar->group($routes);
39+
});
40+
}
41+
}

src/Http/RouterMethods.php

+27-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
namespace Sprout\Http;
55

66
use Closure;
7+
use Illuminate\Routing\Router;
78
use Illuminate\Routing\RouteRegistrar;
9+
use Illuminate\Support\Facades\Route;
10+
use Sprout\Contracts\IdentityResolverUsesParameters;
11+
use Sprout\Exceptions\CompatibilityException;
12+
use Sprout\Http\Middleware\SproutOptionalTenantContextMiddleware;
13+
use Sprout\Http\Middleware\SproutTenantContextMiddleware;
814
use Sprout\Managers\IdentityResolverManager;
915
use Sprout\Managers\TenancyManager;
1016

@@ -34,13 +40,27 @@ class RouterMethods
3440
public function tenanted(): Closure
3541
{
3642
return function (Closure $routes, ?string $resolver = null, ?string $tenancy = null): RouteRegistrar {
37-
return app()->make(IdentityResolverManager::class)
38-
->get($resolver)
39-
->routes(
40-
$this, // @phpstan-ignore-line
41-
$routes,
42-
app()->make(TenancyManager::class)->get($tenancy)
43-
);
43+
return RouteCreator::create($routes, $resolver, $tenancy);
44+
};
45+
}
46+
47+
/**
48+
* Create possibly tenanted routes
49+
*
50+
* @param Closure $routes
51+
* @param string|null $resolver
52+
* @param string|null $tenancy
53+
*
54+
* @return \Illuminate\Routing\RouteRegistrar
55+
*
56+
* @noinspection PhpDocSignatureInspection
57+
*
58+
* @phpstan-ignore parameter.notFound,parameter.notFound,parameter.notFound,return.phpDocType
59+
*/
60+
public function possiblyTenanted(): Closure
61+
{
62+
return function (Closure $routes, ?string $resolver = null, ?string $tenancy = null): RouteRegistrar {
63+
return RouteCreator::create($routes, $resolver, $tenancy, true);
4464
};
4565
}
4666
}

0 commit comments

Comments
 (0)