Doctrine ODM Select database at runtime - php

I want to use doctrine ODM for my project and I had the idea to have a seperate database for each of my clients. I want to be able to manage clients at runtime, through my API. My question now is:
When I setup doctrine ODM I have to set my database settings in my parameters.yml but I want to be able to select the database at runtime. I will have one main database with all my fixture collections and client index to know which database to select, but the client specific stuff will then be in those client databases. Each Document class will still be linked to a collection like in the normal situation, but then in a different database.
Is there a way to select the database for a Document class at runtime?
So lets say I go to www.myproject.com/client1/item/list
I will list every item in the dbclient1.Items collection, and if I go to www.myproject.com/client2/item/list I will list all items in the dbclient2.Items collection.
I hope I made clear what I want to reach here... I couldn't find anything about this, but I think it would be weird if I was the first person to have a question about this... There must have been people before me with the same idea right?

Is there a way to select the database for a Document class at runtime?
Yes, you could call $dm->getConfiguration()->setDefaultDb('some_db_name') each time you need to change database but this can lead to unexpected behaviour when write comes into play.
Based on my experience (and few years practice on multi-tenant apps) the most bulletproof way is to create separate instances of DocumentManager per each context. You can achieve that by having one DocumentManager configured normally with your parameters but never used directly - instead you need to have a class that will manage its instances, fe:
use Doctrine\ODM\MongoDB\DocumentManager;
class DocumentManagerFactory
{
/**
* DocumentManager created by Symfony.
*
* #var DocumentManager
*/
private $defaultDocumentManager;
/**
* All DocumentManagers created by Factory so far.
*
* #var DocumentManager[]
*/
private $instances = array();
public function __construct(DocumentManager $dm)
{
$this->defaultDocumentManager = $dm;
}
public function createFor(Context $ctx)
{
$databaseName = $ctx->getDatabaseName();
if (isset($this->instances[$databaseName])) {
return $this->instances[$databaseName];
}
$configuration = clone $this->defaultDocumentManager->getConfiguration();
$configuration->setDefaultDB($databaseName);
return $this->instances[$databaseName] = DocumentManager::create(
$this->defaultDocumentManager->getConnection(),
$configuration,
$this->defaultDocumentManager->getEventManager()
);
}
}
Thanks to this approach documents from few databases will never be managed by one DocumentManager, you have one class responsible for meddling with ODM configuration and framework-approach preserved (event subscribers et al are same for each DocumentManager).

I also needed to default the databaseName depending on the domainName (i.e. seballot.mysite.com will use seballot database)
I made this hack, which not very nice but works great. I added those line in the DocumentManager construct
$dbName = isset($_SERVER["HTTP_HOST"]) ? explode('.', $_SERVER["HTTP_HOST"])[0] : 'default_database_of_my_root_website';
$this->config->setDefaultDB($dbName);

Related

Symfony 2 best practice DBAL without object doctrine

