I use Doctrine Entity Manager in my code several times, I used in this way for example:
$em = $this->getDoctrine()->getManager();
$countries = $em->getRepository('CommonBundle:Country')->findAll();
It's possible to use Doctrine Cache in this format? I see a lot of docs but all of them relative to Doctrine Query Builder, any advice? How to enable cache for SELECT queries?
Caching is not working (tough)
I'm dealing with caching results and I do this in order to cache some queries:
Enable APC under orm configuration at config.yml:
doctrine:
dbal:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
orm:
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: true
metadata_cache_driver: apc
result_cache_driver: apc
query_cache_driver: apc
Attach a repository to my entity:
/**
* #ORM\Entity(repositoryClass="CommonBundle\Entity\Repository\CountryRepository")
*/
class Country {
....
Write a method inside repository class for get the data and cache the results:
public function getCountries() {
$qb = $this->createQueryBuilder('c');
$query = $qb->getQuery();
$query->useResultCache(true, 3600, 'countries_cache');
return $query->getResult();
}
But any time I reload the page the query is executed as shown in the profiler (see picture below):
What I did wrong? The cache only works for production or also works for development? Should't get cached results instead of query the database once and once?
EntityRepository API does NOT allow to enable ResultCache with find* methods.
You have to use a QueryBuilder and/or DQL requests.
I wrote an article about Doctrine caching, maybe it can help you : http://blog.alterphp.com/2014/05/doctrine2-optimization-with-apc-cache.html
You have to enable cache for results in your symfony configuration :
doctrine:
orm:
metadata_cache_driver: apc # or memcache
result_cache_driver: apc # or memcache
query_cache_driver: apc # or memcache
Then on every request that need to be cached, call
$query->useResultCache(true)
More advanced options available here : http://docs.doctrine-project.org/en/2.0.x/reference/caching.html#result-cache
Related
This is my context:
I have two symfony REST API projects, and I want to do a relation between both projects. Each project uses his own database, entities and controllers.
From project 1 (client-api), I need to get access to entities of project 2 (product-api).
I have tried to use DoctrineRestDriver
Firstly, I have configured the config.yml file of client-api project:
doctrine:
dbal:
default_connection: default
connections:
default:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
memory: "%database_memory%"
path: "%database_path%"
charset: UTF8
product_api:
driver_class: "Circle\\DoctrineRestDriver\\Driver"
host: "http://localhost"
port: 8000
user:
password:
options:
authentication_class: "HttpAuthentication"
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AppBundle:
product_api:
connection: product_api
mappings:
AppBundle:
I want to read a country (id = 1) from the product-api project, so I have created this controller function on client-api project:
/**
* #Route("/countries", name="countries_other_api")
*/
public function getTheCountriesFromOtherApi()
{
$em = $this->getDoctrine()->getManager('product_api');
$country = $em->find("AppBundle\Entity\Country", 1);
}
But I'm getting the eror:
Class 'AppBundle\Entity\Pais' does not exist
Where is my problem ? How can I get access from symfony project 1 to symfony project 2 ?
Thanks.
So, you have 2 services. Each of service is responsible for its own entities. And you want service1 to have access directly to DB of service2...
My opinion is that you try to solve things in wrong way. If you want to get entities from service2 you should use REST API of service2 because you need to reimplement service2 logic in service1.
How to do it in better way? Create RestApi Client library for service2 and add it to service1 composer.json file. Dont forget to use authentication (hardcoded API keys or JWT...).
So, answer to your question 'where is problem?' I can say problem is in solution that you want to implement.
tl;dr How does the getManagerForClass() method find out which entity manager is the right one for a specific class?
I've made a generic controller that should be able to handle basic actions for different entities.
I also have connections to two different databases, so I'm using two entity managers.
In my controller, I'm trying to use Doctrine's getManagerForClass() method to find which manager to use for each class, as explained on this blog and this SO answer.
But the method does not seem to differentiate my two entity managers and simply returns the first one in the configuration.
My controller action starts like this:
public function indexAction($namespace, $entityName)
{
$classFullName = "AppBundle:$namespace\\$entityName";
$em = $this->getDoctrine()->getManagerForClass($classFullName);
This is my Doctrine configuration:
dbal:
default_connection: postgres
connections:
postgres:
driver: pdo_pgsql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
oracle:
driver: oci8
host: "%oracle_host%"
port: "%oracle_port%"
dbname: "%oracle_name%"
user: "%oracle_user%"
password: "%oracle_password%"
charset: UTF8
orm:
auto_generate_proxy_classes: true
entity_managers:
postgres:
connection: postgres
mappings:
AppBundle:
type: annotation
dir: Entity\Postgres
oracle:
connection: oracle
mappings:
AppBundle:
type: annotation
dir: Entity\Oracle
And my folder structure is as follows:
AppBundle
|___Controller
| |___EntityController.php
|
|___Entity
|___Postgres
| |___SomePostgresBasedEntity.php
|
|___Oracle
|___SomeOracleBasedEntity.php
Now I don't know exactly how the method works, and how it it supposed to know about the mapping if not through the configuration.
But if I call it this way, for example:
$em = $this->getDoctrine()->getManagerForClass("AppBundle:Oracle\\SomeOracleBasedEntity");
...I get the entity manager for Postgres.
But if I simply switch the entity manager configuration, putting the one for oracle first, the previous call works, but the following doesn't:
$em = $this->getDoctrine()->getManagerForClass("AppBundle:Postgres\\SomePostgresBasedEntity");
Update 1
getManagerForClass() cycles through every manager and for each one, checks if the class is "non-transient":
foreach ($this->managers as $id) {
$manager = $this->getService($id);
if (!$manager->getMetadataFactory()->isTransient($class)) {
return $manager;
}
}
This goes all the way down to AnnotationDriver->isTransient(). Here the doc says the following:
A class is non-transient if it is annotated with an annotation from the AnnotationDriver::entityAnnotationClasses.
#Entity seems to be one of those annotations that makes a class non-transient.
But then, how could any of my entities be transient at all? How could the driver distinguish an entity that belongs to a specific manager based solely on its annotations?
I must have missed something in the higher level classes.
Update 2
The method works when using yml mappings.
I kind of expected this behaviour. The difference comes from the implementations of the isTransient() method in the different drivers. The FileDriver implementation of isTransient returns true if the metadata file exists in the dir: directory of the mapping configuration.
I would have expected the AnnotationDriver to search for annotations only in the entities contained in the specified dir: directory, but it seems to ignore that parameter.
Or should I use another one?
At long last, I solved it.
The solution was using the prefix parameter.
entity_managers:
postgres:
connection: postgres
mappings:
AppBundle:
type: annotation
dir: Entity\Postgres
prefix: AppBundle\Entity\Postgres
alias: Postgres
oracle:
connection: oracle
mappings:
AppBundle:
type: annotation
dir: Entity\Oracle
prefix: AppBundle\Entity\Oracle
alias: Oracle
Explanation
The prefix parameter gets passed to the corresponding Entity Manager service, and is added to the entityNamespaces property, which otherwise defaults to AppBundle/Entity.
The Annotation Driver will then check for annotations in that specific namespace, whereas the File Driver checks for existing mapping files in the directory specified through the dir parameter.
(The alias parameter is not mandatory.)
At least, that's how I understand it.
My multi-tenant-app uses a master database, that holds information about tenants (like name, etc.) and a app-specific database per tenant.
I configured a master and some_tenant connection and entity manager in the doctrine section inside config.yml.
This gives me access to the master database from a controller (eg. for validating and getting tenant information for some_tenant based on the subdomain some_tenant.my-app.com). And it lets me use a tenant-specific database and entity manager during the application life-cycle.
The doctrine section in my config looks like this:
doctrine:
dbal:
default_connection: 'master'
connections:
master:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
some_tenant:
driver: pdo_mysql
host: "%database_host_some_tenant%"
port: "%database_port_some_tenant%"
dbname: "%database_name_some_tenant%"
user: "%database_user_some_tenant%"
password: "%database_password_some_tenant%"
charset: UTF8
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
master:
connection: master
mappings:
BEMultiTenancyBundle: ~
some_tenant:
connection: some_tenant
mappings:
AppBundle: ~
Here comes the part, which I am unhappy with and cannot find a solution:
First of all, tenants will be more than 20. And it starts to get messy, altering the doctrine config this way.
Then there is another config file, called tenants.yml which holds more information like enabled/disabled app-features, tenant-specific themes, etc.
The file is loaded, validated using the Config Component, and a container parameter is set, so that tenants configurations are available app-wide.
I would like to store the database credentials also in that file.
I want to create connections and entity managers based on that config. One per each tenant, which can be used during the app life-cycle.
I need to have them available also at the console command bin/console doctrine:schema:update --em=some_tenant for updating each tenant's database schem and bin/console doctrine:schema:update --em=master for updating the master database scheme.
By now I guess, the only way to achieve this is to add configuration parameters to the doctrine section programmatically after the AppBundle is loaded, and before the doctrine registry is constructed with the given managers and connections.
But I cannot even find a point, where I could achive this.
Is there another way to get to point 1 and 2?
Even though there is a similiar question, which I am not sure if it is exactly about the same problem and is about 3 years old, I wanted to post this question with a bit more explanation.
The solution in comments is a straight way easy solution and it will work. The other solution I've ever meet is to dynamically replace tenant database credentails using some external conditions, i.e request HOST.
You can decorate or extend the connection factory with a service in a way, that having the request stack available (or providing the domain with ENV or console argument for other SAPI's) you can have the same entity manager (even default!) being configured on demand.
on a brief look this would look like
use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Symfony\Component\HttpFoundation\RequestStack;
class DynamicConnectionFactory extends Factory
{
/** #var RequestStack */
private $requestStack;
public function __construct(array $types, RequestStack $stack)
{
parent::__construct($types);
$this->requestStack = $stack;
}
public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = array())
{
$host = $this->requestStack->getMasterRequest()->getHost();
$params = $this->replaceParamsForHost(array $params, $host);
return parent::createConnection($params, $config, $eventManager, $mappingTypes);
}
private function replaceParamsForHost(array $params, $host)
{
//do your magic, i.e parse config or call Memcache service or count the stars
return array_replace($params, ['database' => $host]);
}
}
I think that, To manage multi-tenant with symfony 2/3.
We can config auto_mapping: false for ORM of doctrine.
file: config.yml
doctrine:
dbal:
default_connection: master
connections:
master:
driver: pdo_mysql
host: '%master_database_host%'
port: '%master_database_port%'
dbname: '%master_database_name%'
user: '%master_database_user%'
password: '%master_database_password%'
charset: UTF8
tenant:
driver: pdo_mysql
host: '%tenant_database_host%'
port: '%tenant_database_port%'
dbname: '%tenant_database_name%'
user: '%tenant_database_user%'
password: '%tenant_database_password%'
charset: UTF8
orm:
default_entity_manager: master
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
master:
connection: master
auto_mapping: false
mappings:
AppBundle:
type: yml
dir: Resources/master/config/doctrine
tenant:
connection: tenant
auto_mapping: false
mappings:
AppBundle:
type: yml
dir: Resources/tenant/config/doctrine
After that, we cannot handle connection of each tenant by override connection info in request_listener like article: http://mohdhallal.github.io/blog/2014/09/12/handling-multiple-entity-managers-in-doctrine-the-smart-way/
I hope that, this practice can help someone working with multi-tenant
Regards,
Vuong Nguyen
I have Entity(s) and EntityHtml(s) entities which has one-to-one relashionship (Entity stores metadata and EntityHtml acts like a cache, storing ready HTML chunks for rendering).
I have defined a relationship in Entity class:
/**
* #ORM\OneToOne(targetEntity="EntityHtml")
* #ORM\JoinColumn(name="entityId", referencedColumnName="entityId")
*/
private $entityHtml;
but it isn't working. Also I have a kind of feeling, that annotations don't work at all, because changing them has no effect upon workability of the application.
On the other hand, messing with .orm.xml(s) reflects in how application works.
Can I tell Symfony to update ORM XMLs based on changes to annotations?
Should I duplicate relation meta to XML?
Does Symfony use info at both XML and annotations or does it choose one source?
config.yml is default one:
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
Does it make any difference if you try this:
/**
* #ORM\OneToOne(targetEntity="EntityHtml")
* #ORM\JoinColumn(name="entity_Id", referencedColumnName="entityId")
*/
I'm thinking that "entityId" is the Id in EntityHtml, and you need to specify a different "name" value in JoinColumn. I think I ran into that problem.
Try it - I'm not sure if it will work.
Figured this one out. It's a configuration issue. In order to make annotations work (xml is default option), you have to explicitly configure it:
# Doctrine Configuration
doctrine:
...
orm:
...
mappings:
AppBundle:
type: annotation
Unfortunatelly that's not specified in tutorials.
I have the following controller action (much simplified):
public function createAction()
{
$request = $this->getRequest();
$form = $this->createForm(
new DashboardType(),
new Dashboard()
);
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$dm = $this->get('doctrine_mongodb')
->getManager();
$dm->persist($dashboard);
$dm->flush();
}
}
}
Where dashboard is a Mongo Document object rather than an Entity. This code fails on the $form->bind line with the following DBAL error:
PDOException: could not find driver
I'm assuming this is due to the fact I don't have a relational database set up in my parameters.yml file. All I need for this app is Mongo which is run via Docker.
Is it possible to run Symfony and Doctrine with features like form validation if no database is installed? It seems a bit pointless to have to dockerise and manage a MySQL instance that won't be used purely to satisfy the Symfony requirements.
Is there a way around this?
Update
This isn't a driver issue as I can read and write fine to Mongo without form valiation. Edited response from modules:
$ php -m
[PHP Modules]
mongo
PDO
Solution
By default, Symfony adds the following to your config.yml
# Doctrine Configuration
doctrine:
dbal:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: true
This somehow affects the form validators. By deleting that section and just having the doctrine mongo config, the errors are fixed.
You should configure Symfony to use MongoDB as persistence system.
You can find the setup process on the DoctrineMongoDBBundle of the Symfony doc :
http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html