Installation
Add the package via Composer (prefer dmr/dmr for stability):
composer require dmr/dmr dmr/dmr-bundle:dev
Or via composer.json:
{
"require": {
"dmr/dmr": "^0.1",
"dmr/dmr-bundle": "dev-master"
}
}
Enable the Bundle
Register DMR\Bundle\DMRBundle in config/bundles.php:
return [
// ...
DMR\Bundle\DMRBundle::class => ['all' => true],
];
First Use Case
Define a custom mapping file (e.g., config/dmr/mapping.yml) and reference it in config/packages/dmr.yaml:
dmr:
mapping_paths: ['%kernel.project_dir%/config/dmr']
Example mapping.yml:
App\Entity\User:
type: entity
table: custom_users
fields:
email: { type: string, length: 255 }
Verify Integration
Run php bin/console doctrine:schema:update --dump-sql to check if custom mappings are applied.
Dynamic Mapping Overrides Use DMR to override Doctrine mappings without modifying entity annotations or XML/YAML files:
# config/dmr/overrides.yml
App\Entity\Product:
lifecycleCallbacks:
prePersist: [setCreatedAt]
preUpdate: [setUpdatedAt]
Multi-Environment Configurations
Leverage Symfony’s environment-aware config (e.g., config/dmr/dev.yml):
# config/dmr/dev.yml
dmr:
mapping_paths: ['%kernel.project_dir%/config/dmr/dev']
Integration with Doctrine Events
Listen to dmr.load_mapping events to dynamically modify mappings:
// src/EventListener/DMRListener.php
namespace App\EventListener;
use DMR\Event\LoadMappingEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DMRListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'dmr.load_mapping' => 'onLoadMapping',
];
}
public function onLoadMapping(LoadMappingEvent $event)
{
$mapping = $event->getMapping();
if ($mapping['name'] === 'App\Entity\User') {
$mapping['fields']['is_active'] = ['type' => 'boolean', 'default' => false];
}
}
}
Combining with Doctrine Extensions
Use DMR to add behaviors (e.g., Gedmo\Timestampable) without annotations:
# config/dmr/behaviors.yml
App\Entity\Post:
gedmo:
timestampable:
timestampable_on: [createdAt, updatedAt]
php bin/console dmr:validate.config/packages/dev/monolog.yaml:
handlers:
dmr:
type: stream
path: "%kernel.logs_dir%/dmr.log"
level: debug
dmr.cache: true in config.Stability Issues
dmr/dmr for stability.dev versions in production unless absolutely necessary.Configuration Conflicts
php bin/console debug:config dmr to verify loaded mappings.Event Dispatching Quirks
dmr.load_mapping fire after Doctrine’s default mappings are loaded. Modify mappings before they are merged into Doctrine’s internal structure.kernel.request event to pre-load DMR mappings:
// src/EventListener/PreloadDMR.php
use Symfony\Component\HttpKernel\Event\RequestEvent;
class PreloadDMR
{
public function onKernelRequest(RequestEvent $event)
{
if ($event->isMainRequest()) {
$dmr = $event->getContainer()->get('dmr');
$dmr->loadMappings(); // Force reload
}
}
}
Namespace Resolution
App\Entity\User). Relative paths (e.g., ../Entity/User) may fail.Doctrine Cache Invalidation
php bin/console cache:clear
php bin/console doctrine:cache:clear-metadata
Inspect Loaded Mappings:
php bin/console debug:container dmr
Then dump the mappings property:
$dmr = $container->get('dmr');
var_dump($dmr->getMappings());
Log Merged Mappings:
Override the DMR\Reader\MappingReader service to log merged results:
# config/services.yaml
services:
App\DMR\LoggerMappingReader:
decorates: 'dmr.reader.mapping'
arguments: ['@dmr.reader.mapping.inner']
// src/DMR/LoggerMappingReader.php
namespace App\DMR;
use DMR\Reader\MappingReaderInterface;
use Psr\Log\LoggerInterface;
class LoggerMappingReader implements MappingReaderInterface
{
private $decorated;
private $logger;
public function __construct(MappingReaderInterface $decorated, LoggerInterface $logger)
{
$this->decorated = $decorated;
$this->logger = $logger;
}
public function loadMappings(array $paths)
{
$mappings = $this->decorated->loadMappings($paths);
$this->logger->debug('Loaded DMR mappings', ['mappings' => $mappings]);
return $mappings;
}
}
Custom Mapping Formats
Extend DMR\Reader\MappingReaderInterface to support new formats (e.g., JSON):
class JsonMappingReader implements MappingReaderInterface
{
public function loadMappings(array $paths)
{
$mappings = [];
foreach ($paths as $path) {
if (file_exists($file = "$path/mapping.json")) {
$mappings = array_merge($mappings, json_decode(file_get_contents($file), true));
}
}
return $mappings;
}
}
Register as a service:
services:
dmr.reader.json:
class: App\DMR\JsonMappingReader
tags: ['dmr.reader']
Post-Processing Mappings
Use dmr.post_load event to transform mappings after loading:
// src/EventListener/DMRPostProcessor.php
use DMR\Event\PostLoadMappingEvent;
class DMRPostProcessor
{
public function onPostLoad(PostLoadMappingEvent $event)
{
$mappings = $event->getMappings();
foreach ($mappings as &$mapping) {
if (isset($mapping['fields']['created_at'])) {
$mapping['fields']['created_at']['column'] = 'created_at_timestamp';
}
}
}
}
Conditional Mapping Loading Dynamically enable/disable mappings based on environment or user roles:
// src/DMR/ConditionalMappingReader.php
use Symfony\Component\HttpFoundation\RequestStack;
class ConditionalMappingReader implements MappingReaderInterface
{
private $decorated;
private $requestStack;
public function __construct(MappingReaderInterface $decorated, RequestStack $requestStack)
{
$this->decorated = $decorated;
$this->requestStack = $requestStack;
}
public function loadMappings(array $paths)
{
$mappings = $this->decorated->loadMappings($paths);
if ($this->requestStack->getCurrentRequest()->attributes->get('is_admin')) {
$mappings['App\Entity\User']['fields']['admin_notes'] = ['type' => 'text'];
}
return $mappings;
}
}
How can I help you explore Laravel packages today?