Home » Code » Multi-Tenancy in Laravel 4

Multi-Tenancy in Laravel 4

Posted by on March 31st, 2014

Multi Tenancy in Laravel 4
Multi-tenant applications are where you create a single application that is used independently by many different clients. When a client authenticates with your application, they are only able to access the data that they have created in your application and cannot see any data that was created by any of your other clients.

A typical example of a multi-tenant application is Basecamp. Basecamp customers can only see their own projects, but Basecamp is a single application that is used for all customers.

Multi-tenant applications have become increasingly popular over the last couple of years due to the rise of Software as a Service and the move towards delivering applications through the cloud. It is now much more common for an application to be web based rather than installed on a client’s physical hardware.

In this tutorial I’m going to walk you through setting up the core components to allow you to create multi-tenant applications in Laravel 4.

You might be thinking, “what has this got to do with building Cribbb?!”. You would be correct in thinking that multi-tenancy has no place in consumer social web applications. It would be extremely weird to build that type of application using a multi-tenant approach.

However I think this is an important topic that would be interesting to a lot of you building your own Software as a Service businesses. Seeing as though I’m doing this in Laravel it also makes sense to include it in this series.

So for one week only we will diverge from the path of building a social consumer application, and instead delve into the murky depths of Software as a Service.

How do Multi-Tenant applications work?

As I mentioned in the introduction to this post, multi-tenant applications are a single installation of an application that allows many different clients to access their data independently.

There are a few different approaches to managing the separation of data in multi-tenant applications.

Firstly, you could use a separate database for each client. This means the connection to the correct database is established on authentication. Using separate databases ensures that data from one client is never mixed with the data of another.

Secondly you could use a separate schema. This is where you use one database but the database is able to manage the separation of data through the schema. This basically means that every client has it’s own set of tables within the same database.

And thirdly, you could use a shared database with a shared schema where you select data from the database using a tenant_id field.

The choice between the three different approaches really comes down to what type of application you a building. If you are building a high security application for a regulated industry, it makes sense to have physical separation of data through isolated databases.

However if you are building a low cost Software as a Service application, you can sacrifice this security measure for lower cost infrastructure and overheads. Maintaining separate databases or even a separate schema will be significantly more costly to run and maintain.

My chosen approach is the shared database and shared schema application. I think the benefits of lower overheads and easier maintainability greatly outweigh the security implications for the majority of multi-tenant applications. However, if you were building software for banks, hospitals or the government you will probably want to use the separate database approach.

How do shared database multi-tenant applications work?

So now that I’ve established what are multi-tenant applications and what are the different application architecture choices you have, I’ll explain how my chosen approach works.

In a typical Software as a Service application a client will authenticate and then be presented with their unique dashboard filled with their data and none of the data from any of the other clients.

When the client authenticates our application will set a global context that will scope all database queries to only return data for that current client. This means that all the code of the application is unaffected because the data is scoped at the database.

There are usually many different ways to authenticate into an application. For example you could require client credentials for your application but also offer token based authentication via an API. In either case we want a solution that will work for any type of authentication so the database scoping logic is not repeated and therefore less prone to errors or bugs.

Creating the Context Interface

The first thing I need to do is to create the Context service that will act as the scope of the application. This is basically just a global object that will be injected into other aspects of the code (for example the Repositories) to restrict data to only the current client.

The Context is simply a wrapper around the entity object of the current client. However, because business rules often change over the life of an application, it’s important to make the context open for substitution.

To enable this substitution I will create an interface for the context like this:

<?php namespace Cribbb\Contexts;

use Illuminate\Database\Eloquent\Model;

interface Context {

  /**
   * Set the context
   *
   * @param Illuminate\Database\Eloquent\Model
   */
  public function set(Model $context);

  /**
   * Check to see if the context has been set
   *
   * @return boolean
   */
  public function has();

  /**
   * Get the context identifier
   *
   * @return integer
   */
  public function id();

  /**
   * Get the context column
   *
   * @return string
   */
  public function column();

  /**
   * Get the context table name
   *
   * @return string
   */
  public function table();

}

As you can see above, the client entity will be injected into the service. The methods on the service are available as a generic public API for working with the context in our application. This is important because, for example, if instead we need to substitute a $client entity for a $organisation entity, we would have to replace every instance of client_id for organisation_id.

Creating the Context implementation

Next I can create the specific Context implementation. In this case my context is a Client entity. The implementation is just a class that fulfils the requirements of the Context interface:

<?php namespace Cribbb\Contexts;

use Illuminate\Database\Eloquent\Model;

class ClientContext implements Context {

  /**
   * The current context
   *
   * @var Illuminate\Database\Eloquent\Model
   */
  protected $context;

  /**
   * Set the context
   *
   * @param Illuminate\Database\Eloquent\Model
   */
  public function set(Model $context)
  {
    $this->context = $context;
  }

  /**
   * Check to see if the context has been set
   *
   * @return boolean
   */
  public function has()
  {
    if($this->context) return true;

    return false;
  }

  /**
   * Get the context identifier
   *
   * @return integer
   */
  public function id()
  {
    return $this->context->id;
  }

  /**
   * Get the context column
   *
   * @return string
   */
  public function column()
  {
    return 'client_id';
  }

  /**
   * Get the context table name
   *
   * @return string
   */
  public function table()
  {
    return 'clients';
  }

}

The importance of the IoC container

Now that I’ve created the generic Context interface and a specific implementation for the Client model, I now need to bind the two together in Laravel’s IoC container so that when I call for an instance of Cribbb\Contexts\Context I will be returned an instance of Cribbb\Contexts\ClientContext. This is exactly the same as how the EloquentUserRepository is returned when you write UserRepository in your Controllers.

However, there is one more important detail. The Context object should be shared through the application as a single instance. When I set the context through the authentication, I want the same object to be available in the Repositories. To ensure that the same object is shared throughout the application for each request I will use the shared method of the IoC container.

