What is multi-sites with laravel?

If you are an advance laravel developer and want to know how you can run multiple website using single laravel repositry or project then we will cover how to do that in this tutorial.

By default laravel does not allow you to run multiple sites with single codebase however making small changes to your AppProvider file you can achieve this task.

We have to create a new folder called Sites in app directory. We will create two different website however we will put our website logic in single laravel project.

First of all create a new laravel project. If you don't know how to create a laravel project follow the link below:

Install Laravel5

Why are we doing this?

If you have read my similar tutorial about setting up multi-sites setup then you will notice that it only works fine with web however it does not get correct .env config for multi-sites.

In this tutorial we will take a different approach to get both web and console working with multi-site setup. We will still use single repo to manage our multisites.

All of our sites will be using single laravel codebase.

Virtual host settings for Apache

Let's say that we have two websites:

  • www.site1.com
  • www.site2.com

Let's create a virtual host config for our site1:

<VirtualHost *:80>

    # variable that identify which 
    # project to use from multi-site project
    SetEnv SITE_CODE SITE1

    ServerName www.site1.com
    DocumentRoot /var/www/SITE1/public

    <Directory /var/www/SITE1/public>
        DirectoryIndex index.php
        AllowOverride All
        Require all granted
        Order allow,deny
        Allow from all
    </Directory>

</VirtualHost>

Let's create another virtual host config for our site2:

<VirtualHost *:80>

    # variable that identify which 
    # project to use from multi-site project
    SetEnv SITE_CODE SITE2

    ServerName www.site1.com
    DocumentRoot /var/www/SITE2/public

    <Directory /var/www/SITE2/public>
        DirectoryIndex index.php
        AllowOverride All
        Require all granted
        Order allow,deny
        Allow from all
    </Directory>

</VirtualHost>

Once you are done with your virtual host configuration now we have to check out our laravel project. I assume that you have remote repository with laravel 5.4.

For each site we will use a uppercase code to uniquely identify these websites:

  • SITE1 - www.site1.com
  • SITE2 - www.site2.com

Let us check out laravel project into two different directory to work with:

# go to your apache project root
# and run following commands in terminal
cd /var/www
git clone git@bitbucket.org:teamsinspace/laravel.git SITE1
git clone git@bitbucket.org:teamsinspace/laravel.git SITE2

# we will have following directory structure now
|--var
   |--www
      |--SITE1
      |--SITE2

# edit hosts file and add following lines in /etc/hosts
127.0.0.1 www.site1.com
127.0.0.1 www.site2.com

Until now, we have created two virtual host for our two different sites and point them in different directories of our apache project root directories.

Let us create some important folder structure for our multi-site application. Create following folders and files in your project as shown below:

Now, we have a decent folder structure where each site has their own .env file, config.php file and routes.php file. These files are site specific files. Each site has their own routes, configurations, translations and migration files.

Now, whenever we create a new site we have to create a folder in app/Sites folder with site code for example SITE1, SITE2, SITE3 and on.

Make sure to use unique uppercase code and site related controllers, view, models, migrations, config and route file will be placed in that new site folder as shown above.

Now, we have to tell laravel to follow this new structure. We have to place some sort of logic in our AppServiceProvider.php class.

Open app/Providers/AppServiceProvider.php file and add following logic to boot method:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // get current site path
        $site_name = strtoupper(getenv('SITE_CODE'));
        $namespace = "\\App\\Sites\\{$site_name}\\Controllers";
        $site_path = app_path(). '/Sites/' .$site_name;

        if (file_exists($site_path)) {
            $this->loadMigrationsFrom($site_path.'/Migrations');
            $this->loadViewsFrom($site_path. '/Views', strtolower($site_name));
            $this->mergeConfigFrom($site_path. '/config.php', strtolower($site_name));
            $this->loadTranslationsFrom($site_path.'/Translations', strtolower($site_name));
            \Route::namespace($namespace)->middleware('web')->group($site_path. '/routes.php');
        }
    }
}

When laravel app loads it will call above boot method where we get SITE_CODE constant from apache env variable.

When we create our virtual host configurations for apache we have to define SITE_CODE for each of our domain name so that we load appropriate site code.

Update Kernel to identify sites correctly

Once we are done with AppServiceProvider.php file changes next we need to let laravel know about env variable that we are going to use for our project.

Open app/Http/Kernel.php file modify it so that it points to correct .env file:

namespace App\Http;

use Illuminate\Routing\Router;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    public function __construct(Application $app, Router $router)
    {
        $env_path = strtoupper(getenv('SITE_CODE'));
        if($env_path) {
            $app->useEnvironmentPath(app_path('Sites/' .$env_path. '/'));
        }

        parent::__construct($app, $router);
    }

    // existing code will go here
}

To test that .env file is loaded correctly for each site. Add following line to your .env files:

# add following line to "app/Sites/SITE1/.env"
SITE_NAME=www.site1.com

# add following line to "app/Sites/SITE2/.env"
SITE_NAME=www.site2.com

# edit your laravel route file "routes/web.php"
Route::get('/', function () {
    dd(env('SITE_NAME'));
    return view('welcome');
});

Now, reload your sites "www.site1.com" and "www.site2.com" and you will see that it will dump the variable value from correct .env file for each site.

Whenever you create a new project you have to remember following file structure:

|-- app
     |-- Sites
          |-- SITE1
                 |-- routes.php   -> routes for www.test1.com site
                 |-- .env         -> site configurations for www.site1.com
                 |-- config.php   -> config variables specific to www.site1.com
                 |-- Controllers  -> controllers specific to www.site1.com 
                 |-- Views        -> views specific to www.site1.com
                 |-- Migrations   -> Migrations specific to www.site1.com
                 |-- Translations -> Translations specific to www.site1.com

We have got web configuration working fine however your app might need to work with console as well. We have to configure our laravel app so that console command should identify correct website .env file.

Let's overwrite console constructor in app/Console/Kernel.php

namespace App\Console;

use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    public function __construct(Application $app, Dispatcher $events)
    {
        # get the current project directory i.e. SITE1 or SITE2
        $site_path = basename(dirname(dirname(__DIR__)));

        # define path to .env i.e Sites/SITE1/.env or Sites/SITE2/.env
        $env_path  = app_path('Sites/' .strtoupper($site_path). '/');

        # load configuration from env file
        $app->useEnvironmentPath($env_path);

        parent::__construct($app, $events);
    }

    // existing code will go here
}

We are not doing any fancy things with above code. We identify current directory and get parent directory name which would be basically return our site code either SITE1 or SITE2

After identifying correct code it will set correct .env file path so that our console will properly identify which site we are running console commands for.

That is it, we have successfully configure multi-sites with single laravel codebase to work fine with both web and console application.