Tenancies

Tenancies are a combination of things, they're core concepts within Sprout that encapsulate the logic behind having a current tenant, and it's also a term to refer to the state of there being a current tenant.

Introduction

The terms "tenancy" and "tenancies" have two meanings within Sprout.

  1. A tenancy is the state. If you're accessing the application as a particular tenant, say using their subdomain, you are within their tenancy.
  2. A tenancy, or Tenancy is an object that represents the configuration for a particular type of tenant.

For this particular section of the documentation, we're focusing more on the second definition, dealing with a configured tenancy. These tenancies are responsible for keeping track of things like the current tenant, the tenant provider to use, and even how the current tenant was resolved.

Sprout supports multiple different tenancies, which means you can a multitenanted application with multiple types of tenants, either entirely separate from one another or nested. Have a tenant that itself has multiple child tenants is definitely a rare use case, but it is supported nonetheless.

How they work

Every entry in the tenancies section of the multitenancy config is a tenancy, and will be represented by an object that implements the Sprout\Contracts\Tenancy interface. These instances have a single responsibility, and it's to keep track of the tenancies' state, though this manifests itself in several different ways.

Reading the current tenant

Tenancies have a handful of methods for "reading" the current tenant from the tenancy, that is, operations that return data and values without ever actually setting or changing anything.

1public function check(): bool;
2 
3public function tenant(): ?Tenant;
4 
5public function key(): int|string|null;
6 
7public function identifier(): ?string;

Checking for a current tenant

If you need to see if a particular tenancy has a current tenant, you can use the check() method. This method returns true if there is a current tenant, and false if there isn't.

1if ($tenancy->check()) {
2 // There's a tenant
3} else {
4 // There isn't a tenant
5}

Getting the current tenant

If you need to retrieve the current tenant, you can use the tenant() method which will return an instance of Sprout\Contracts\Tenant if there is a tenant, or null if there isn't.

1$tenant = $tenancy->tenant();
2 
3if ($tenant === null) {
4 // There isn't a tenant
5}

Getting the tenants key or identifier

If you only need to retrieve the tenants' key or identifier, there are two helper methods for this. The key() method returns the tenant key, and the identifier() method returns the tenant identifier. If there is no current tenant, both methods return null.

1$model->attach($tenancy->key());
2 
3route('my.route', ['tenant_subdomain' => $tenancy->identifier()]);

Writing the current tenant

Tenancies also have several methods for "writing" the current tenant, almost entirely centred around setting them.

1public function identify(string $identifier): bool;
2 
3public function load(int|string $key): bool;
4 
5public function setTenant(?Tenant $tenant): static;

Setting the tenant by identifier

If you want to set the current tenant and all you have is an identifier, you can use the identify() method. This method will return true if the current tenant changed, and false otherwise.

1if ($tenancy->identify($identifier)) {
2 // Tenant was set successfully
3}

The default implementation of this method comes with a few side effects.

The default implementation of this method calls setTenant() before dispatching the event.

Setting the tenant by key

If you want to set the current tenant and all you have is a key, you can use the load() method. This method will return true if the current tenant changed, and false otherwise.

1if ($tenancy->load($key)) {
2 // Tenant was set successfully
3}

The default implementation of this method comes with a few side effects.

The default implementation of this method calls setTenant() before dispatching the event.

Setting the tenant

If instead of a tenant key or identifier, you have an instance of your tenant, you can use the setTenant() method. Since this method is a setter, it should always set the current tenant, therefore, it does return a value to indicate its success.

1$tenancy->setTenant($tenant);
2 
3// The tenancy **IS** set

The default implementation of this method also has a few side effects.

  • If the provided value is the same as the current tenant, it will not attempt to write to the property.
  • If the provided value is null, the current resolver and hook will be set to null.
  • If the provided value is not null, and is not the same as the current tenant, a CurrentTenantChanged event will be dispatched.

How did the current tenant come to be?

As well as providing ways to read and write the tenancies' state, these instances provide a handful of methods for storing information about HOW the current tenant came to be, as well as methods to read this.