Well I am finally started to learn Symfony and I think peoples will understand my question (I Hope) and my wish to structure my code...
Well, I would like to create a Class which is called Reception and this class has a sql let's say in each methods/function. Each and evry methods can return a different nombre of column results.
Example :
Sql 1 : Jo;DATE;
Sql 2 : Client;Car;Time
Let's tell that I don't want to create a Entity to use it with doctrine...
I would like to use DBAL (pdo doctrine sql query) to do execute my queries... like in normal PHP poo programming.
Finally the question is : wether I should do this class as a Service, Entity? or I can simple put the pdo query in the controller....
Thanks in advance for your answers...
I avoid doctrine for the moment because I principaly doing some statistiques and also to play a bit with symfony and to increase the difficutly level progresivly...
thanks for your understanding...
Good Day
A Service (docs) is usually just a class which is responsible for doing some specific task. Lets say you have some statistics you need to be updated whenever specific events occur (i.e. file is downloaded, favored, etc) and you have multiple controllers where those different events occur. It would be a really bad idea to simply "copy-paste" your code. A better way would be to create a service and call it.
An Entity(docs) is an object which represents your database table. This object can later be used to generate forms in Symfony. Once you create an Entity, you can then create an EntityRepository. It is used to store more comprehensive sql queries. You can, for instance, have a method like this:
public function findUsersWithOrders() {
// Here you can:
// 1. use queryBuilder, which generates the query for you
// 2. write your DQL (Doctrine Query Language) manually
// 3. write a plain SQL query and return the results
}
I would strongly advice you to use this approach - it will save you a lot of time once you get a hold of it and IMHO is a better coding practice.
If you still decide you would want to pursuit your idea of storing queries in a class:
Yes, you could create a Service and use it for that purpose. You should use Symfony >= 2.3 because of Lazy Services which optimizes service loading. Here is an example of how your service might look like:
// App\BaseBundle\Services\MyServiceName.php
namespace App\BaseBundle\Services;
use Doctrine\ORM\EntityManager;
class MyServiceName {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* #var \Doctrine\DBAL\Connection
*/
private $connection;
public function __construct(EntityManager $entityManager) {
$this->em = $entityManager;
$this->connection = $entityManager->getConnection();
}
public function getUsers(){
// Update query
$this->connection->query('UPDATE statistics SET counter=counter+1 WHERE id = 1')->execute();
// Prepare (you can use just query() as well)
$select = $this->connection->prepare('SELECT * FROM users WHERE username LIKE :username');
$select->bindValue(':username', '%sample%');
$select->execute();
return $select->fetch(\PDO::FETCH_ASSOC);
}
}
Then, in your services.yml file you need to put this:
app.myservicename:
class: App\BaseBundle\Services\MyServiceName
arguments: [ #doctrine.orm.entity_manager ]
Now, whenever you call $this->get('app.myservicename') from a Controller, you will get an instance of your class.
Of course, you can put your sql code in the controller as well. This is not a good practice and you should avoid doing it, though. This example shows you how to do it:
/**
* #Route("/some/route")
* #Template()
*/
public function indexAction()
{
/**
* #var \Doctrine\ORM\EntityManager $em
*/
$em = $this->getDoctrine()->getManager();
// some business logic ...
try {
$em->getConnection()->query('UPDATE statistics SET counter=counter+1 WHERE id = 1')->execute();
} catch(\Doctrine\DBAL\DBALException $e){
// the query might fail, catch the exception and do something with it.
}
// other business logic...
return array('name' => 'Hello World');
}
I would advice you to have a look at the symfony best practices to see what are the best approaches to common problems. Also reading the main documentation will help you clear a lot of questions.

DataMapper Pattern with m:n relation (Domain Driven Design)

I am currently refactoring my software according to the Domain Driven Design. The problem is my understanding in the DataMapper part, specifically if some data have m:n relationship via a connection table (in case of SQL).
In this case I have a Domain Object:
class UserGroup extends DomainObject {
public $title;
public $description;
}
Now in my understanding, I would get an Object this way:
$userGroup = UserGroupMapper::instance()->findById(42);
$userGroup->title = 'Foo';
$userGroup->description = 'Bar';
UserGroupMapper::instance()->save($userGroup);
Am I on the right way, right now?
So here comes the problem:
I have 3 Tables:
core_users
id | etc
core_usergroups
id | title | description
core_usergroups_dependencys
usergroup_id | user_id
Basicly, the goal is, to find all Users which are in a specific group (or the inverted way)
The first thing, which comes to my mind:
$userGroup = UserGroupMapper::instance()->findById(42);
$userCollection = UserGroupMapper::instance()->findUsersByGroup($userGroup);
The DataMapper knows 2 tables and 2 Domain Objects, but this doesn't feel right, does it?
I don't understand the Service Part of DDD. According to it (in my understanding) I would write
UserGroupService::instance()->findUsers($userGroup);
But in this case the service need an own Data Mapper. Why shouldn't I call
UserGroupDependencyMapper::instance()->findUsersByGroup($userGroup);
UserGroupDependencyMapper::instance()->findGroupsByUser($user);
I generaly dont understand the mapping for dependency. A DataMapper should everytime return an object instance or a collection of object instances of the same type. But here, the mapper could return either a user or a group.
So, what is the way I should go for such a common problem? Bonus: What should my service do in this Example?
Update to Clarify
My question is not about working with the data in Domain Driven Design. It is about how can i fetch a list of data in m:n relation without fetching all. How should the Interface look like?
Basicly i need this according to the DataMapper pattern:
select * from core_users
join core_usergroups_dependency on core_users.id = core_usergroups_dependency.user_id
where core_usergroups_dependency.usergruop_id = 42
But where would the Call findUsersByUserGroup($myGroup) Call be located? UserMapper?
If i have that Mapper or how ever we will call this, which part knows about it? The Domain Repository?
The method is part of the repository interface definition (defined in the Domain). The implementation is part of the persistence and does the actual query. The Domain services will know only about the abstraction.
// in the Domain
interface IUsersRepository // I think we need a better name
{
public function getUsersByGroup($myGroup);
}
class MyDomainService
{
private $repo;
public function __construct(IUsersRepository $repo)
{
$this->repo = $repo;
}
public function doSomething($group)
{
$groups = $this->repo->getUsersByGroup($group);
// process $groups
}
}
// in DAL
class MyRepo implements IUsersRepository
{
// implementation
}
What matters here is that the Domain doesn't know about how you get those groups. Only the implementation knows that, so you can do whatever query you want there. The Domain is decoupled from the query itself.

How do I write a Doctrine migration which can redistribute data into new tables

I have a database (which was actually created using Propel in a Symfony1 application). I am reimplementing it in Symfony2 and Doctrine, but I also want to take the opportunity to refactor the database somewhat.
I have defined a set of Doctrine Entities and run doctrine:migrations:diff, which has created me a basic Migration to add tables, columns and constraints, and drop a load of columns.
However, before dropping these columns I want to copy the data into some of the new tables, and then link the new records in these table to new columns in the first table. I don't believe it's possible to do this in pure SQL (in general, the contents of one table are being distributed among three or four tables).
This gave me a hint, and caused me to find this (which I had skipped, because I had no idea what relevance "containers" might be to my problem).
But what I have not found anywhere in the Symfony or Doctrine documentation is an example of actually moving data around in a migration - which to me seems to be one of the core purposes of migrations!
It is possible that I could use the hints in those links above, but then I'm not sure how to proceed. I do not have (and don't really want to take the time to create, though I'm sure I could do) Doctrine entities for the existing database schema: can I then use DQL? I simply don't know.
So two questions:
Can somebody give me an example of a Doctrine migration which moves data between tables?
Alternatively, can anybody clarify how dependent the syntax of DQL is on the definitions of the Entities in Doctrine? Can I use it to specify columns which are not in the Entity definitions?
OK, I seem to have found it, from a number of sources (including this) and trial and error.
Cerad's comments were a little help, but mainly I'm doing it by using the DBAL layer to read in the data (which I can get at by $this->connection), and the ORM to save the new data (which requires the EntityManager, so I did have to use the trick with the container).
I put all the code in postUp(), including the generated code to drop columns from tables.
Sample bits of my code:
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use PG\InventoryBundle\Entity\Item;
use PG\InventoryBundle\Entity\Address;
.
.
.
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20140519211228 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
.
.
.
}
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
$query = "SELECT * FROM item";
$stmt = $this->connection->prepare($query);
$stmt->execute();
// We can't use Doctrine's ORM to fetch the item, because it has a load of extra fields
// that aren't in the entity definition.
while ($row = $stmt->fetch()) {
// But we will also get the entity, so that we can put addresses in it.
$id = $row['id'];
// And create new objects
$stock = new Stock();
.
.
.
$stock->setAssetNo($row['asset_no']);
$stock->setItemId($row['id']);
$em->persist($stock);
$em->flush();
}
// Now we can drop fields we don't need.
$this->connection->executeQuery("ALTER TABLE item DROP container_id");
$this->connection->executeQuery("ALTER TABLE item DROP location_id");
.
.
.
}

