I'm creating a PHP package that will reside in the /vendor
directory and it needs to access the database using Doctrine. In my main Symfony 4.2 code, I can access the database by using autowiring in the constructors like this:
public function __construct(EntityManagerInterface $em)
However, this only works with Symfony and does not seem to work inside public bundles. Please note that I need to access the default database connection, I do not want to create a new connection if one already exists.
I am trying to create a package that will work both with and without Symfony, so what I really need is the raw PHP to get the current database connection (not a new connection) without all the autowiring magic.
If the package is running under Symfony, it should use the current Symfony database connection. If not, it should create its own connection.
For example, I can open a new connection like this:
$conn = \Doctrine\DBAL\DriverManager::getConnection($params, $config)
That works fine, but I need a way to get the current Symfony database connection (something like this):
$conn = \Doctrine\DBAL\DriverManager::getCurrentConnection()
Unfortunately, there is no such function and I can't figure out how to create my own version of it.
You should define the class as a service and inject the EntityManager as an argument:
# vendor/Acme/HelloBundle/Resources/Config/services.yml
services:
acme_hellp.service_id:
class: Acme\HelloBundle\Class
arguments: ['@doctrine.orm.entity_manager']
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.xml');
}
}
Also, check this documentation on the Symfony site how to load your config files: https://symfony.com/doc/current/bundles/extension.html
Let me understand your problem, are you looking for a package to share using the doctrine Entity Manager of Symfony? so let me share with you my experience.
That is possible, and there are no solution visible to read or watch, so you need to review some packages to understand how they resolve the problem, the package i review was on this link, this package provide a support not only doctrine, also others platforms like Mongo, so let me share with you how they made the solution.
First, you need to crete a package in a bitbucket or github, and made this package as "type": "symfony-bundle". i read the comments before and you are correct, you need to create a Configuration and Extension file to enable you packages, so in your extension file you need to implement a Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface this package will allow you make the magic. so you can write some lines of code similar of this and replace for your namespace:
<?php
namespace NameSpaceBundle\CoreBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Class NameSpaceBundleExtension Configurations of the Core
* @package NameSpaceBundle\CoreBundle\DependencyInjection
*/
class NameSpaceBundleExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritdoc}
* @param array $configs
* @param ContainerBuilder $container
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader->load('routing.yml');
$this->processConfiguration(new Configuration(), $configs);
}
/**
* {@inheritdoc}
* @param ContainerBuilder $container
*/
public function prepend(ContainerBuilder $container)
{
$configs = $container->getExtensionConfig($this->getAlias());
$this->processConfiguration(new Configuration(), $configs);
$doctrineConfig = [];
$doctrineConfig['orm']['mappings'][] = array(
'name' => 'NameSpaceBundle',
'is_bundle' => true,
'type' => 'annotation',
'prefix' => 'NameSpaceBundle\CoreBundle\Entity'
);
$container->prependExtensionConfig('doctrine', $doctrineConfig);
}
/**
* {@inheritdoc}
* @return string
*/
public function getAlias()
{
return 'bundelName_core';
}
}
Review your service.yaml allocated on Resouces/config and now you can inject not only EntityManagerInterface.
services:
_defaults:
autowire: false # Automatically injects dependencies in your services.
autoconfigure: false # Automatically registers your services as commands, event subscribers, etc.
public: false
NameSpaceBundle\CoreBundle\Manager\Company:
public: false
class: NameSpaceBundle\CoreBundle\Doctrine\CompanyManager
arguments:
- '@doctrine'
- '\NameSpaceBundle\CoreBundle\Entity\Company'
Also this aprouch is similar of some packages of FriendsOfSymfony and the package i tell you before.
PD. Sorry for my english is not my native language
After a couple days of trying to create a Symfony bundle to make things work the "official" way, I figured out a one-liner hack that gets the Symfony Doctrine connection and does what I need. It relies on the fact that $kernel
is a Symfony global:
// Singleton to get Doctrine database connection
//
// If we are running on the legacy system, it will open a new database connection.
//
// If we are running under Symfony, it will use the existing Doctrine
// connection.
class Doctrine
{
static private $instance = null;
private $conn;
// The database connection is established in a private constructor
private function __construct()
{
global $kernel;
if (class_exists('\config')) {
// running on legacy system
$cfg = new \config();
//
// set up Doctrine connection
//
$params = [
'driver' => 'pdo_mysql',
'user' => $cfg->db_username,
'password' => $cfg->db_password,
'host' => $cfg->db_hostname,
'dbname' => $cfg->db_name,
];
$config = new \Doctrine\DBAL\Configuration();
$this->conn = \Doctrine\DBAL\DriverManager::getConnection($params, $config);
} else {
// running on Symfony system
$this->conn = $kernel->getContainer()->get('doctrine.orm.default_entity_manager')->getConnection();
}
}
static public function getInstance()
{
if (!self::$instance) {
self::$instance = new Doctrine();
}
return self::$instance;
}
static public function connection()
{
return self::getInstance()->getConnection();
}
public function getConnection()
{
return $this->conn;
}
}
By using the above class, this simple statement will get a Doctrine connection in both Symfony and our legacy code:
$conn = Doctrine::connection()
Now we can the same libraries on both Symfony and our legacy code. Yes, I know it's a hack, but it is easier to understand and maintain than the 10+ files and dozens of lines of code required to create even a simple Symfony bundle (and the legacy system won't recognize a bundle). I've put this class in a private composer package that can be used by both legacy systems and Symfony. This will make it much easier to keep things running until we can migrate all the legacy stuff to Symfony. After that is done, then I can do things the right way.