Using different connections with Doctrine - symfony

My application pulls data from an external database and then stores in the application database after minor processing. How would I set-up the mappings for the external database since it's not tied to entities? This is what I currently have:
dbal:
default_connection: default
types:
json: Sonata\Doctrine\Types\JsonType
connections:
default:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
rnr:
driver: pdo_mysql
host: "%database_host2%"
port: "%database_port2%"
dbname: "%database_name2%"
user: "%database_user2%"
password: "%database_password2%"
charset: UTF8
orm:
default_entity_manager: default
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
connection: default
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
rnr:
connection: rnr
mappings:
AppBundle: ~
With this implementation, I get the error below:
[Doctrine\ORM\ORMException]
Unknown Entity namespace alias 'AppBundle'.
Here's how I implement the function:
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$q = $em->createQueryBuilder();
$q->select('t')->from('AppBundle:TransactionSync', 't')->orderBy('t.id', 'DESC')->setMaxResults(1);
$sync = $q->getQuery()->getResult();
$em1 = $this->getContainer()->get('doctrine')->getManager('rnr');
$conn = $em1->getConnection();
$query = "SELECT id, merchant, client, phone, traderTransIdent AS member_id, transaction_id, transaction_type_id, value AS amount, points, DATE_FORMAT(STR_TO_DATE( transaction_date, '%d-%m-%Y' ), '%Y-%m-%d') AS transaction_date FROM merchant_transactions WHERE id > ". $sync->getId();
$stmt = $conn->prepare($query);
$stmt->execute();
$results = $stmt->fetchAll();
if(count($results) > 1)
{
$ts = new TransactionSync();
$ts->setStartTime(new \DateTime());
$id = 0;
foreach($results as $result)
{
$transaction_type = $em->getRepository('AppBundle:TransactionType')->find($result['transaction_type_id']);
$member = $em->getRepository('AppBundle:Member')->find($result['member_id']);
$transaction = new Transaction();
$transaction->setAmount($result['amount']);
$transaction->setPoints($result['points']);
$transaction->setClient($result['client']);
$transaction->setPhone($result['phone']);
$transaction->setTransactionId($result['transaction_id']);
$transaction->setTransactionDate(new \DateTime($result['transaction_date']));
$transaction->setTransactionType($transaction_type);
$transaction->setMember($member);
$em->persist($transaction);
$id = $result['id'];
}
$ts->setLastId($id);
$ts->setRecords(count($results));
$ts->setEndTime(new \DateTime());
$em->persist($ts);
$em->flush();
}
$output->writeln($text);
}

Unfortunately, you cannot use auto_mapping with multiple connections and you cannot map the same Bundle and/or the same alias to different connections. Doctrine is probably looking for AppBundle entities in connection default, completely ignoring rnr; to check all entities known by Doctrine run:
app/console doctrine:mapping:info
and see if your classes are listed correctly.
To workaround your problem, you need to move the TransactionSync entity out of AppBundle. For example, you can create a 'ExtEntity' directory and change your configuration to something like this:
orm:
default_entity_manager: default
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
connection: default
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
AppBundle: ~
rnr:
connection: rnr
mappings:
ExtEntityMapping:
arbitrary_key:
type: xml # or annotation/yml
dir: %kernel.dir%/../src/AppBundle/ExtEntity
prefix: AppBundle\ExtEntity\
alias: ExtEntity
To avoid some headache, you can also use the method Registry:: getEntityManagerForClass to retrieve the proper ObjectManager for the object.
For example you can do the following in your code:
$registry = $this->getContainer()->get('doctrine');
$transSyncManager = $registry->getEntityManagerForClass('ExtEntity:TransactionSync');
$transTypeManager = $registry->getEntityManagerForClass('AppBundle:TransactionType');
Going off-track, note that you are using prepared statement in a wrong way, which is prone to SQL-injection (maybe not if IDs are always generated by DBMS); use parameters like this:
$query = "SELECT id, merchant, client, phone, traderTransIdent AS member_id, transaction_id, transaction_type_id, value AS amount, points, DATE_FORMAT(STR_TO_DATE( transaction_date, '%d-%m-%Y' ), '%Y-%m-%d') AS transaction_date FROM merchant_transactions WHERE id > :minId";
$stmt = $conn->prepare($query);
$stmt->execute(array('minId' => $sync->getId());
And even better -- if you can modify your database schema -- use DateTime for transaction_date column and avoid the string conversion madness. :)