How do I use Zend_Db_Table with multiple schemata and multiple deployments?

My boss wants the application we are currently working on to be split across several schemata in the database, because he wants multiple applications -- some of which I have no control over -- to be able to access the data, following a naming convention like DeploymentPrefix_Category. For instance, there'd be a few schemata for production, Production_Foo, Production_Bar, and Production_Baz, and then the same for staging Staging_Foo, Staging_Bar, and Staging_Baz, and the same for development.
The problem is that while Zend_Db_Table lets me specify a schema, it doesn't seem to let me generate that schema on the fly, which I would need to do to put that prefix on the schema.
What's the best way to handle that?
The issue of different configs for different environments is easily handled with Zend_Config. See the section on config in the quickstart:
http://framework.zend.com/manual/en/learning.quickstart.create-project.html
This allows you to specify different settings for each environment.
As for the schemas, I'm guessing you have some tables that live in Production_Foo and others that live in Production_Bar. Consider extending Zend_Db_Table for each of these schemas and pointing to the correct database at the time of construction.
Zend_Db_Table's constructor is defined as follows:
public function __construct($config = array(), $definition = null)
{ ... }
When we follow through to see where $definition leads it allows you to pass an array that is loaded into Zend_Db_Table_Definition. One of the options for this is the table name:
/**
* #param string $tableName
* #param array $tableConfig
* #return Zend_Db_Table_Definition
*/
public function setTableConfig($tableName, array $tableConfig)
{
// #todo logic here
$tableConfig[Zend_Db_Table::DEFINITION_CONFIG_NAME] = $tableName;
$tableConfig[Zend_Db_Table::DEFINITION] = $this;
if (!isset($tableConfig[Zend_Db_Table::NAME])) {
$tableConfig[Zend_Db_Table::NAME] = $tableName;
}
$this->_tableConfigs[$tableName] = $tableConfig;
return $this;
}
As for your schema, you just pass in different set of options for the db adapter that points to the correct one.
Well i'd argue that it's not "good" to have different table-names for different staging scenarios "Production_Foo" - "Staging_Foo" - "Testing_Foo".... just "Foo" is so much easier and more productive...
But anyways:
Personally i use the Table-Data-Gateway (i guess that's what it's called) - using Zend_Db_Table_Abstract extensions, so i would do it like this:
class Application_Model_DbTable_Foo extends Zend_Db_Table_Abstract
{
public function __construct($config = array()) {
$this->_name = Zend_Registry::get('config')->env_tbl_prefix.'Foo';
parent::__construct($config);
}
}
Obviously this requires you to have the config stored to the registry and inside the config to define the key "env_tbl_prefix" with your environment prefixes "Production_", "Staging_", "Testing_", etc...
Still, you're the developer, tell your boss to make life easier for all of you ^^ There's so many disadvantages using different table names depending on environment :\

