Download the PHP package rentpost/doctrine-multi-tenancy without Composer
On this page you can find all versions of the php package rentpost/doctrine-multi-tenancy. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download rentpost/doctrine-multi-tenancy
More information about rentpost/doctrine-multi-tenancy
Files in rentpost/doctrine-multi-tenancy
Package doctrine-multi-tenancy
Short Description Advanced Doctrine2 multi-tenancy extension
License MIT
Homepage https://github.com/rentpost/doctrine-multi-tenancy-extension
Informations about the package doctrine-multi-tenancy
Doctrine MultiTenancy
Doctrine 2 extension providing advanced multi-tenancy support. The purpose of this extension is to allow flexibility in how multi-tenancy is defined on a per entity basis, as well as within contexts.
Why?
Often times multi-tenancy is handled differently depending on a number of different business concerns. Maybe each user has different roles, or is part of multiple organizations, etc.
Now, generally speaking, you could handle much of these concerns within repositories, and if your business logic allows for such organization, you should consider this approach instead. However, this is not always possible or ideal in many scenarios, especially when accessing relational entities and even more-so when exposing your entities and relationships over something like a GraphQL API where relationships can be traversed in an end-user defined manner.
This advanced approach to multi-tenancy aims to address these concerns, providing flexibility to define how multi-tenancy is handled across contexts on a per-entity basis.
Getting Started
Use the following instructions to get started with this Doctrine extension.
Prerequisites
This extension is compatible with Doctrine 2 and PHP >= 8.1.
If you're looking for PHP >= 7.4 support, please use 1.0.3
, the last version to support it
Installation
Setup
In order for this extension to work, you will need to register it with Doctrine's EntityManager
and EventManager
. To do so, you'll want to add the following to your configuration and setup for Doctrine. How this is done will depend on your implementation. See Doctrine's installation and configuration documentation for further details.
Now, let's break this down, if you're not familiar with Doctrine's configuration/setup. Depending on how your application is configured, the above may vary. We won't go into the particulars of Doctrine's configuration here.
The first part you need to be concerned with here is subscribing the listener to the EventManager
. If, for whatever reason, you do not wish to have any ValueHolders
or ContextProviders
, you can actually skip this step entirely, and only add the filter. Let's assume that you want to use both though.
What is a ValueHolder?
A ValueHolder
is a class that implements Rentpost\Doctrine\MultiTenancy\ValueHolderInterface
. The primary purpose of a ValueHolder
is to define a value for a given "identifier".
In the configuration above, we've added ValueHolder
s for Company
, User
, and Role
. These are going to provide parameters and values you'll want to use within an SQL query. The ValueHolderInterface
defines 2 methods:
The example, User
, above might return userId
as the "identifier" and the id of that User, represented as a string. It's effectively acting as a key/value store that's lazily loaded, such that, the value can mutate state.
The purpose of this will be more clear when viewing the example attributes below.
What is a ContextProvider?
A ContextProvider
is a class that implements Rentpost\Doctrine\MultiTenancy\ContextProviderInterface
. The primary purpose of a ContextProvider
is to define "contexts" with a way to validate if that context is currently within context, or "contextual".
A "context" might, for example, be the "roles" for Users, or, it could be an authorization level, or any other use you may find to be fitting for your business logic. It's intended to be flexible, so as to accommodate any number of use cases.
In the configuration example above, we added ContextProvider
s for Admin
, Manager
, and Guest
. Each of these ContextProvider
s will expose a "context".
The ContextProviderInterface
defines 2 methods:
Using the Admin
example above, we might return admin
as an "identifier". The isContextual
method is responsible for determining if this particular admin
identifier is consider to be within context, or contextual. In this situation, you might construct this class with a User
object that has a method called isAdmin
.
As with the ValueHolder
, this will all be more clear when viewing the example attributes below.
Usage
After you've gotten everything setup, the hard part is out of the way. Taking the time to properly evaludate how you'll setup your ValueHolder
and ContextProvider
classes will go a long way in making the usage clean and simple.
Examples
There are a couple things to note first.
$this
represents the alias for the current table, as defined by Doctrine.- "Identifiers" of a
ValueHolder
are enclosed in filters with curly brackets,{myIdentifier}
. - Multiple filters can be applied. Adding multiple fitlers will execute all that are "in context".
- Filters without an explicitly defined context, even if you have added
ContextProvider
s, will be applied for all contexts. Basically, it will always be executed. - Multiple "contexts" can be defined for a filter. If any context defined is "contextual", the filter will be applied.
Simple example without any context
In this example, it's assumed that the Product
table has a column called company_id
, which is used for multi-tenancy to associate products with a given company. The {companyId}
parameter here is defined in our ValueHolder\Company
in the example configuration above. companyId
would be the "identifier" and the value would be the id, of the current company.
Another example with multiple filters and context
In this example, we've added multiple filters. The first filter would always be applied. The second filter, with the "manager" context, would only be applied if the "identifier", manager
, as defined in the respective ContextProvider
is considered to be "contextual", via the isContextual()
method. If so, it would be applied as well.
In the second filter, the product
table doesn't have access to the necessary information we need to properly apply multi-tenancy filtering. Therefore, we execute a sub-select query. This allows for us to perform queries on relational tables. In this case, we're effectively saying that a manager
context only has access to a Product
that's in a product_group
with a status that is "published". If isn't true, the Product
wouldn't be returned.
Example with multiple context filters and a strategy
In the previous examples, we've been using the default FilterStrategy::AnyMatch
, which means that
any of the contexts that evaluate as true have had their where clause applied. In this example, you
can see that we've applied a FilterStrategy::FirstMatch
, which means the filter contextual filter
will be applied - only.
Keep in mind, if you do not provide a context, it's assumed to be in context, and that filter will be applied, meaning any subsequent filters will never be evaluated.
One other thing to note about the above example is the ignore
parameter. This allows for you to
specify a context where no filters will be applied. This can be especially useful in combination
with the FilterStrategy::FirstMatch
strategy. The combination of these two allows you to entirely,
or selectively, ignore all multi-tenancy for an entity - for a given context, that is.
Issues / Bugs / Questions
Please feel free to raise an issue against this repository if you have any questions or problems.
Contributing
New contributors to this project are welcome. If you are interested in contributing please send a courtesy email to [email protected].
Authors and Maintainers
Jacob Thomason [email protected]
License
This library is released under the MIT license.