Allows fine-grained definitions how entities and their properties are allowed to be accessed in CRUD applications.
Provides PHP classes that build upon the query component to provide the means to enforce access limitation and simple aliasing from the wrapper schema to the schema of your backing object. The path-building component can optionally be used to ease the usage depending on the use case.
This component provides the means to define so called Types
for corresponding objects in
your application. Your Types
will limit the access to the schema and instances of your objects
depending on the authorization of the accessing user or other states in your application. It
shows its main advantages in CRUD applications but
can be applied to other types of applications as well.
As an example lets assume a very simple CMS
software. It has an Article
class which is connected to its author in a bidirectional many-to-one relationship.
Suppose for your business layer you need to distinguish between articles which are in their
draft state and thus only visible to their authors and finished articles visible to everyone.
Instead of sprinkling your business layer with potentially duplicated checks which user is allowed to
access which articles you can centralize your authorisations in an ArticleType
class.
After the initial setup you can use it with conditions to query instances from your data source, similar to the Query Component:
use EDT\Wrapping\Contracts\Types\TransferableTypeInterface;
use EDT\ConditionFactory\ConditionFactoryInterface;
function getArticleType(): ArticleType {
// returns the Type defining access limitations to your Article objects
}
function getEntityProvider(): EntityProviderInterface {
// returns the instance to access your data source
}
function getConditionFactory(): ConditionFactoryInterface {
// returns a factory for conditions adjusted to your data source
}
// initializations
$articleType = getArticleType();
$conditionFactory = getConditionFactory();
$typeProvider = new PrefilledTypeProvider([$articleType]);
$pathProcessor = new SchemaPathProcessor(new PropertyPathProcessorFactory(), $typeProvider);
// create a condition for your business logic
$nameCondition = $conditionFactory->propertyHasValue('jacob', 'author', 'accountName');
// check if the path used in the condition is allowed to be accessed
// and resolve aliases if used in the path
$pathProcessor->mapFilterConditions($articleType, [$nameCondition]);
// add the access condition defined by the type itself
$conditions[] = $pathProcessor->processAccessCondition($articleType);
// get the entities matching not only the name condition
// but also all conditions defined by the article type itself
$filteredEntities = $entityFetcher->getEntities($conditions);
The three helper function are left empty because their implementation depends on your actual use case:
-
getArticleType
returns yourArticleType
which you can implement by extendingTransferableTypeInterface
and instantiate in your favorite style (manually, as Symfony Service, loaded by configuration, …). An overview for the different interfaces available and how to implement them is shown in How to implement types. -
The instances returned by
getEntityProvider
andgetConditionFactory
depend on your data source. For possible classes see Providers and Factories setup.
As you can see in this example we explicitly specified a condition to only get articles written by jacob
.
However, if the ArticleType
was configured correctly we will actually get a subset of the result with only those articles that the current
user is allowed to access. E.g. if the user currently logged in is not jacob
she or he will
only receive articles that are not in their draft state anymore. jacob
on the other hand
well get all his articles. This implicit condition will be executed automatically and only needs to be set
once in the ArticleType
, where it will be applied every time the ArticleType
is used to access Article
objects.
If the object class you’ve written your Type
for doesn’t contain any relationships but
primitive types only then accessing your actual object instances may be fine. However, in the case
of the Article
objects we need to prevent users from simply getting an arbitrary article available to them
via the ArticleType
and then accessing its actual author instance. This would enable unlimited access
to all Article
instances of that author, regardless of any setting in the ArticleType
.
Because of this you can wrap your entity instances with a corresponding type.
The properties the wrapper grants access to depend on the
ArticleType
and its relations to other Types
. For example instead of completely denying access
to the author in the ArticleType
we may want to configure a UserType
. Like the ArticleType
the UserType
can restrict access to data and schema. This way we can allow access to authors
to get their public name, but can prevent the access to their drafts.
function getWrapperFactory(): WrapperObjectFactory {
// the wrapper factory initialized with the necessary parameters
}
$wrapperFactory = getWrapperFactory();
$wrappedEntities = array_map(function (Article $articleEntity) use ($articleType): WrapperObject {
return $this->getWrapperFactory()->createWrapper($articleEntity, $articleType);
}, $articleEntities);
Also note that the restrictions of Types
are in place no matter how they are accessed. For example
when you’ve restricted access to the internal e-mail address of your users using a UserType
then it does not matter
if the wrapper of the User
object was received via a relationship from a wrapped Article`
,
a wrapped Comment
or wrapped manually, the restriction will always be applied.
Conception and implementation by Christian Dressler with many thanks to eFrane.