Architecture
ExpressionEngine’s architecture is based around a few core ideas. This section is meant to illustrate them to those that are interested. Much of it is optional but a cursory understanding will help to clarify the big picture.
Dependencies
ExpressionEngine’s core is built around a dependency container:
use ExpressionEngine\Service\Dependency\InjectionContainer;
$di = new InjectionContainer();
This is simply a container of named objects or factories. The container itself is not public. Additions are made by declaring them in the addon.setup
file.
Retrieving Data
Anything stored on a dependency container can be retrieve with make()
:
$di->make('Something');
This method is exposed globally as ee()
:
ee('Something');
Adding Objects
You can add objects directly to a dependency container and then call make()
to retrieve them:
$di->register('Member', $member);
$di->make('Member') == $member // true
Creating Factories
If a closure is added to the dependency container, it will be treated as a factory:
$di->register('User', function($di, $name)
{
return new User($name);
});
When you make()
an item that is a closure, it will be executed and its result will be returned. The first parameter to the closure will always be the dependency object that holds it. All other parameters will be any additional ones that were passed to make()
:
$u1 = $di->make('User', 'Bob');
$u2 = $di->make('User', 'Bob');
$u3 = $di->make('User', 'Alice');
// $u1 != $u2 != $u3
Hiding Dependencies
The first parameter to a closure inside a dependency container is always the container itself. This means you can nest calls to the container to resolve complex dependencies without exposing them in your public API:
$di->register('Database', function($di, $name)
{
return new Database($name);
})
$di->register('Session', function($di)
{
return new Session(
$di->make('Database', 'local')
);
});
$di->make('Session'); // no mention of a database
Prefixes
In a system that runs add-on code from potentially many different developers it can be difficult to prevent naming collisions. Namespaces work well when dealing with native objects, but the ability to alias long namespace names gets lost when using strings to identify files and class names.
To consistently solve this problem, ExpressionEngine assigns a prefix to all independent code sources. For any native code this prefix is ee:. All add-ons are assigned a prefix that matches the add-on folder name. This also matches the name used in the templating engine.
The following services currently support the prefix naming conventions:
Config Service
All config operations support prefixes on item and file names:
ee('Config')->get('addonname:item');
Dependency Container
The dependency container can use prefixes to create add-on services:
ee('addonname:ServiceName')
Model Service
The model service supports prefixes wherever a model shortname is allowed:
ee('Model')->get('addonname:MyModel')
View Service
Views support prefixes wherever a view name is allowed:
ee('View')->make('addonname:myview');
Providers
Motivation
Consider a situation where an add-on exposes a “Cart” service on the dependency container. Without separation, this service name could not be used by other add-ons without creating a collision.
Important: Please make sure you understand the Dependency Container before reading this section.
Solution
Providers help keep these parts separate by automatically prefixing them. All providers are simply a wrapper around the same core dependency object, with a given prefix:
$prefix = 'myaddon';
$provider = new Provider($dependencies, $prefix);
When registering or accessing an element on a provider, the prefix is automatically enforced. So these two are equivalent:
$provider->register('service', $obj);
// ==
$dependencies->register('myaddon:service', $obj);
All providers are simply wrappers around the same core dependency object, so elements from other provider are available on all of them, using the correct prefix:
$provider->make('service'); // addon:service
$provider->make('addoff:service'); // addoff:service
Default Provider
The default provider is the one that exposes the “ee” prefix. This prefix is used for all the default services. The ee()
function is an alias to the default provider’s make()
method.