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.
- A tenancy is the state. If you're accessing the application as a particular tenant, say using their subdomain, you are within their tenancy.
- 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 tenant3} else {4 // There isn't a tenant5}
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 tenant5}
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 successfully3}
The default implementation of this method comes with a few side effects.
- If the resulting tenant (retrieved by its identifier) is
null
, the current resolver and hook will be set tonull
. - Once the tenant has been set, a
TenantIdentified
event will be dispatched.
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 successfully3}
The default implementation of this method comes with a few side effects.
- If the resulting tenant (retrieved by its key) is
null
, the current resolver and hook will be set tonull
. - Once the tenant has been set, a
TenantLoaded
event will be dispatched.
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 tonull
. - If the provided value is not
null
, and is not the same as the current tenant, aCurrentTenantChanged
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 here3}
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 class2app(Sprout\Sprout::class)->tenancies();3 4// From the Sprout helper5sprout()->tenancies();6 7// From the facade8Sprout\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;