Related

Symfony 2.8 two database connections

I need to connect to an external database, I just need to do a simple query in one table so I think I dont need to create an new entity manager. I believe all configurations are set up correctly but I still don't get connected to the new database. So i´m missing something but cant find what, here are my files:
# Doctrine Configuration
doctrine:
dbal:
default_connection: default
connections:
default:
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%'
database2:
driver: pdo_mysql
host: '%database2_host%'
port: '%database2_port%'
dbname: '%database2_name%'
user: '%database2_user%'
password: '%database2_password%'
charset: UTF8
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
parameters:
#Set_Goals Database
database_host: 127.0.0.1
database_port: null
database_name: set_goals
database_user: root
database_password: null
#Database2 Database
database2_host: 127.0.0.1
database2_port: null
database2_name: second
database2_user: root
database2_password: null
Repository:
public function getAccounts(){
$conn = $this->getEntityManager()->getConnection('database2');
$sql = 'SELECT * FROM leme_account';
$stmt = $conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
Error:
An exception occurred while executing 'SELECT * FROM leme_account':
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'set_goals.leme_account' doesn't exist
set_goals database is the default connection.
Thanks in advance and sorry if I´m missing something really simple here but I´m new to Symfony and programming in general and I followed the documentation and also some related questions here but can´t make it work.
I think you need to create another entity manager
doctrine:
dbal:
default_connection: default
connections:
default:
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%'
database2:
driver: pdo_mysql
host: '%database2_host%'
port: '%database2_port%'
dbname: '%database2_name%'
user: '%database2_user%'
password: '%database2_password%'
charset: UTF8
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
default_entity_manager: default
entity_managers:
default:
connection: default
database2:
connection: database2
Then in your code
$em = $this->get('doctrine')->getManager();
$em2 = $this->get('doctrine')->getManager('database2');
The documentation mentions a mappings key but I don't know if it is mandatory, especially since you are using auto mapping.
Ref: https://symfony.com/doc/2.8/doctrine/multiple_entity_managers.html
Meanwhile, I found the problem.... it was a newb mistake. I was putting my method inside the repository of the other entity. When I added directly to the controller it worked.
Thanks for your answers.

Doctrine issue: Cannot get geometry object from data you send to the GEOMETRY field

I followed this guide to add point type to Doctrine.
This is how I define coordinates field in an Entity:
/**
* #ORM\Column(type="point", nullable=true)
*/
private $coordinates;
And this is how I'm trying to save an Entity:
$obj = new GeoPoint();
$obj->setAddress('Test');
$obj->setCoordinates(new Point(40.7603807, -73.9766831));
$manager->persist($obj);
$manager->flush();
My configuration:
doctrine:
dbal:
types:
point: Viny\PointType
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
mapping_types:
point: point
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
quote_strategy: backend_lib.orm.quote_strategy
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
Result DB column definition:
`coordinates` point DEFAULT NULL,
In result I get:
An exception occurred while executing 'INSERT INTO geo_point (address, coordinates) VALUES (?, ?)' with params ["Test", "POINT(-73.976683 40.760381)"]:
SQLSTATE[22003]: Numeric value out of range: 1416 Cannot get geometry
object from data you send to the GEOMETRY field
Final query:
INSERT INTO geo_point (address, coordinates) VALUES ('Test', 'POINT(-73.976683 40.760381)');
Your posted doctrine configuration is wrong. It should be:
#doctrine.yaml
doctrine:
dbal:
types: ### Use types instead of mapping_types
point: App\...\GeoPointType
You can tell the mapping is a problem because of your generated sql:
INSERT INTO geo_point (address, coordinates) VALUES ('Test', 'POINT(-73.976683 40.760381)');
mysql itself does not understand POINT. It needs to be wrapped in PointFromText. This wrapping is accomplished via:
// class GeoPointType
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('PointFromText(%s)', $sqlExpr);
}
And it is clearly not being called.
Here is a working example.