1public function resolvedVia(IdentityResolver $resolver): static;
2 
3public function resolvedAt(ResolutionHook $hook): static;
4 
5public function resolver(): ?IdentityResolver;
6 
7public function hook(): ?ResolutionHook;
8 
9public function wasResolved(): bool;

When resolving a tenant, the resolvedVia() and resolvedAt() methods are called, to store the identity resolver that was used, as well as the resolution hook where it happened.

1$identity = $resolver->resolveFromRequest($request, $tenancy);
2 
3$tenancy->resolvedVia($resolver)->resolvedAt($hook);

You can then access this information using resolver() for the identity resolver, hook() for the resolution hook, and wasResolved() for a boolean check to determine whether the current tenant was in fact resolved.

The default implementations of these methods don't have any side effects, though it should be considered that wasResolved() will only return true if there's a current tenant, and an identity resolver.

Tenancy Options

Tenancies also have tenancy options, as covered in that part of the documentation. With this in mind, the objects responsible for managing a tenancies' state also come with methods for working with tenancy options.

1public function hasOption(string $option): bool;
2 
3public function addOption(string $option): static;
4 
5public function removeOption(string $option): static;

If you want to check if a tenancy has an option, you have hasOption(). If you want to add a new option, you have addOption(), and if you want to remove an option, you have removeOption(). All these methods accept a string, and aren't tied to the Sprout\TenancyOptions class, specifically to allow for additional options.

Tenancy options are treated as simple boolean values, they are present, or they are not. Applications will not do anything regarding a tenancy option, unless code has been written specifically to make use of their presence, like with the default options.

How to work with them

Sprout keeps track of each tenancy as it becomes active, treating the most recent tenancy as the current one. Tenancies are considered active, either when manually set as the current tenancy, or when resolution is attempted. Even if resolution fails, that tenancy is still active, it just doesn't have a current tenant.

Sprout also provides a handful of different ways to work with tenancies.

Using an attribute

Sprout comes with a contextual attribute that you can add to a method/function parameter to have the current tenancy injected. This attribute is Sprout\Attributes\CurrentTenancy, and requires no arguments.

1public function __construct(#[CurrentTenancy] Tenancy $tenancy) {
2 // Do something here
3}

Using the Sprout core

The core Sprout class (Sprout\Sprout), and by extension the sprout() helper, and Sprout\Facades\Sprout facade has a handful of self-explanatory methods for dealing with active tenancies.

1public function setCurrentTenancy(Tenancy $tenancy): void;
2 
3public function hasCurrentTenancy(): bool;
4 
5public function getCurrentTenancy(): ?Tenancy;
6 
7public function getAllCurrentTenancies(): array;

If you want to manually set the current tenancy (append the tenancy stack), you use setCurrentTenancy(). If you want to check if there is a current tenancy, you use hasCurrentTenancy(), and if you want to get the current tenancy, or all tenancies, you use getCurrentTenancy() and getAllCurrentTenancies() respectively.

Tenancies are not marked as the "current tenancy" automatically, UNLESS they're going through Sprouts built-in resolution process. If you're doing something that requires a current tenancy, but doesn't resolve the tenant, you will have to manually set the current tenancy.

Using the tenancy manager

Tenancy instances are created and handled by a class known as the "Tenancy Manager". The tenancy manager is bound to container as a singleton using its class Sprout\Managers\TenancyManager, so is available for injection. It is also aliased as sprout.tenancies if you need to access it that way. The Sprout\Sprout class will always contain the instance of this class, so it can be accessed via that, the helper or the facade.

1// From the Sprout core class
2app(Sprout\Sprout::class)->tenancies();
3 
4// From the Sprout helper
5sprout()->tenancies();
6 
7// From the facade
8Sprout\Facades\Sprout::tenancies()

Full usage of the tenancy manager is an advanced topic covered in another part of the documentation, but for our purposes here, we only need to worry about the get() method.

1public function get(?string $name = null): Tenancy

This method will retrieve an already instantiated tenancy, instantiate a new tenancy, or throw an exception if there's a problem. You can give it the name used in the tenancy config to register a particular type of tenancy, or leave it null to use the default tenancy. This method is also accessible as a helper.

1function tenancy(?string $name = null): Tenancy;