1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 23:34:03 +00:00

Update path identification and Fortify integration-related logic (#13)

* Add commented UrlBinding + FortifyRouteTenancy bootstrappers to the config

* Improve FortifyRoute bootstrapper docblock

* Rename bootstrappers

* Complete renaming

* Pass defaults of the original URL generator to the new one

* Fix URL generator-related test (query string id test WIP)

* Fix code style (php-cs-fixer)

* Make Fortify bootstrapper not depend on the UrlGenerator bootstrapper, update comments

* Fix testing UrlGenerator bootstrapper

* Update TenancyUrlGenerator annotations

* Pass tenant parameter manually in Fortify bootstrapper

* Properly test TenancyUrlGenerator functionality

* Get rid of query string in Fortify bootstrapper

* Fix code style (php-cs-fixer)

* Delete outdated comment

* Improve comment

* Improve before/afterEach

* Encourage passing parameters using TenancyUrlGenerator instead of URL::defaults()

* Delete rest of defaulting logic

* Fix code style (php-cs-fixer)

* Delete test group

* Update ForgetTenantParameter docblock

* Update passTenantParameterToRoutes annotation

* Complete todo in test

* Improve test

* Update comment

* Improve comment

* Add keepQueryParameters bool to Fortify bootstrapper

* Test keepQueryParameters

* minor docblock update

* minor docblock changes

* Delete extra import

* Update src/Overrides/TenancyUrlGenerator.php

Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>

* Improve comment

* Rename test

* Update bypass parameter-related test comments

* Fix merge

* Rename $keepQueryParameters

* Add docblock

* Add comment

* Refactor Fortify bootstrapper

* Fix code style (php-cs-fixer)

* Fix comment

* Skip Fortify bootstrapper test

* minor code improvements

* Improve fortify bootstrapper test

* Add Fortify bootstrapper annotation, improve code

* Fix code style (php-cs-fixer)

* Add commenet

* Complete resource syncing todo (cleanup not needed)

* Delete incorrect namespace

* Complete route context trait name todo

* Fix code style (php-cs-fixer)

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
lukinovec 2023-11-26 21:08:41 +01:00 committed by GitHub
parent c043661318
commit 4953c69fd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 255 additions and 141 deletions

View file

@ -7,23 +7,56 @@ namespace Stancl\Tenancy\Bootstrappers\Integrations;
use Illuminate\Config\Repository;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Enums\Context;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
/**
* Allows customizing Fortify redirect URLs.
* Intended to be used with UrlBindingBootstrapper.
* Allows customizing Fortify action redirects
* so that they can also redirect to tenant routes instead of just the central routes.
*
* @see \Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper
* Works with path and query string identification.
*/
class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
{
// 'fortify_action' => 'tenant_route_name'
public static array $fortifyRedirectTenantMap = [
// 'logout' => 'welcome',
];
/**
* Make Fortify actions redirect to custom routes.
*
* For each route redirect, specify the intended route context (central or tenant).
* Based on the provided context, we pass the tenant parameter to the route (or not).
* The tenant parameter is only passed to the route when you specify its context as tenant.
*
* The route redirects should be in the following format:
*
* 'fortify_action' => [
* 'route_name' => 'tenant.route',
* 'context' => Context::TENANT,
* ]
*
* For example:
*
* FortifyRouteTenancyBootstrapper::$fortifyRedirectMap = [
* // On logout, redirect the user to the "bye" route in the central app
* 'logout' => [
* 'route_name' => 'bye',
* 'context' => Context::CENTRAL,
* ],
*
* // On login, redirect the user to the "welcome" route in the tenant app
* 'login' => [
* 'route_name' => 'welcome',
* 'context' => Context::TENANT,
* ],
*];
*/
public static array $fortifyRedirectMap = [];
// Fortify home route name
public static string|null $fortifyHome = 'dashboard';
protected array|null $originalFortifyConfig = null;
/**
* Tenant route that serves as Fortify's home (e.g. a tenant dashboard route).
* This route will always receive the tenant parameter.
*/
public static string $fortifyHome = 'tenant.dashboard';
protected array $originalFortifyConfig = [];
public function __construct(
protected Repository $config,
@ -32,9 +65,9 @@ class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
public function bootstrap(Tenant $tenant): void
{
$this->originalFortifyConfig = $this->config->get('fortify');
$this->originalFortifyConfig = $this->config->get('fortify') ?? [];
$this->useTenantRoutesInFortify();
$this->useTenantRoutesInFortify($tenant);
}
public function revert(): void
@ -42,16 +75,31 @@ class FortifyRouteTenancyBootstrapper implements TenancyBootstrapper
$this->config->set('fortify', $this->originalFortifyConfig);
}
protected function useTenantRoutesInFortify(): void
protected function useTenantRoutesInFortify(Tenant $tenant): void
{
// Regenerate the URLs after the behavior of the route() helper has been modified
// in UrlBindingBootstrapper to generate URLs specific to the current tenant
$tenantRoutes = array_map(fn (string $routeName) => route($routeName), static::$fortifyRedirectTenantMap);
$tenantKey = $tenant->getTenantKey();
$tenantParameterName = PathTenantResolver::tenantParameterName();
$generateLink = function (array $redirect) use ($tenantKey, $tenantParameterName) {
// Specifying the context is only required with query string identification
// because with path identification, the tenant parameter should always present
$passTenantParameter = $redirect['context'] === Context::TENANT;
// Only pass the tenant parameter when the user should be redirected to a tenant route
return route($redirect['route_name'], $passTenantParameter ? [$tenantParameterName => $tenantKey] : []);
};
// Get redirect URLs for the configured redirect routes
$redirects = array_merge(
$this->originalFortifyConfig['redirects'] ?? [], // Fortify config redirects
array_map(fn (array $redirect) => $generateLink($redirect), static::$fortifyRedirectMap), // Mapped redirects
);
if (static::$fortifyHome) {
$this->config->set('fortify.home', route(static::$fortifyHome));
// Generate the home route URL with the tenant parameter and make it the Fortify home route
$this->config->set('fortify.home', route(static::$fortifyHome, [$tenantParameterName => $tenantKey]));
}
$this->config->set('fortify.redirects', array_merge($this->config->get('fortify.redirects') ?? [], $tenantRoutes));
$this->config->set('fortify.redirects', $redirects);
}
}

View file

@ -10,7 +10,7 @@ use Illuminate\Contracts\Routing\UrlGenerator;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
class UrlTenancyBootstrapper implements TenancyBootstrapper
class RootUrlBootstrapper implements TenancyBootstrapper
{
public static Closure|null $rootUrlOverride = null;
protected string|null $originalRootUrl = null;

View file

@ -21,7 +21,7 @@ use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
* @see TenancyUrlGenerator
* @see \Stancl\Tenancy\Resolvers\PathTenantResolver
*/
class UrlBindingBootstrapper implements TenancyBootstrapper
class UrlGeneratorBootstrapper implements TenancyBootstrapper
{
public function __construct(
protected Application $app,
@ -55,6 +55,8 @@ class UrlBindingBootstrapper implements TenancyBootstrapper
$app['config']->get('app.asset_url'),
);
$newGenerator->defaults($urlGenerator->getDefaultParameters());
$newGenerator->setSessionResolver(function () {
return $this->app['session'] ?? null;
});

View file

@ -13,8 +13,7 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Route as RouteFacade;
use Stancl\Tenancy\Enums\RouteMode;
// todo1 Name maybe DealsWithMiddlewareContexts?
trait DealsWithEarlyIdentification
trait DealsWithRouteContexts
{
/**
* Get route's middleware context (tenant, central or universal).

View file

@ -11,12 +11,13 @@ use Stancl\Tenancy\PathIdentificationManager;
/**
* Remove the tenant parameter from the matched route when path identification is used globally.
*
* The tenant parameter gets forgotten using PathTenantResolver so that the route actions don't have to accept it.
* Then, tenancy gets initialized, and URL::defaults() is used to give the tenant parameter to the next matched route.
* But with kernel identification, the route gets matched AFTER the point when URL::defaults() is used,
* and because of that, the matched route gets the tenant parameter again, so we forget the parameter again on RouteMatched.
* While initializing tenancy, we forget the tenant parameter (in PathTenantResolver),
* so that the route actions don't have to accept it.
*
* We remove the {tenant} parameter from the hydrated route when
* With kernel identification, tenancy gets initialized before the route gets matched.
* The matched route gets the tenant parameter again, so we have to forget the parameter again on RouteMatched.
*
* We remove the {tenant} parameter from the matched route when
* 1) the InitializeTenancyByPath middleware is in the global stack, AND
* 2) the matched route does not have identification middleware (so that {tenant} isn't forgotten when using route-level identification), AND
* 3) the route isn't in the central context (so that {tenant} doesn't get accidentally removed from central routes).

View file

@ -6,13 +6,9 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\Events\InitializingTenancy;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
@ -49,8 +45,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
// We don't want to initialize tenancy if the tenant is
// simply injected into some route controller action.
if (in_array(PathTenantResolver::tenantParameterName(), $route->parameterNames())) {
$this->setDefaultTenantForRouteParametersWhenInitializingTenancy();
return $this->initializeTenancy(
$request,
$next,
@ -61,18 +55,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
}
}
protected function setDefaultTenantForRouteParametersWhenInitializingTenancy(): void
{
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
/** @var Tenant $tenant */
$tenant = $event->tenancy->tenant;
URL::defaults([
PathTenantResolver::tenantParameterName() => $tenant->getTenantKey(),
]);
});
}
/**
* Path identification request has a tenant if the middleware context is tenant.
*

View file

@ -9,7 +9,7 @@ use Illuminate\Support\Arr;
use Stancl\Tenancy\PathIdentificationManager;
/**
* This class is used in place of the default UrlGenerator when UrlBindingBootstrapper is enabled.
* This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled.
*
* TenancyUrlGenerator does two extra things:
* 1. Autofill the {tenant} parameter in the tenant context with the current tenant if $passTenantParameterToRoutes is enabled (enabled by default)
@ -28,16 +28,20 @@ class TenancyUrlGenerator extends UrlGenerator
public static string $bypassParameter = 'central';
/**
* Determine if the route names of routes generated using
* `route()` or `temporarySignedRoute()` should get prefixed with the tenant route name prefix.
* Determine if the route names passed to `route()` or `temporarySignedRoute()`
* should get prefixed with the tenant route name prefix.
*
* Set this to true when using path identification.
* This is useful when using path identification with packages that generate URLs,
* like Jetstream, so that you don't have to manually prefix route names passed to each route() call.
*/
public static bool $prefixRouteNames = false;
/**
* Determine if the tenant parameter should get passed
* to the links generated by `route()` or `temporarySignedRoute()`.
* to the links generated by `route()` or `temporarySignedRoute()` whenever available
* (enabled by default works with both path and query string identification).
*
* With path identification, you can disable this and use URL::defaults() instead (as an alternative solution).
*/
public static bool $passTenantParameterToRoutes = true;

View file

@ -8,14 +8,14 @@ use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Traits\Macroable;
use Stancl\Tenancy\Concerns\DealsWithEarlyIdentification;
use Stancl\Tenancy\Concerns\DealsWithRouteContexts;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByIdException;
class Tenancy
{
use Macroable, DealsWithEarlyIdentification;
use Macroable, DealsWithRouteContexts;
/**
* The current tenant.