Yii2 & Doctrine ORM simple integration

https://unsplash.com/photos/IKHvOlZFCOg

This all comes from a consultancy we’re currently doing. Our client need, as a stepping-stone towards more modern architecture and a more modern software stack, to integrate Doctrine ORM into their current Yii2 installation. We found some extensions: Yii2-Doctrine and Doctrine2Orm but after examining them further, they don’t seem like a good fit. Mainly for the lack of maintenance of both of them. In fact, they don’t currently have active development anymore.

Therefore, we started digging into Yii2 documentation trying to find the most simple way to integrate both of them. And this is what we achieved.

Yii2 Dependency Injection for the win

Although compared to other frameworks like Symfony or Laravel it’s a bit simpler, yet Yii2 Dependency Injection has enough features to integrate Doctrine ORM in a straightforward way. All the Doctrine ORM entities should be placed in a /src/DomainModel folder and all the XML mapping files should be placed in a src/Infrastructure/Persistence/doctrine-mappings

<?php // file path config/di.php

declare(strict_types=1);

$doctrineOrmConfig = require __DIR__ . '/doctrine-orm' . (YII_ENV_DEV ? '_dev' : '') . '.php';

return [
    'singletons' => [
        Cache::class => static fn(): Cache => YII_ENV ? new ArrayCache() : new RedisCache(),

        EntityManagerInterface::class => static function(Container $container) use ($doctrineOrmConfig): EntityManagerInterface {
            $isDevMode = YII_ENV_DEV;
            $proxyDir = __DIR__ . '/../var/cache/' . YII_ENV . '/doctrine/orm/Proxies';
            
            $config = Setup::createXMLMetadataConfiguration(
                [__DIR__ . "/../src/Infrastructure/Persistence/doctrine-mappings"],
                $isDevMode,
                $proxyDir,
                $container->get(Cache::class)
            );

            $conn = [
                'driver' => $doctrineOrmConfig['driver'],
                'url' => $doctrineOrmConfig['url'],
            ];

            return EntityManager::create($conn, $config);
        },
    ]
];

We’ve added a new file di.php into standard Yii2 config folder

Then it’s really easy to reuse this configuration into theweb.php or console.php files.

<?php

declare(strict_types=1);

$params = require __DIR__ . '/params.php';
$db = require __DIR__ . '/db.php';
$di = require __DIR__ . '/di.php';

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'aliases' => [
        '@bower' => '@vendor/bower-asset',
        '@npm'   => '@vendor/npm-asset',
    ],
    
    // ...
    
    'params' => $params,
  
    // HERE WE INCLUDE ALL THE DI CONFIG
    'container' => $di,
];

if (YII_ENV_DEV) {
    // configuration adjustments for 'dev' environment
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = [
        'class' => 'yii\debug\Module',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];

    $config['bootstrap'][] = 'gii';
    $config['modules']['gii'] = [
        'class' => 'yii\gii\Module',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];
}

return $config;

Example web.php file

And then from a controller

<?php

declare(strict_types=1);

namespace app\controllers;

class OrdersController extends \yii\web\Controller
{
    private EntityManagerInterface $entityManager;
    
    public function __construct(
        $id,
        $module,
        EntityManagerInterface $entityManager,
        $config = [])
    {
        $this->entityManager = $entityManager;

        parent::__construct($id, $module, $config);
    }
    
    public function actionEmit(): array
    {
        $this->entityManager->transactional(function() {
            $this->entityManager->persist(
                new Order(/*orderId: */1, /* price: */15.6, /*amount: */3)
            );
        });

        $response = \Yii::$app->response;
        $response->statusCode = 201;
        $response->format = Response::FORMAT_JSON;

        return ['success' => true];
    }
}

The full source code can be found at https://github.com/rigorguild/yii2-doctrine.

This is one of the cases we help with. If you need help on how to take on your technical challenges, we can surely help. Don’t hesitate to contact us!

Originally published on Medium.