Bonjour,
je test actuellement la multitenancy dans laravel et je rencontre un problème lié aux Routes.

Le fonctionnement de mon application est le suivant:

Pour les clients sousDomaine.exemple.com
Pour l'administration de l'application app.exemple.com

Actuellement tout fonctionne comme je le veux excepté les routes qui sont similaires entre l'admin et les clients.

client001.exemple.com/login
app.exemple.com/login

Si j'ai bien compris le fonctionnement de Laravel, il va enregistrer les routes dans l'ordre et prenant la dernière route.

Ce que je veux
Afin de contrer la gestion des routes de Laravel, je souhaiterais dans un premier temps savoir si il est possible au niveau MiddlWare d'utiliser un fichier de routes en fonction de la requête.

En gros ça donnerais :

Si $request->getHost() === 'app' alors utilise le routeFile admin.php sinon utilise client.php

Dans un deuxième temps si comme je le pense ce n'est pas possible, je voudrais pouvoir créer un middleware permettant de retourner une erreur 404 lorsqu'on demande une url similaire à une route client.
Mais je n'ai pas trouvé comment le faire.

Ce que j'obtiens

Avec les routes ci-dessus, j'obtiens une erreur au niveau de la requête lorsque je demande le login sur l'admin.

Il recherche automatiquement un client car la route est lié aux clients.

Merci d'avance pour vos conseils et aide.

3 réponses


Alors pour les routes, tu n'as pas besoin de faire de middlewares
Regardes comment Laravel gères ses routes :p

En gros tu passes par le fichier RouteServiceProvider (ça doit etre dans le dossier App\Providers je pense)

Tu réée une fonction mapAppRoutes/mapClientRoutes qui va gèrer tes routes App:

    public function mapAppRoutes()
    {
        Route::namespace($this->namespace)->group(base_path('routes/app.php'));
    }

    public function mapClientRoutes()
    {
        Route::namespace($this->namespace)->group(base_path('routes/client.php'));
    }

Et voila tu as tes routes customs pour tes subdomain :)

Après tu a donné comme exemple client001; je suppose q'uil peut y avoir client002, client003 etc..., dans ce cas oui tu peux faire un middleware:

    public function mapClientRoutes()
    {
        Route::namespace($this->namespace)
            ->middleware('client')
            ->group(base_path('routes/client.php'));
    }

Et ton fichier client gèreras ton 001, 002 etc...

Ah et dans ton provider n'oublies pas de rajouter tes nouvelles fonctions dans la fonction map:

    public function map()
  {
      $this->mapApiRoutes();
      $this->mapWebRoutes();

      // lignes à rajouter
      $this->mapAppRoutes();
      $this->mapClientRoutes();
  }

comme sa tu as "web" pour tes routes sans subdomain, "app" pour tes subdomain "app", et "client" pour tes subdomain "client"

Franco
Auteur

Merci Popotte pour ta réponse,

J'utilise déjà ce système, le problème c'est que Laravel charge automatiquement toutes les routes dans l'ordre et si tu as 2 mêmes routes dans App et Clients il va prendre la dernière route => dans ton exemple la route Clients étant donné qu'il est en dernier dans le RouteProvider.

Mon problème c'est qu'il semble impossible de charger un fichier de route en fonction de l'url, je pense donc qu'il faut un Middleware qui va renvoyer une erreur 404 si la route ne correspond pas au domaine de l'url.

Exemple: app.exemple.com/login nous donne la route {client}.exemple.com/login dans ce cas l'url ne correspond pas à la route on renvois un erreur 404.
{client}.exemple.com/login retourne la view login
app.exemple.com/adminlogin retourne la vue admin.login

Le middleware devrait être associé à toutes les routes clients pour prévenir l'utilisation de ses routes dans l'admin. Mais je n'arrive pas formuler le code permettant de faire ce que je recherche.

Voici ce que j'ai actuellement et qui ne fonctionne pas.

Le Middleware PreventAccessFromAdmin

class PreventAccessFromAdmin
{
    use UsesTenantModel;
    use UsesMultitenancyConfig;

    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        // Ceci n'est pas suffisant pour prévenir l'utilisation des routes identiques
        // Il manque un OnFailResponse ??
        if ($request->getHost() === config('app.admin_url')) {
            abort(404);
        }
        return $next($request);
    }
}

Les routes clients (tenants)

/**
 * Tenants Routes
 */
Route::middleware(['web', 'tenant', 'PreventAccessFromAdmin'])->group(function () {
    Route::get('/', function () {
        return Inertia::render('Welcome', [
            'canLogin' => Route::has('login'),
            'canRegister' => Route::has('register'),
            'laravelVersion' => Application::VERSION,
            'phpVersion' => PHP_VERSION,
            'tenant' => Tenant::current(),
        ]);
    })->name('welcome');

    Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
        return Inertia::render('Dashboard');
    })->name('dashboard');

});

Avec ça j'obtiens bien les views login, register et profil de JetStream avec client.exemple.com et une erreur sql avec app.exemple.com

Pour l'admin:

/**
 * Admin Routes
 */
Route::domain('app.exemple.com')->middleware(['web', 'admin'])->group(function () {
    Route::get('/', function () {
        return "Admin Routes OK";
    });
    Route::get('/signup', function () {
        return "Signup Page OK";
    });
    Route::get('/login', function () {
        return "Login as Admin";
    });

    // Others route goes there.

    // Catch All Route
    Route::any('{any}', function () {
        abort(404);
    })->where('any', '.*');

});

Comme on peut le voir dans l'erreur il recherche la table landlord.users au lieu de la table landlord.admins (landlord est la db admin / tenant est la db clients)

Illuminate\Database\QueryException
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'landlord.users' doesn't exist (SQL: select count(*) as aggregate from `users` where `email` = test@test.com)

L'erreur se voir aussi dans la partie requête ou recherche un tenant (client) au lieu d'aller directement à admin
Partie debug de Laravel

Query
    Query
          select
            *
          from
            `tenants`
          where
            `domain` = ?
          limit
            1
Time
1.78
Connection name
landlord
0
app.exemple.com

Petits détails supplémentaire j'utilise:

  1. Laravel 8
  2. Package Spatie/Multitenancy
  3. JetStream + InertiaJS scafolding

Toute aide sera la bienvenue Merci d'avance

Mmmmh ouai c'est vrai qu'il y'a des conflits de routes :/

Alors la doc Laravel dit qu'il faut mapper en premier le subdomain avant de mapper web et api qui sont root:

In order to ensure your subdomain routes are reachable, you should register subdomain routes before registering root domain routes. This will prevent root domain routes from overwriting subdomain routes which have the same URI path.

Pour Laravel8 de toutes façon je regarde dans la doc Laravel 8 :p
ensuite pour JetStream et InertiaJS, ça passe APRES le routing donc y'a pas de soucis à ce niveau la, et pour la librairie de spatie... je vais vérifier ça :o

Franco
Auteur

Salut popotte,
désolé de répondre si tardivement, mais je n'ai pas eu beaucoup de temps pour continuer mes tests.

En fait ce qu'il se passait, c'est que les routes du package Fortify étaient chargées en dernier et prenaient donc l'ascendant sur mes routes.

En ajoutant un ignore route dans le provider Fortify le problème est réglé et suffit de créer ses propres routes ou de copier les routes originales dans le bon fichier de route.

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Fortify::ignoreRoutes();
    }
}

Avec ceci tout fonctionne correctement maintenant et je peux avoir les mêmes routes (ex. '/login') avec des vues et controller différents.

Ce post est pour moi résolu!