Here is my ContextServiceProvider:

<?php namespace Cribbb\Contexts;

use Illuminate\Support\ServiceProvider;

class ContextServiceProvider extends ServiceProvider {

  /**
   * Register
   */
  public function register()
  {
    $this->app['context'] = $this->app->share(function($app)
    {
      return new ClientContext;
    });

    $this->app->bind('Cribbb\Contexts\Context', function($app)
    {
      return $app['context'];
    });
  }

}

Finally add the following line to your list of Service Providers in app/config/app.php:

'Cribbb\Contexts\ContextServiceProvider'

Authenticating and setting the context

Now that you have the global Context object in your application you can set the context when you authenticate the request by injecting the context and setting the database entity into the Context object.

This could be as simple as extending the inbuilt Laravel authentication filter to set the context, or creating your own API filter to authenticate requests based upon a token and a secret. The choice of how you implement authentication is really up to you.

For example, you could modify the existing Laravel auth filter like this:

Route::filter('auth', function()
{
  $context = App::make('Cribbb\Contexts\Context');

  if (Auth::guest()) return Redirect::guest('login');

  $context->set(Auth::user());
});

Here I’m resolving the context out of the IoC container and setting the user entity.

This is really open to work however you need it to work. If you authenticate against an organisation, just set the organisation entity instead of the user entity.

Creating the Tenant Repository

Now that the Context is set we can use it to scope the database requests in the Repositories. To do this I will create another Repository named TenantRepository which extends the base AbstractRepository.

For each repository that needs to be scoped per client I then extend the TenantRepository rather than the AbstractRepository directly. This enables my repositories to all inherit the basic blueprint, but the repositories that need to be scoped can inherit an extra layer of logic.

In the TenantRepository I create a couple of methods that wrap my basic building block methods to scope the query if applicable. A scoped relationship could either be any of the different types of database relationship. For example it could be based upon a foreign id column or it could be through a pivot table. In the majority of cases you will only need to scope through a column. I think it is rare that a multi-tenant application would have resources that are available to many clients. However I’ve included those methods in the example below to show you how you would achieve that using Laravel’s query builder.

I also don’t want to create different repositories for scoped access and not scoped access. For example, if I wanted to create a dashboard to monitor the application I don’t want to have to create a second set of repositories that weren’t scoped per client. You will notice that I check to see if the scope has been set to achieve this:

<?php namespace Cribbb\Repositories;

use StdClass;
use Illuminate\Database\Eloquent\Builder;

abstract class TenantRepository extends AbstractRepository {

  /**
   * Scope a query based upon a column name
   *
   * @param Illuminate\Database\Eloquent\Builder
   * @return Illuminate\Database\Eloquent\Builder
   */
  public function scopeColumn(Builder $model)
  {
    if($this->scope->has())
    {
      return $model->where($this->scope->column(), '=', $this->scope->id());
    }

    return $model;
  }

  /**
   * Scope the query based upon a relationship
   *
   * @param Illuminate\Database\Eloquent\Builder
   * @return Illuminate\Database\Eloquent\Builder
   */
  public function scopeRelationship(Builder $model)
  {
    if($this->scope->has())
    {
      return $model->whereHas($this->scope->table(), function($q)
      {
        $q->where($this->scope->column(), '=', $this->scope->id());
      });
    }

    return $model;
  }

  /**
   * Retrieve all entities through a scoped column
   *
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function allThroughColumn(array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeColumn($entity)->get();
  }

  /**
   * Retrieve all entities through a scoped relationship
   *
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function allThroughRelationship(array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeRelationship($entity)->get();
  }

  /**
   * Find a single entity through a scoped column
   *
   * @param int $id
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function findThroughColumn($id, array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeColumn($entity)->find($id);
  }

  /**
   * Find a single entity through a scoped relationship
   *
   * @param int $id
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function findThroughRelationship($id, array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeRelationship($entity)->find($id);
  }

  /**
  * Get Results by Page through scoped column
  *
  * @param int $page
  * @param int $limit
  * @param array $with
  * @return StdClass Object with $items and $totalItems for pagination
  */
  public function getByPageThroughColumn($page = 1, $limit = 10, $with = array())
  {
    $result             = new StdClass;
    $result->page       = $page;
    $result->limit      = $limit;
    $result->totalItems = 0;
    $result->items      = array();

    $query = $this->scopeColumn($this->make($with));

    $users = $query->skip($limit * ($page - 1))
                   ->take($limit)
                   ->get();

    $result->totalItems = $this->model->count();
    $result->items      = $users->all();

    return $result;
  }

  /**
  * Get Results by Page through scoped relationship
  *
  * @param int $page
  * @param int $limit
  * @param array $with
  * @return StdClass Object with $items and $totalItems for pagination
  */
  public function getByPageThroughRelationship($page = 1, $limit = 10, $with = array())
  {
    $result             = new StdClass;
    $result->page       = $page;
    $result->limit      = $limit;
    $result->totalItems = 0;
    $result->items      = array();

    $query = $this->scopeRelationship($this->make($with));

    $users = $query->skip($limit * ($page - 1))
                   ->take($limit)
                   ->get();

    $result->totalItems = $this->model->count();
    $result->items      = $users->all();

    return $result;
  }