How to create a Second database connection with Symfony2?

I'm trying to connect a second database to my project in Symfony2. First, I added into parameters.yml some parameters to create the connection.
Then, I edited my config.yml, and now looks like:
doctrine:
dbal:
default_connection: default
connections:
default:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
circutor3:
driver: pdo_sqlsrv
host: "%database_host_circutor3%"
port: "%database_port_circutor%"
dbname: "%database_name_circutor%"
user: "%database_user_circutor3%"
password: "%database_password_circutor3%"
charset: UTF8
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
Finally, I tried to get connected, using the following code in my controller:
$em = $this->getDoctrine()->getManager('circutor3');
And, the error returned by Symfony2:
Doctrine ORM Manager named "circutor3" does not exist.
The circutor3 makes connection to a database, external to my system, so I don't need to create entities or objects. I only need to execute some SELECT to get information and store it using an array.
Is creating a typical mysqli connection the best way to solve my problem?
I don't know how to solve this with Symfony.
Thank you in advance.
you could access to the database connection in the controller as follow:
$connection = $this->getDoctrine()->getConnection('circutor3');
then use the connection as:
$stmt = $connection->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
Some help here and here
Hope this help
According to Symfony documentation (http://symfony.com/doc/current/cookbook/doctrine/multiple_entity_managers.html), you only defined a connection, not an entity manager :
You have to create an entity_manager for each connection.
orm:
default_entity_manager: default
entity_managers:
default:
...
circutor3:
connection: circutor3
mappings:
AppBundle: ~

Mapping entities across 2 (or more) databases

Context
I'm developing a website where I have to use data from two distinct databases (one local with full access, one external on read only).
One of "local" entities needs mapping to an "external" entity.
The external entity won't have its data changed since I can't persist these to the DB anyway.
Question
Is there a way to mark this mapping so that the external entity is pulled along when I retrieve the local entity ?
Short answer, No.
You can setup multiple database connections and use the same entity classes for both of them. But a single entity will not be able to have properties that map to different databases. You may have reference fields on there but those will need to just be keys that you can use to look them up using the other connection. For example imagine the following setup:
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%'
charset: UTF8
customer:
driver: '%database_driver2%'
host: '%database_host2%'
port: '%database_port2%'
dbname: '%database_name2%'
user: '%database_user2%'
password: '%database_password2%'
charset: UTF8
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AcmeBundle: ~
customer:
connection: customer
mappings:
AcmeBundle: ~
Both managers will use the entity classes in the AcmeBundle. Then you can do something like
public function someControllerAction(){
// Get customer from the default connection
$customer = $this->getDoctrine()
->getManager() // If no value is provided the default is implied
->getRepository('AcmeBundle:Customer')
->findOneBy([
'id'=>12
]);
// Get the customers details from another connection
$customerDetails = $this->getDoctrine()
->getManager('customer')
->getRepository('AcmeBundle:CustomerDetails')
->findOneBy([
'customer_details_id' => $customer->getDetailsId()
]);
...
}

Symfony2 and Doctrine APC Cache

i have read the documentation of symfony2 in relation to the performance and I have realized the following steps.
Install APC 'php-apc' on my webserver and restart my webserver
Modify my doctrine configuration
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
Now if i call a action to retrieve all users from database i see in the information bar at the bottom that doctrine execute every time 114 queries. Why the queries not cached?
My action look like this:
$users = $this->getDoctrine()->getRepository('AppUserBundle:User')->findAll();
return $this->render('AppUserBundle:User:index.html.twig', array('users' => $users));
Doctrine doesn't cache query results by default. You have to explicitly point that you want to cache query using useResultCache method. For example, if you'd like to cache getting all users, write your own method in User repository class:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function fetchAll()
{
$query = $this->createQueryBuilder('u')->getQuery();
return $query->useResultCache(true)->getResult();
}
}
The method may take additional arguments:
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
$bool - set to true if you want to cache query result
$lifetime - TTL of cached result in seconds
$resultCacheId - you can pass your own id, in case of null Doctrine will handle that

Resources