Doctrine 2, query inside entities

How do I perform queries in an entity?
namespace Entities\Members;
/**
* #Entity(repositoryClass="\Entities\Member\MembersRepository")
* #Table(name="Members")
* #HasLifecycleCallbacks
*/
class Members extends \Entities\AbstractEntity
{
/**
* #Id #Column(name="id", type="bigint",length=15)
* #GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Column(name="userid", type="bigint", length=26, nullable=true)
*/
protected $userid;
/**
* #Column(name="fname", type="string", length=255,nullable=true)
*/
protected $fname;
/**
* #OneToMany(targetEntity="\Entities\Users\Wall", mappedBy="entry", cascade={"persist"})
*/
protected $commententries;
public function __construct()
{
$this->commententries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Example I would like to have a function inside this entity called: filter()
and I want to be able to filter the commententries collection. It should return a collection with a certain condition such id=1. Basically it should be filtering the data received from the join query.
So something like this:
$this->commententries->findBy(array('id' => 1));
But obviously this does not work.
Your ArrayCollection already implements a filter() method, you need to pass a Closure to get it to work your entities (here, the commentEntries).
$idsToFilter = array(1,2,3,4);
$member->getComments()->filter(
function($entry) use ($idsToFilter) {
if (in_array($entry->getId(), $idsToFilter)) {
return true;
}
return false;
}
);
(not tested)
Note that such method will iterate and eager load over all your Comments, so in case where a User has a lot it may be a big bottleneck;
In most case, you want to use a custom repositories, where you can place such logic.
As timdev suggested, you can create a MemberService which will wrap such call by being aware of the EntityManager.
Separating Entities from the Peristance Layer is a big improvement over Doctrine 1, and you should not break that rule.
Generally speaking, you shouldn't do this.
Entities, as a rule of thumb, should not know about the entitymanager (directly, or via some intermediary object).
The reason for this is mostly testability, but in my experience, it helps keeps things organized in other ways.
I'd approach it by designing a service class that handles the lookups for you. Your controller (or whatever) would drive it like this:
<?php
// create a new service, injecting the entitymanager. if you later wanted
// to start caching some things, you might inject a cache driver as well.
$member = $em->find('Member',$member_id); //get a member, some how.
$svc = new MemberService($em);
$favoriteCommentaries = $svc->getFavoriteCommentaries($member);
As I hint in the comment, if you decide later that you want to add caching (via memcached, for instance) to avoid frequent lookups, you'd do that somewhere near or in this service class. This keeps your entities nice and simple, and easily testable. Since you inject your entitymanager into the service at construction-time, you can mock that as needed.
getFavoriteCommentaries() could use various implementations. A trivial one would be to proxy it to Member::getFavoriteCommentaries(), which would actually load everything, and then filter out the "favorite" ones. That probably won't scale particularly well, so you could improve it by using the EM to fetch just the data you need.
Use a custom repository for queries
You should not write queries in your entities, but you should use a repository for that. This is also explained in the doctrine documentation 7.8.8 Custom Repositories. It will allows you to build your custom queries on a central spot and keeps your entity definitions clean.
Use criteria to filter collections:
But if you want to filter inside your collection in a get method you can use Criteria. You can read on how to use Criteria in the Doctrine documentation 8.8 Filtering collections. To filter like you want to do would look something like this:
Declare at the top of your entity class the Criteria class
use Doctrine\Common\Collections\Criteria
In your getCommentEntries method use the class to filter:
public function getCommentEntries()
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq('id', 1));
$filteredCommentEntries = $this->commententries->matching($criteria);
return $filteredCommentEntries;
}
Your question was really hard to understand, please try and work on how you structure your questions in the future. For instance, you say "return back the same result" but "filter", which could mean anything. Do you want to use the same result set (why on earth would you ever choose to do that), and just use array_filter or array_walk to filter the results or do you actually want to use a conditional join? It's incredibly ambiguous.
Anyway.. answer ( after reading your question 4 times).
$q = $qb->select ( "m","w" )
->from ( "Members", "m" )
->leftJoin( "m.commententries","w",'WITH', 'w.id = :id')
->setParameter ( "id", $id )
->getQuery ();
I agree with "timdev". You shouldn't define query in your entities class. My way to define a service class support the entities are repository classes. For example: User (entity -- YourBundle/Entity/User.php) will have UserRepository (service class -- YourBundle/Repository/UserRepository.php). Your "filter" method should be in here. You just need to map this service class in your entity class. In your controller, you can always access the "filter" via its repository. It's documented very detail in the Symfony2 book

Categories