  /**
   * Search for a single result by key and value through a scoped column
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function getFirstByThroughColumn($key, $value, array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeColumn($entity)->where($key, '=', $value)->first();
  }

  /**
   * Search for a single result by key and value through a scoped relationship
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function getFirstByThroughRelationship($key, $value, array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeRelationship($entity)->where($key, '=', $value)->first();
  }

  /**
   * Search for many results by key and value through a scoped column
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function getManyByThroughColumn($key, $value, array $with = array())
  {
    $entity = $this->make($with);
    
    return $this->scopeColumn($entity)->where($key, '=', $value)->get();
  }

  /**
   * Search for many results by key and value through a scoped relationship
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function getManyByThroughRelationship($key, $value, array $with = array())
  {
    $entity = $this->make($with);

    return $this->scopeRelationship($entity)->where($key, '=', $value)->get();
  }

}

Extending the Tenant Repository

Now that I’ve got the abstract TenantRepository set up I can extend it to create my individual repositories.

For example, if this was an application like Basecamp I might have a ProjectRepository that was scoped through a column. My repository might look something like this:

<?php namespace Cribbb\Repositories\Project;

use Cribbb\Contexts\Context;
use Illuminate\Database\Eloquent\Model;
use Cribbb\Repositories\TenantRepository;

class EloquentProjectRepository extends TenantRepository implements ProjectRepository {

  /**
   * @var Model
   */
  protected $model;

  /**
   * @var Context
   */
  protected $scope;

  /**
   * Construct
   *
   * @param Cribbb\Contexts\Context $scope
   * @param Illuminate\Database\Eloquent\Model $model
   */
  public function __construct(Model $model, Context $scope)
  {
    $this->model = $model;
    $this->scope = $scope;
  }

  /**
   * Return all projects
   *
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function all(array $with = array())
  {
    return $this->allThroughColumn($with);
  }

  /**
   * Return a single project
   *
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function find($id, array $with = array())
  {
    return $this->findThroughColumn($id, $with);
  }

 /**
  * Get Results by Page
  *
  * @param int $page
  * @param int $limit
  * @param array $with
  * @return StdClass Object with $items and $totalItems for pagination
  */
  public function getByPage($page = 1, $limit = 10, $with = array())
  {
    return $this->getByPageThroughColumn($page, $limit, $with);
  }

  /**
   * Search for a single result by key and value
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Model
   */
  public function getFirstBy($key, $value, array $with = array())
  {
    return $this->getFirstByThroughColumn($key, $value, $with);
  }

  /**
   * Search for many results by key and value
   *
   * @param string $key
   * @param mixed $value
   * @param array $with
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function getManyBy($key, $value, array $with = array())
  {
    return $this->getManyByThroughColumn($key, $value, $with);
  }

}

Testing the context

Now that you have set up everything, we can do a dirty little test to ensure everything is working correctly. First seed your database with enough data to clearly show that your tests are working correctly. By this I mean a couple of clients and lots of projects.

Next create a new test route you can hit and copy the following code:

Route::get('/test', function()
{
  $client = Client::find(1);
  $context = App::make('Cribbb\Contexts\Context');
  $context->set($client);

  $repository = App::make('Cribbb\Repositories\Project\ProjectRepository');

  $projects = $repository->all();

  foreach($projects as $project)
  {
    var_dump($project->title);
  }
});

In the example above I’m grabbing the first client and resolving the Context out of the IoC container. Next I resolve the ProjectRepository out of the IoC container and call the all method to select all the available projects.

Now when you hit the test route in the browser you should see a list of available projects. However, when you change which client you select from the database and hit the route again the project list will automatically change.

Congratulations! You’ve just set up automatically scoping repositories for your new Software as a Service application!

Conclusion

Phew! We covered a lot in this tutorial. Hopefully if you’ve been following a long with this series you will see that the individual building blocks of what I’ve implemented in this tutorial have mostly been covered in previous tutorials. It just goes to show that if we learn and understand the individual building blocks, we can build some pretty powerful applications.

In this tutorial we’ve covered a couple of interesting things. Firstly, creating a Context that wraps an existing object and provides an easily accessible public API. This approach makes it really easy to substitute how you want to scope your application if the business rules of your company change in the future.

Secondly we looked at setting the scope through authentication. There are many different ways to authenticate an application. You now have the basic simple building blocks for authenticating and setting a global scope that can be used through the application.

And thirdly I showed you how to add another layer of inheritance to add scoping logic to the repositories in your application that need to be scoped. This can optionally be applied to whichever repositories that you want to be scoped, but also offers you the flexibility to ignore the scope or to scope different types of database relationships.

As I mentioned at the top of this post, this multi-tenant architecture has no place in Cribbb. However hopefully you can use this as a building block or inspiration to build your own multi-tenant applications for your company or for your clients.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

To view a full listing of the tutorials in this series, click here.

Philip Brown

Hey, I'm Philip Brown, a designer and developer from Durham, England. I create websites and web based applications from the ground up. In 2011 I founded a company called Yellow Flag. If you want to find out more about me, you can follow me on Twitter or Google Plus.

Join the Culttt

Become an insider and join the Culttt

  • http://www.dixens.net/ Dixens

    … and another excellent post + thanx, have to remake my multi-tenancy Laravel app from scratch now :-)

    • http://culttt.com/ Philip Brown

      Haha, thank you :) there’s nothing better than a clean slate :)

  • Parsec

    This is exactly what i was looking for. Thanks a bunch!

    • http://culttt.com/ Philip Brown

      No problem, glad it helped you out!

  • http://www.papertrail.io Rob Walker

    Hi Philip. Great post. I think ‘CribbbContextsContext’ in app.php should be ‘CribbbContextsContextServiceProvider’, unless I’ve got the wrong end of the stick.

    Also, do you have an example of how the context can be set from in an auth filter?

    • http://culttt.com/ Philip Brown

      Where abouts is that?, I’m not sure I know what you mean.

      You would basically just do $context->set(Auth::user());. You just need to set the current user entity on the context.

      I’ll add a proper example in to the tutorial for clarity.

      • http://www.shaneperera.com Shane Perera

        I think Rob might be right. The service provider line on app.php should probably be ‘CribbbContextsContextServiceProvider’.

        Great article BTW!

        • http://culttt.com/ Philip Brown

          Ah indeed it should be! Thank you Shane, I’ve fixed that.

          Thank you :)

  • http://www.bentomobility.com Nathaniel Johnson

    Excellent post. This type of approach will dramatically clean up my multi-tenant logic in my app. Never thought of doing it this way. Thanks!

    • http://culttt.com/ Philip Brown

      No problem Nathaniel, glad it helped you out :)

  • Bouchaala Reda

    That’s awesome dude!
    Thank you for these amazing articles, great job!

    • http://culttt.com/ Philip Brown

      Thank you :)

  • Eduardo WB

    This post came in good time as we will build a Software as Service application. Nice approach to solve the multi-user problem. Thank you!

    • http://culttt.com/ Philip Brown

      Thank you Eduardo :)

  • Arnie

    Sorry, but how do you bind the EloquentProjectRepo?
    return new EloquentProjectRepository( new Project, $app[‘context’]
    gives:
    undefined property: EloquentProjectRepository::$scope

    I’m missing a clue somewhere.

    • http://culttt.com/ Philip Brown

      What code are you using to trigger that error?

      • Arnie

        I’m using the same test route as you do. And then the following in a service.
        public function register()
        {
        $this->app->bind(‘ProjectRepositoriesProjectRepository’, function($app)
        {
        return new EloquentProjectRepository( new Project, $app[‘context’] );
        });
        }
        think i got an huge misunderstanding and have to read a lot, thanx for your posts!

        • http://culttt.com/ Philip Brown

          Put your code up on Github and I’ll take a look

          • Arnie
          • http://culttt.com/ Philip Brown

            It might be because you haven’t registered your services providers in app/config/app.php

          • Björn

            I ran into the same problem.
            Seems like you mixed some variable names, as $this->scope should actually be $this->context.

          • http://culttt.com/ Philip Brown

            Ahhh you’re right I did!

            That should be fixed now.

          • JamesDiGioia

            I’m still seeing $this->scope in your blog. Otherwise, this blog post has been incredible for helping me understand what I should be doing in the repositories, as I was putting a lot of the database querying code into the models themselves.

          • http://culttt.com/ Philip Brown

            Thanks James. Confusingly the scope is a Context but the class property in the repository is $this->scope.

            I think I’ve made changes to fix the problem (although admittedly it is still confusing) but can you see an area where I haven’t?

          • JamesDiGioia

            Ah, that’s where I was confused following this: The constructor is (…, Context $scope). In other words, you use the Context to scope the queries.

          • http://culttt.com/ Philip Brown

            Yeah that’s right :)

  • Arnie

    I’m using the same test route as you do. And then the following in a service.
    public function register()
    {
    $this->app->bind(‘ProjectRepositoriesProjectRepository’, function($app)
    {
    return new EloquentProjectRepository( new Project, $app[‘context’] );
    });
    }
    think i got an huge misunderstanding and have to read a lot, thanx for your posts!

  • Diego Felix

    Nice Philip. I see you updated the code to show how to add the context. I was thinking about an event listening on auth.login, What do you think?

    • http://culttt.com/ Philip Brown

      Hmm, yeah if that works for you? Hopefully this is a pretty flexible setup that allows you to make it work in a number of different scenarios :)

  • carbonvader

    Easy and solid way for multi-tenancy question. Thanks Philip.

    How would you code if you choose the first method, using a separate database for each client?

    • http://culttt.com/ Philip Brown

      Hmm, I’m not sure, I’ve never set up switching databases per client. I guess you could use a similar method, but set the database config at runtime, rather than scoping the repository.

  • huzzi

    Can’t find the context code in the github

    • http://culttt.com/ Philip Brown

      Yeah, this isn’t part of Cribbb so it won’t be on GitHub

      • OzPritchA

        Just trying to get my head around ioc and some of the concepts you introduce here.
        Do you have this code anywhere, or can you comment the file path/name into the code above?

        • http://culttt.com/ Philip Brown

          All the file names are just the name of the class under the directory structure of the namespace.

          Which bit are you stuck on?

  • karan

    good series you have here. I suggest you post the link to your git repo before and after each post you do here. That way it would be easier to follow this series if someone is going to start from the beginning.

    • http://culttt.com/ Philip Brown

      Thank you :)

      Hmm, I’m not sure based on your description. It might be the case that you don’t need to scope that model, rather, it should just be a relationship to your project model.

      • ka

        yes a relationship which is one level up. A collaborator belong to project..which belongs to the user.

        So if we want to get all collaborators, the query needs to be somehow scoped to the user. But they are not DIRECTLY in a relationship with the user.

        the collaborators are not users. Maybe my example is bad.
        I can give another example to explain what I mean.
        Thanks for your help!

        • http://culttt.com/ Philip Brown

          Hmm, I’m not sure if I’m understanding you correctly, but that should just work through the relationship because you only need to scope to the current user.

          • ka

            A new example is here https://gist.github.com/karanits/dcb601a1ebc34deaace8

            Want some input on what would be the best course of action.
            Thanks

          • http://culttt.com/ Philip Brown

            Do the scopeThroughRelationship methods not do what you want to do?

          • ka

            I dont think so. I dont see how the method would be used. An example maybe? :)

            If time permits, you may make one ‘all’ method for the client repo seeing the gist. What it should return is all clients ‘indirectly’ related to the context company

          • http://culttt.com/ Philip Brown

            Actually can you not just do a has many through relationship? http://laravel.com/docs/eloquent#has-many-through

          • ka

            I figured it out. I need to set the table in context class dynamically, for relationships to work. It was my bad I guess. :) thanks

          • http://culttt.com/ Philip Brown

            Ah interesting. Well, glad you got it sorted :)

          • mcanaves

            Hi ka, I’m in the same situation. Could you show me your soution?. Thanks :)

  • LearningLaravel

    Great tutorial! Never thought of doing it this way, will definitely use this for my next SaaS project.

    Can I make a tutorial request? I’m not sure how much experience you have with it, but it’d be great to get a tutorial about benchmarking and testing for scale. In other words, how to use Laravel to seed potentially millions of database records and emulate traffic the way it may come in through a sudden spike in traffic, and make sure there are no slow queries, non-cached rea, or other soft/vulnerable spots that bring down your site. And all of this on a staging server/cloud. Maybe using Apache Benchmark?

    It’s fairly easy to make unit and functional tests and make them pass, but the real test would be how it would work with 1000+ concurrent connections per second. That would mean making sure you have the proper indexes and stuff. Obviously the server/cloud you’re using will make a difference, but that doesn’t change the fact that your code should be written in a way that’s ready for it.

    Thanks, and keep it up!

    • http://culttt.com/ Philip Brown

      Thanks :)

      Yeah I’m probably not the best person to write a tutorial like as I’ve never really set up any kind of benchmarking to test that kind of traffic.

      To be honest I’m more of a “I’ll cross that bridge when I come to it” kind of person. I don’t mind have code that does the job today, but at some point in the future might need to be thrown out. I don’t think you can really build a website for that kind of scale from day one. I think if I’m lucky enough to get to that point, then it’s a nice problem to have.

      I wouldn’t worry about it too much. As long as you aren’t doing anything drastically wrong, I don’t think it’s that big of a concern.

  • Continuum81

    I’ve taken a look at both the Vagrant article as well as this one, and would like to ask whether you would consider writing an amendment (or commenting here) – to discuss the technical server aspects of getting a multi-tenancy site, using wildcard DNS (*.example.com), to work during provisioning, on a precise32 box.
    Thanks in advance !

    • http://culttt.com/ Philip Brown

      Hmm, well I guess all you would need to do would be to set up your vhost on the vagrant box to accept wildcards.

  • Nicolas LA

    Very useful post!
    I might be mistaken here, but using your context/scope column approach, would that mean that you’ll have to have this column (client_id here) on all the tables?
    Cheers

    • http://culttt.com/ Philip Brown

      Yeah all the tables that you want to scope need to have a client_id on them.

      • Cardy

        I really appreciate your post!
        Just a question: What would you do if a relation between a model and the client is a many to many relationship?
        Bests

        • http://culttt.com/ Philip Brown

          You would just use one of the “through relationship” methods.

  • edward

    Hi Phillip, This is wonderful. Thank you so much for posting.

    I was wondering if there would be a method for setting the $client_id to the current context id while storing records. I am pretty new at all of this, maybe that doesn’t make sense. Would you set the client_id field manually each time you stored a related entity?

    • http://culttt.com/ Philip Brown

      No problem, glad you liked it :)

      You could do that, but to be honest I would just set it whenever your store the related entity. It’s easy enough to do with Eloquent because Eloquent will automatically read the id from the model instance.

  • lainga9

    thanks for the great tutorial. i’ve developed a few multi-tenant systems before now and they’ve all been littered with endless if statements when retrieving models – this should help clean things up!

    i’m trying to follow through the tutorial but i’ve noticed that the EloquentProjectRepository class implements ProjectRepository… do you have the code for the ProjectRepository class?

    apologies if i’m missing something obvious!

    • http://culttt.com/ Philip Brown

      Thank you :)

      Well it was just an example really, so your ProjectRepository would just include any methods that you want to ensure would appear on the repository. It could be a blank interface if all of the methods are already covered by other interfaces.

      Does that make sense?

      • lainga9

        Ah rite ok yeah makes sense thanks!

        Just one more question about the system I’m currently building if that’s alright..

        It’s basically a price comparsion sort of system. Customers give some details and are provided with a list of quotes from different partners. They can then select a quote etc etc.

        So when a customer logs in they’ll only see quotes specific to them i.e. the column to filter by would be user_id.

        However, when a partner admin logs in (there can be several users for each partner) they should be able to see all quotes for which a customer has selected them i.e. the column to filter by would be partner_id.

        To make things even more complicated there are different roles so a super admin can login and view all quotes.

        Would this be easy enough to implement? I’m happy to try and figure it out myself but just want to make sure it’s possible before I to lead myself down a blind alley!

        • http://culttt.com/ Philip Brown

          Yeah I see what you are getting at.

          Using the basic multi-tenancy approach is probably not going to work well in that situation. I think multi-tenancy works great for applications like Basecamp, but it breaks down in the situation that you described where there are many different types ways the data needs to be accessed.

          It’s hard for me to recommend “the right” approach without a deep understanding of the domain that you are tackling. I wouldn’t get too hung up on trying to fit your model into the traditional multi-tenancy approach though.

  • Danny Dinges

    Getting the following error:

    Call to undefined method ProjectRepositoryContactEloquentContactRepository::make()

    Any ideas?

    • Danny Dinges

      Got it. Didn’t realize it was in Cribbb. Great article Philip!

      • http://culttt.com/ Philip Brown

        Thanks Danny :)

  • John Hamman

    Above and beyond outstanding series! I have learned so much…
    So my question is: instead of a client_id, I have an account_id with an account holding multiple users (many users to one account). And so I would like to put the account ID in the url as in domain.com/123456/ and use that to identify the scope. How do you suggest I go about doing that after handling the context in auth?

    • http://culttt.com/ Philip Brown

      Thanks John :D

      I normally create a filter and attach it to the routes I want to set the scope on. The filter will just take the id from the URL, and find the entity from the database and then set the scope.

      I’ve only ever done that using subdomains, but it will work in the exact same way for url params.

      Something like this perhaps…

      Route::filter('auth.scope', function($route, $request)
      {
      $repository = App::make('your repository');
      $id = Request::segment(1);
      $entity = $repository->find($id)
      if(! $entity)
      {
      return Redirect::route('login');
      }
      $scope = App::make('your scope');
      $scope->set($entity);
      });

      • John Hamman

        Amazing, thanks!

  • Paul Spiteri

    Hi. Interesting post. This is such a grey area in Laravel – the Rails community is on top of this (not I am not a Rails person, just an interested observer) … perhaps the Laravel discussion will evolve this way.

    Can I ask why you chose this approach rather than Taylor O’s approach, shown here (http://blog.userscape.com/post/organizing-snappy)?

    Back to the Rails comparison – there seems to be two broad schools of thought in the Rails community on how to implement multi tenancy. One is to implement global filters in the database model. Laravel supports this, but I reckon it has bugs (I have a post on this on Laracasts somewhere). The other is to use Postgres’ schema, using ‘before’ filters to change the schema used to the tenant name. Any thoughts as to why we don’t discuss either of these two methods in the Laravel community (I discussed the former on Laracasts and got shot down, somewhat)?

    I really like how you stated that sometimes shared DB / schema is best, sometimes it is not. I stumbled across an approach that seems to combine the best of both worlds (http://web.archive.org/web/20120211232457/http://www.reachcrm.com/2010/03/11/multi-tenant-strategy-for-saas-using-mysql5) –

    1. You declare all your base tables to have a Tenant ID column.

    2. You declare an updateable view for every base table which excludes the Tenant ID, but uses a where clause to equate the Tenant ID to the current system userid.

    3. You create a trigger for every base table that sets the Tenant ID on insert to the current system Userid.

    4. All application code uses the views to access the database. They should never use the base tables.

    Having said that, I am struggling to implement this approach, mainly because I don’t know how to pass a password for a user account to the ‘before’ filter, but also because I am not certain exactly how the ‘before’ filter works – I mean, if two users hit a multi-tenanted site within milliseconds of each other is there a chance they will inherit the same user account name and password – as set by the ‘before’ filter – and thus break the separation of data? Of course, this may (or may not) be a problem with the Postgres schema approach above, but at least you could code to test on the fly to make sure the current user is a member of the current tenant and to fail if they are not.

    Anyway, I’d be interested in your thoughts seeing as you have clearly given the topic a lot of thought. And if anybody should have any feedback about my concerns and questions about filters, above, I’d appreciate your thoughts as well.

    • http://culttt.com/ Philip Brown

      Thank you Paul :)

      Yeah there’s not much covered for this in Laravel, I couldn’t find anything when I searched so I decided I would write something up. I think over time we’ll see more of this technique popping up in Laravel, Rails has been around for quite a while now.

      That blog post from Taylor was actually the inspiration for how I thought about implementing this myself, so I don’t think it’s too dissimilar from what I could assume from the code snippets. How did you interpret that post which makes you think that my implementation is much different?

      To be honest I’ve only ever used the scoped model approach. I’m not really a fan of the using separate tables or a separate database so I’m probably not the best person to ask. I’m not sure why you would be shot down though? Again I think it might just be because there are so few resources on setting up a multi-tenant application in Laravel.

      • Paul Spiteri

        Now … I am pretty average at this game so if I tell you what I read in your code … well, it might not be particularly insightful. But as I read Taylor’s code he seems to provide almost a utility or helper to be sprinkled throughout controllers and repositories and used as is, to be tacked onto the end of all model references to limit the data returned.

        Now as I read your code – and again, I really don’t know much at all – it looks like a series of predefined scenarios which are used to return the scoped data required by the application.

        At its simplest, I can see how Taylor’s code can be implemented in a few lines of code because of what I have (sloppily) termed this utility / helper approach. And that, for me, is the difference with your code. Of course, using Taylor’s approach and attaching it to the end of each database query … well, leaving it off accidentally would compromise data separation.

        Clearly, I am open to being corrected, so please feel free to do so.

        On using the scoped model approach – I’m with you on this one. I really don’t want to use a separate schema approach – I am already dreading writing a custom artisan command to maintain it – but the app I am looking at has pretty sensitive data and I am not sure I’ll sleep well scoping the data in my code.

        That’s the reason I love the idea of the database scoping the data using a tenant login / password approach because the single schema is maintained but a code error – heaven forbid – results in no data being returned, rather than all data being returned. But, again, I am struggling to implement it because I don’t know how to pass a password for a user account to the ‘before’ filter.

        On Taylor’s approach, I wonder why he didn’t scope it in the data model using a scoped query. Again, that would be a very Rails way of doing it.

        Re the shoot down … meh … haters gonna hate. Most others I have been sharing my thoughts with – including Fidel and yourself – have been open minded, which I appreciate.

        • http://culttt.com/ Philip Brown

          Ah right, I see what you mean with the difference between mine and Taylor’s approach. I think you are right in thinking that Taylor’s approach is simply a helper method that is available within an Abstract Repository to scope queries. Mine is based upon that exact same theory, but I’ve created specific methods to scope via different methods (either through a column or a relationship) and the ability to ignore the scoping so an admin could see all results. The underlying principle is exactly the same though. So if you didn’t need to scope via a relationship and you didn’t need to not scope sometimes, Taylor’s approach would be better because it’s much simpler.

          Yeah I see what you mean with the sensitive data issue. Sometimes there is just no getting away from separating data, especially if it is a legal requirement.

          I’d say you are probably best off using a tried and trusted approach. I can’t think of any open implementations of this approach in Laravel, so you might end up having to invent a lot of the stuff you will need. You might be better off doing it in Rails where there would probably be gems that solve a lot of your problems, and blog posts or conference talks from people who have implemented it already.

  • JamesDiGioia

    How would one go about testing a repository like to to make sure it’s scoping the queries correctly? Also, do you test the AbstractRepository and/or TenantRepository, or just the implementation EloquentProjectRepository?

    • http://culttt.com/ Philip Brown

      The easiest way would be to just seed the database and then assert that you are returned the correct results. I would create a stub repository that extends the abstract classes and test that.

      • JamesDiGioia

        Ah, right, because we can’t really mock the DB calls, since that’s specifcally what we’re testing :). Does this fall more under integration testing where we’d seed a DB and run through all the steps we expect a user to go through?

        Another question: so this scopes the queries when we pull from the DB, but what about saving? Do you just go the old fashioned way: `Auth::user()->projects()->save($project)` or is do you scope through the Tenant repository for saving as well? It doesn’t look like I can do this with the Builder model though, which is what the Tenant repository is based on.

        • http://culttt.com/ Philip Brown

          Yeah that’s integration testing.

          Yeah you would just save in the normal way by associating the record through the relationship method.

      • JamesDiGioia

        Any tips on using dependency injection for Abstract/Tenant repositories? When we did that with the controller testing, we called the controller via $this->get() in the tests, but for these repositories need to instantiated? I think? Am I doing this right?

        class AbstractRepositoryStub extends AbstractRepository {}

        class AbstractRepositoryTest extends TestCase {

        public function setUp() {

        parent::setUp();

        $this->model = $this->mock(‘IlluminateDatabaseEloquentModel’);

        $this->scope = $this->mock(‘MyappContextsContext’);

        $this->errors = $this->mock(‘IlluminateSupportMessageBag’);

        }

        public function testMake() {

        $this->model->shouldReceive(‘with’);

        $testRepo = new AbstractRepositoryStub();

        $entity = $testRepo->make();

        $this->assertInstanceOf(‘Model’, $entity);

        }

        public function tearDown() {

        Mockery::close();

        }

        }

        When I do the instantiation, getting an error saying I need to pass in MessageBag into the parent __construct().

        Thanks for your help!

        • http://culttt.com/ Philip Brown

          I would just create an instance of the repository and run the tests on the methods. You don’t need to mock everything.

          To be honest I would just seed the database or create fixtures and see if the returned results were correct. You don’t need to try and test every individual method because you will just end up mocking everything and ultimately writing tests that are brittle and don’t actually prove if the thing is working or not.

          • JamesDiGioia

            Ah ok – sounds like I’m making this more complicated than it needs to be. Thanks for your help (and your patience :) ).

  • Danny Dinges

    Anyone have any guidance on going down the road of custom domains for clients under this model? For example if I give every user a subdomain what would be the best method for allow them to set a DNS record on their own domain to be masked back to the SaaS app subdomain? CNAME record on their end but any ideas on how to handle it on the server side?

    • http://culttt.com/ Philip Brown

      Hmm, I’m not sure actually. I guess you could just set the scope in the same way as you would for a subdomain, but you are using the whole domain instead.

    • Justin

      A wildcard vhost/nginx configuration is what you’re looking for. So that your web server/app responds to ANY domain trying to access it and then setting which tenant/customer it is. Hopefully that helps. :)

  • redmoon7777

    How about extending the database connexion, then overriding the select/insert/update/delete methods so that they add a where clause for tenant_id (or populate the tenant_id field in case of insertion) ? Would love to hear your thoughts on this approach.

    • http://culttt.com/ Philip Brown

      Well that’s kinda like I’m doing for the select method. I’m not really a fan of doing it for the insert, update and delete methods though.

  • Fasil K K Cholakkara

    This is what im looking for.Thank you very much..

    Why cant put a sample application in github? + a demo. All can use this as a base for their simple SAAS applicatins.

    • http://culttt.com/ Philip Brown

      Hmm, I guess I could. Everything you need is in this post though :)

      • http://JhauraWachsman.com/ Jhaura Wachsman

        Nice post :-) I’d like to see the schema or migrations. I think that would be super helpful.

        • http://culttt.com/ Philip Brown

          Hmm, well it would just be the normal schema and migrations but with a tenant_id, nothing complicated :)

      • Fasil K K Cholakkara

        Trouble in username validation.:( I have to keep it like unique username for whole application.(Cant do for each tenancy in shared db)

        • http://culttt.com/ Philip Brown

          Hmm, yeah you would have to either write your own validation rule (could be tricky) or implement it as a separate check in your code.

          • Fasil K K Cholakkara

            I cant do uniqueness based on tenant. Because there is a chance for getting same username in other tenancy. Accidentally if those 2 users(same username) used same password, then the big issue will get while login. The only solutions is i think is to pass Tenant id also, while login.

          • http://culttt.com/ Philip Brown

            Well when you authenticate users you need to be checking to ensure that they are a member of the current tenant. You wouldn’t want to allow a user to sign into to another customer’s account.

          • bmadigan

            I thought of this as well, but I think if you used subdomains for each tenant that may help

  • Wakabish

    I love you guy, awesome. But could you provide quick example how to handle Model Save with the context?

    • http://culttt.com/ Philip Brown

      You would just save normally using the Auth::user() or however you are defining the current user to save against.

  • http://buzzknow.com Buzzknow

    Hello Philip,

    now i’m able to create each DB connection for each tenant, but how to use it with Cache ? so every tenant has each cache instance, even with same key, will receieved different value of cache

    Thanks

    • http://culttt.com/ Philip Brown

      It would be difficult to explain an entire caching strategy in the body of a comment, but you would basically just have to use the tenant id as part of the cache key.

  • Mike Fuller

    Thank you for post, very helpful. However, I just started working with Laravel so pardon me if this is a stupid question. Does Laravel’s global scopes http://laravel.com/docs/eloquent#global-scopes make some of this code unnecessary?

    Thanks

    • http://culttt.com/ Philip Brown

      Thanks Mike :)

      Hmm, I’m not 100% sure. I know that global scopes are used for soft deletes, but I haven’t really looked into them too much to be honest.

  • Gayan Hewa

    Hey nice post , how do u manage the migrations for a single code base multiple database ( Not a single database , multiple schema – since that is not applicable to this use case ) scenario ? My only option at hand is to trigger migrations on each database connection via a custom command but im all ears for suggestions

    • http://culttt.com/ Philip Brown

      Thank you :)

      Yeah I think that is probably the best option. I guess you could write a command to iterate through a list of connections and run the migrations on each.

  • John Carter

    Please Please Please Please Please Please with a watermelon and cherry on top of the watermelon, create a tutorial that dives into multi-tenancy for multi databases! I want to use Laravel fluently with Multitenancy with testing, etc. I will pray that God makes you live the best life every if you do this!!!

    • http://culttt.com/ Philip Brown

      Hi John, sorry no plans to write tutorial(s) on multi tenancy with multiple databases. Perhaps in the future.

  • Pingback: Working with Doctrine 2 Filters | Culttt()

  • Daniel Bethel

    Where is the AbstractRepository that the tenant repo extends?

  • Uziel Bueno

    Hi Philip Brown, i wrote a solution to this based in the knowledge you leaved here. Here is the link

    https://laracasts.com/discuss/channels/general-discussion/hitting-multiple-databases-dinamically-with-laravel/replies/1785

    • http://culttt.com/ Philip Brown

      Wow, that looks great! Excellent work sir :)

  • ZaL

    would this design approach work fine and and well for a multi regional website ? like serving the posts made by country A user only visible to the users of the same country ..

    • http://culttt.com/ Philip Brown

      Yeah you could use the same logic, where a country would be your “tenant” :)

  • nkconnor

    Great post but I’m a bit confused about things. How does the ProjectRepository know to use the Project model?

    I get a BindingResolutionException: Target [IlluminateDatabaseEloquentModel] is not instantiable running this as is

    • nkconnor

      Figured all the out.. now struggling to realize how the Repositories get the Context

      • http://culttt.com/ Philip Brown

        You need to inject both the Project Eloquent instance and the Context through a Service Provider.

  • vince

    I really must have missed something big in this tutorial. I do not understand all the complexity when all you want to do is isolate the data that belongs to a specific tenant based on their login credentials. What is meant by “Context” – do you mean the id of the user and their data when you might have 200 people all called John Smith ? I got thoroughly lost on this tutorial. Regardless I would really, really like to be put straight as this is a very important topic for me. Thanks !!

    • http://culttt.com/ Philip Brown

      Yes the “Context” is the current authenticated User / Organisation.

      By using this approach you scope the data at the data access layer so only data for the current Context is returned. This means you don’t have to have any logic in any of your Controllers or Services for restricting data retrieval.

      • vince

        ok got it. Can you give me a bullited list of things I should learn in order to understand this topic to it’s best. Thanks !

        • http://culttt.com/ Philip Brown

          Hmm, well everything you need is in this blog post.

          I would take a look at this talk https://www.youtube.com/watch?v=OaiQ7Piogmk

          The code is Ruby, but it’s the exact same principle.

          • vince

            heah Philip, that’s great . I am watching it already. However, as I am a Laravel Newbie. I have already done a lot of studying on Laravel, however, as a best and braces, can you please give be a list of core **Laravel** technologies I need to understand well in order to get the best out of your article.

            thanks !

          • http://culttt.com/ Philip Brown

            Well there isn’t anything that is specific to Laravel really. I guess you just need to understand that the Context is set during authentication. You then resolve the Context from the IoC container and inject it into the Repository. The Repository then scopes all database queries so you don’t have to deal with that logic in your Controllers or Services.

  • Ben Cavens

    A pure eloquent way of limiting scope to a tenant (account) can be done by checking the existence of an account relationship and if the current tenant/account matches these relationships. Something like:

    if($model->account and $model->account->id == $account->id){ return $model; }

    if($model->accounts and null != $model->accounts()->where(‘id’,$account->id)->first() ) return $model;

    return null;

  • jamie cawley

    When utilizing the two getByPageThrough functions should they be using the model directly to get the counts

    $result->totalItems = $this->model->count();

    This is returning an unscoped count for me, but maybe I missed something…

    Thanks!

    • http://culttt.com/ Philip Brown

      Ah indeed! That is my mistake. Yes the model should be scoped through the column first!

  • emini

    This is awesome! Thank you very much Philip!

    • http://culttt.com/ Philip Brown

      No problem Emini :) Glad you found it useful! :D

  • Wanny Miarelli

    Thanks for the article.
    How can i query without the scope ?

    There is a ” has() ” function that chekc the scope like this
    if($this->context)

    but how can i set the scope to null ? there is only a function set that accept a Model instance.

    • http://culttt.com/ Philip Brown

      By default the scope will be null, so all you have to do is not set the scope. So for example, the scope will be set through a subdomain, but for an admin panel, you just wouldn’t set the scope.

      • Wanny Miarelli

        Got it. I set the scope inside the Auth filter. In this case once a user log in he is scoped inside his context.
        Actually i need to use some shared tables between users ( with a context set ).

        In this case i have to set the scope to null for the specific query to get shared data not binded to any user.

        I coded a simple function to do this, but i wonder if there is a better way or if i miss something.

        Excuse me for typo.

        Thanks a lot for your time btw.

        • http://culttt.com/ Philip Brown

          Ah I see. Well you don’t have to scope every time you access the database. In that case you could simply use a Repository that isn’t scoped, rather than trying to unscope a scoped repository.

          • Wanny Miarelli

            This is what i actually did.
            Thanks a lot for your time, you really helped my team to get out of some structural problems !

          • http://culttt.com/ Philip Brown

            Good stuff Wanny :D Glad you and your team found it useful :)

  • Sanket Sahu

    Great Article there! It helped me a lot. I did a quick hack, instead of overloading all the functions, I just overloaded the newQuery() function.

    • http://culttt.com/ Philip Brown

      Thanks Sanket :) Glad you liked it :)

Supported by