How to create a new bundle¶
Up until 3.x branch, Open Loyalty used standard Symfony division of application into Bundles and Components. Starting with 4.0, we decided to embrace Domain Driven Design and Hexagonal Architecture principles. That means every class in the system is placed according to its layer (Domain, Application, User Interface or Infrastructure) and its bounded context (Core, User, Campaign, Level, Segment, EarningRule and so on).
Bundles are used to group the classes by their bounded context for discoverability and order – and to group the configuration files by their area of influence.
Creating a new bundle in Open Loyalty is as simple as creating a new bundle in Symfony Framework
(see `Symfony documentation on bundles<https://symfony.com/doc/3.4/bundles.html>`_).
The only difference is the placement of the classes – all of the Symfony-required files will be placed
in the Infrastructure layer.
The bundles in Open Loyalty’s core use OpenLoyalty<name>Bundle
name convention, with <name>
being
the bounded context and directory’s name.
Create a bundle¶
Let’s create OpenLoyaltyAppBundle that will contain our logic.
First of all, create a new directory: src/Infrastructure/App
and then create a class named OpenLoyaltyAppBundle
:
// src/Infrastructure/App/OpenLoyaltyAppBundle.php
namespace OpenLoyalty\Infrastructure\App;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class OpenLoyaltyAppBundle extends Bundle
{
}
Then you need to register your newly created bundle in the framework.
Go to app/AppKernel.php
file and add an instance of your bundle to $bundles
array.
// app/AppKernel.php
public function registerBundles()
{
$bundles = [
// ...
// register your bundle
new OpenLoyalty\Infrastructure\App\OpenLoyaltyAppBundle(),
];
// ...
return $bundles;
}
Let’s verify that the bundle has been registered properly, assuming you use Docker:
$ docker exec -it --user=www-data open_loyalty_backend bin/console debug:config
Somewhere in the table, you should see the newly created OpenLoyaltyAppBundle
Available registered bundles with their extension alias if available
====================================================================
--------------------------------- ------------------------------
Bundle name Extension alias
--------------------------------- ------------------------------
...
OpenLoyaltyAppBundle open_loyalty_app
...
Configure the new bundle¶
To add configuration to your new bundle, you need to create an Extension for it. The process is explained thoroughly in `Symfony docs<https://symfony.com/doc/3.4/bundles/extension.html>`_, but a brief explanation is provided below.
To make the extension visible to symfony, it needs to be in DependencyInjection
directory inside the directory
which has the OpenLoyalty<name>Bundle
class in it.
The name of the extension is the name of the bundle with Bundle
replaced by Extension
.
In our example, this means a new directory, src/Infrastructure/App/DependencyInjection
, and a new file,
OpenLoyaltyAppExtension
.
The extension is mostly used to load configuration from a file, but it can also add parameters and more to a container.
To do that you need to import several classes that are included in a file below:
// src/Infrastructure/App/DependencyInjection/OpenLoyaltyAppExtension.php
namespace OpenLoyalty\Infrastructure\App/DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class OpenLoyaltyAppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
The example uses an Open Loyalty convention of creating configuration files in
src/Infrastructure/<bounded_context>/Resources/config/
directory.
By convention, the application uses YAML files for their brevity.
Add persistence configuration with Doctrine¶
Open Loyalty uses PostgreSQL as its main data store and a write DB with Elasticsearch as a read DB. To make operations on database easier, Doctrine’s DBAL and ORM are used.
This means you will sometimes need to create configuration files for your DB entities in order to save and read them.
Entities themselves are placed in Domain layer; the Doctrine configuration belongs to Infrastructure layer
and is placed in the same directory as the bundle class, in Persistence/Doctrine/ORM
subdirectory.
This is also where declarations of Types (Persistence/Doctrine/Type
) and concrete implementations of repositories
(Persistence/Doctrine/Repository
) live.
To add configuration to Doctrine, you need to add Doctrine’s compiler pass to your bundle’s build method:
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class OpenLoyaltyAppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
// ...
$container->addCompilerPass($this->buildMappingCompilerPass());
// ...
}
public function buildMappingCompilerPass()
{
return DoctrineOrmMappingsPass::createYamlMappingDriver(
[__DIR__.'/Persistence/Doctrine/ORM' => 'OpenLoyalty\Domain\App'],
[],
false,
['OpenLoyaltyApp' => 'OpenLoyalty\Domain\App']
);
}
}
An example file can look like this:
OpenLoyalty\Domain\App\SomeRecord:
type: entity
repositoryClass: OpenLoyalty\Infrastructure\App\Persistence\Doctrine\Repository\DoctrineSomeRecordRepository
table: app_some_records
id:
recordId:
type: record_id # This type will be defined in Persistence/Type/RecordIdDoctrineType.php
column: record_id
fields:
someField:
type: string
createdAt:
type: datetime
uniqueConstraints:
app_some_record_some_field_idx:
columns:
- someField
You can use other files in the current structure as examples.