Just recently I on-boarded a Laravel 5 project and faced the problem of extending Doctrine DBAL implementation with custom types supported by Postgres. I have to plug in nicely https://github.com/opensoft/doctrine-postgres-types and https://github.com/boldtrn/JsonbBundle into the application configuration. Coming from Symfony2 I was expecting that there will be a smooth way of adding it to Doctrine's configuration (as in http://symfony.com/doc/current/cookbook/doctrine/dbal.html#registering-custom-mapping-types-in-the-schematool). Sadly it doesn't seem to be the case.
I saw the suggestion mentioned in 2013 by betawax in https://github.com/laravel/framework/issues/1675, also replied by tylorotwell that there are no plans for such kind of support. I don't feel convinced this is the right way to do it.
Did anyone overcome this obstacle recently?
After some further research I've created my own package with support of some additional functions and datatypes for PostgreSql 9.4 on Laravel and Symfony. Below is a working solution for my initial question (see original instructions here):
The steps below are based on Laravel 5 integration with FoxxMD's fork of mitchellvanw/laravel-doctrine.
1) Register the functions and datatype mappings:
# Usually part of config/doctrine.php
<?php
return [
'entity_managers' => [
'name_of_your_entity_manager' => [
'dql' => [
'string_functions' => [
// Array data types related functions
'ALL' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\All',
'ANY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Any',
'ARRAY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Arr',
'ARRAY_APPEND' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAppend',
'ARE_OVERLAPING_EACH_OTHER' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayAreOverlapingEachOther',
'ARRAY_CARDINALITY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCardinality',
'ARRAY_CAT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayCat',
'ARRAY_PREPEND' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPrepend',
'ARRAY_REMOVE' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayRemove',
'ARRAY_REPLACE' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayReplace',
'ARRAY_TO_STRING' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToString',
'STRING_TO_ARRAY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\StringToArray',
// Functions and operators used by both array and json(-b) data types
'CONTAINS' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Contains',
'IS_CONTAINED_BY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\IsContainedBy',
// Json(-b) data type related functions and operators
'JSON_GET_FIELD' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField',
'JSON_GET_FIELD_AS_TEXT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText',
'JSON_GET_OBJECT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObject',
'JSON_GET_OBJECT_AS_TEXT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetObjectAsText',
'JSONB_ARRAY_ELEMENTS' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements',
'JSONB_ARRAY_ELEMENTS_TEXT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText',
'JSONB_ARRAY_LENGTH' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength',
'JSONB_EACH' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach',
'JSONB_EACH_TEXT' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText',
'JSONB_EXISTS' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbObjectKeys',
'JSONB_OBJECT_KEYS' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists',
// Basic text search related functions and operators
'TO_TSQUERY' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery',
'TO_TSVECTOR' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector',
'TSMATCH' => 'MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Tsmatch',
],
],
'mapping_types' => [
'jsonb' => 'jsonb',
'_jsonb' => 'jsonb[]',
'jsonb[]' => 'jsonb[]',
],
2) Add EventSubscriber for Doctrine
<?php
namespace Acme\Handlers\Events;
use Doctrine\Common\EventSubscriber as Subscriber;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Types\Type;
class DoctrineEventSubscriber implements Subscriber
{
/**
* @return array
*/
public function getSubscribedEvents()
{
return [
Events::postConnect,
];
}
/**
* @param ConnectionEventArgs $args
* @throws DBALException
*/
public function postConnect(ConnectionEventArgs $args)
{
Type::addType('jsonb', "\MartinGeorgiev\Doctrine\DBAL\Types\Jsonb");
Type::addType('jsonb[]', "\MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray");
}
}
3) Add the EventSubscriber for Doctrine to a ServiceProvider
<?php
namespace Acme\Providers;
use Config;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Doctrine\Common\Persistence\ManagerRegistry as DoctrineManagerRegistry;
use Acme\Handlers\Events\DoctrineEventSubscriber;
/**
* Class EventServiceProvider
* @package Quantum\Providers
*/
class EventServiceProvider extends ServiceProvider
{
/**
* Register Doctrine Events as well.
*/
public function register()
{
$this->registerDoctrineEvents();
$this->registerDoctrineTypeMapping();
}
/**
* Register any other events for your application.
* @param DispatcherContract $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
}
/**
* Register Doctrine events.
*/
private function registerDoctrineEvents()
{
$eventManager = $this->registry()->getConnection()->getEventManager();
$eventManager->addEventSubscriber(new DoctrineEventSubscriber);
}
/**
* Register any custom Doctrine type mappings
*/
private function registerDoctrineTypeMapping()
{
$databasePlatform = $this->registry()->getConnection()->getDatabasePlatform();
$entityManagers = Config::get('doctrine.entity_managers');
foreach ($entityManagers as $entityManager) {
if (array_key_exists('mapping_types', $entityManager)) {
foreach ($entityManager['mapping_types'] as $dbType => $doctrineName) {
$databasePlatform->registerDoctrineTypeMapping($dbType, $doctrineName);
}
}
}
}
/**
* Get the entity manager registry
* @return DoctrineManagerRegistry
*/
function registry()
{
return app(DoctrineManagerRegistry::class);
}
}