Skip to content

Instantly share code, notes, and snippets.

@samsch
Last active November 14, 2018 10:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save samsch/9dd2cec7f21a3e879fd5 to your computer and use it in GitHub Desktop.
Save samsch/9dd2cec7f21a3e879fd5 to your computer and use it in GitHub Desktop.
Symfony dynamic DB connection
<?php
namespace AppBundle\Services\Factories;
//Inject the EM and Connection for the configured em that needs to be dynamic.
//I think you can actually just inject the EM, and call ->getConnection() it.
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\Connection;
//This service gets the dynamic DB creds from the request/user/whatever.
use AppBundle\Services\RequestToAccount;
class EmFactory
{
protected $reqToAcct;
protected $conn;
protected $rawEm;
protected $em = null;
public function __construct(RequestToAccount $reqToAcct, Connection $connection, EntityManager $em)
{
$this->reqToAcct = $reqToAcct;
$this->conn = $connection;
$this->rawEm = $em;
}
public function getEm()
{
//This bit of code effectively caches the connection.
//It assumes the connection isn't going to change once set during a request,
//so it just returns the connected EM.
if($this->em !== null) {
return $this->em;
}
//getDbCredArray() returns the DB creds as an array.
//You can see the structure used below. This can be modified to suit.
$dbCreds = $this->reqToAcct->getDbCredArray();
$dbParams = $this->conn->getParams();
//This is an extra check to make sure the EM isn't already connected
//to the correct DB. In my case, the user is always different.
//You might check based on host, dbname, whatever.
if($dbParams["user"] !== $dbCreds["user"]) {
$dbParams["user"] = $dbCreds["user"];
$dbParams["host"] = $dbCreds["host"];
$dbParams["dbname"] = $dbCreds["dbname"];
$dbParams["password"] = $dbCreds["pass"];
if($this->conn->isConnected()) {
$this->conn->close();
}
$this->conn->__construct(
$dbParams, $this->conn->getDriver(), $this->conn->getConfiguration(), $this->conn->getEventManager()
);
try {
$this->conn->connect();
}
catch(Exception $ex) {
//Might want to log this error somewhere.
throw new Exception("Failed to connect to DB");
}
}
$this->em = $this->rawEm;
return $this->em;
}
//These are your factory methods.
//They can be used to create services for repositories, so you can just inject
//what you need directly into most services. Since it's just getting injected
//in, those services don't know and don't care that the connection is dynamic.
//Generic respository method.
public function getRepository($repName)
{
return $this->getEm()->getRepository($repName);
}
//Specific repo method. I usually make one of these per repo that I plan to use.
public function createThingRepository()
{
return $this->getEm()->getRepository('AppBundle:Thing');
}
}
...
#Obviously, the service names themselves aren't important. I'm just using what's here as
#examples.
#If you plan to use the repositories in security (user provider, or whatever), these
#services need to be declared "lazy". For lazy services, you need proxymanager setup,
#which you can find details about here:
#http://symfony.com/doc/current/components/dependency_injection/lazy_services.html
#This is made with the assumption that your dynamic connection and em is called "dynamic".
#Swap out the service names according to your actual DB configuration.
connection_factory:
class: AppBundle\Services\Factories\EmFactory
arguments: ["@app.request_to_account", "@doctrine.dbal.dynamic_connection", "@doctrine.orm.dynamic_entity_manager"]
lazy: true
#If you need the EM in a service (or controller/containerAware!), use a service like this.
entity_manger:
class: Doctrine\ORM\EntityManager
factory: ["@connection_factory", getEm]
lazy: true
#Use services like this for injecting repositories.
thing_repo:
class: AppBundle\Model\ThingRepository
factory: ["@connection_factory", createThingRepository]
lazy: true
@ihorsamusenko
Copy link

Is there a way to force doctrine use this factory?

@samsch
Copy link
Author

samsch commented Mar 14, 2017

@samusenkoiv; I'm not sure about overriding the internal Doctrine service, but you can use this service as a factory to inject the EM or Connection objects as dependencies of your other services. If your app is entirely service-driven, this should cover nearly all usage. If you are using $this->get("doctrine.dbal.default_connection") or similar in your controllers (or other container aware classes), then you would have to change these out.

@samsch
Copy link
Author

samsch commented Mar 14, 2017

The create repository methods make sense to be split off into a new services.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment