Symfony2 Doctrine Metadata Cache with Redis Issue - php

I'm trying to use Redis as a driver for caching doctrine metadata, query and results. Follwing is my configuration.
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
result_cache_driver:
type: redis
host: %redis_host%
instance_class: Redis
query_cache_driver: redis
#metadata_cache_driver: redis
When I remove the comment from line #metadata_cache_driver: redis, I get an error running a test I have with following error.
TypeError: Argument 1 passed to Doctrine\ORM\Mapping\ClassMetadataFactory::wakeupReflection() must implement interface Doctrine\Common\Persistence\Mapping\ClassMetadata, string given, called in vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php on line 214
My Functional Test looks like Following:
public function testX()
{
//The data in prepared in setup..
$param1 = 'test-id';
$param2 = 'test-key';
$result = $this->em->getRepository('MyBundle:Test')
->findOneByXX($param1, $param2);
$this->assertTrue($result instanceof Test);
}
And My Query looks like following:
$qb->select('c')
->from('MyBundle:Test', 'c')
->where('c.id = :id')
->andWhere('c.key = :key')
->setParameter('id', $id)
->setParameter('key', $key);
$query = $qb->getQuery()
->useResultCache(true);
return $query->getOneOrNullResult();
Do I need additional configuration for Redis? Any Help would be appreciated??

I believe to resolve this you need to set the serializer for redis, the default serializer is probably not aware of the the PHP classes and when the object is removed from cache, and unserialized, it is not the same type as it was prior to serialization.
$redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
For you case you will probably need to set the configuration option as a part of the driver configuration.

Related

Symfony SQLSTATE[42S22]: Column not found

I have an error that i don't understand, few days ago i create a new project for trying resolving this error in a project with fewer files and i solve it, but the solution didn't work in my Real project.
( This error is related to an other question i asked few day ago : The class was not found in the chain configured namespaces, but like i said i solve it so maybe i need to ask a new question ).
I tried to implement two EntityManager in my project for multple database connexion,
and that is the problem, oneof my entityManager searched tables in the wrong place, and don't find theme ... IDK why and most crazy thing it's some function work and other didn't, you'll see that :
In first my doctrine.yaml file :
doctrine:
dbal:
default_connection: default
connections:
default:
# configure these for your database server
url: '%env(resolve:DATABASE_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
customer:
# configure these for your database server
url: '%env(resolve:DATABASE_CUSTOMER_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
orm:
default_entity_manager: default
entity_managers:
default:
mappings:
Tdsmo:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Tdsmo'
prefix: 'App\Entity\Tdsmo'
alias: Tdsmo
customer:
connection: customer
mappings:
Customer:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Customer'
prefix: 'App\Entity\Customer'
alias: Customer
And know an exemple of some function that i can use in my controller :
// this one work, but his not the default connexion ..
// return all agent in agentCustomer that is in the second database.
$agentCustomers = $this->getDoctrine()
->getRepository(AgentCustomer::class, "customer")
->findAll()
;
// And this one, that is the default connexion, didn't work like this same for 'AgentRepository $agentRepository' that can be implemented in the method and called after for ->findAll()
// return an error
$agents = $this->getDoctrine()
->getRepository(Agent::class, "default")
->findAll()
;
BUT This shit work and return a array with all column and values :
$connexion = $this->getDoctrine()->getConnection('default')
$agentsFetch = $connexion->fetchAll("SELECT * from agent");
But i can't use the fetch method 'cause if i do in my template i need to replace all agent.firstName by agent.first_name for example, Furthermore i need to understand why one method work but not the other ...
Finally, the full error is :
An exception occurred while executing 'SELECT t0.id AS id_1, t0.email AS email_2, t0.roles AS roles_3, t0.password AS password_4, t0.firstName AS firstName_5, t0.lastName AS lastName_6, t0.registeredAt AS registeredAt_7, t0.telephone AS telephone_8, t0.fonction_id AS fonction_id_9, t0.subdivision_id AS subdivision_id_10, t0.section_id AS section_id_11 FROM Agent t0':
SQLSTATE[42S22]: Column not found: 1054 Champ 't0.firstName' inconnu dans field list
Thank you for your help !
OKAY
Thanks to Nicolai, he help me found the real problem, it was because of the naming strategy !
I don't know why but if it is not explicitly defined in the context of the use of several entityManager doctrine will generate the tables in the database in camelCase, instead of the snake_case (underscore).In any case that's what was causing me problem
So it must be specified with this line :
naming_strategy: doctrine.orm.naming_strategy.underscore
For where i found to specify 'underscore' :
How to configure naming strategy in Doctrine 2
And the doc For thoses who want :
https://symfony.com/doc/current/bundles/DoctrineBundle/configuration.html#configuration-overview

Is it possible to use two diffrent document managers with different databases for the same bundle in Symfony?

My goal is to have two different document managers connected to different databases that share the same database models.
I have heavily changed my database model and I would like to write a custom migration script that retrieves objects from the old model reads its values and then creates a new object on the new schema with the information of the old object.
I have found a related stackoverflow question here:
Working with two entity managers in the same bundle in Symfony2
Howewer, this solution suggests to use different prefixes for each database and it stores the classes for the data model in different folders:
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
second:
driver: %database_sqlite_driver%
host: ~
port: ~
dbname: %database_sqlite_shop_name%
path: %database_sqlite_shop_name%
user: ~
password: ~
charset: UTF8
orm:
auto_generate_proxy_classes: %kernel.debug%
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
YourBundle:
# you must specify the type
type: "annotation"
# The directory for entity (relative to bundle path)
dir: "Entity/FirstDb"
#the prefix
prefix: "Your\Bundle\Entity\FirstDb"
shop:
connection: second
mappings:
YourBundle:
type: "annotation"
#here the second path where entity for the connection stand
dir: "Entity/SecondDb"
#the prefix
prefix: "Your\Bundle\Entity\SecondDb"
I would really just like to have two different document manager objects that share the same models in the same folder but are connected to diffrent databases. Is this possible?
I found out that it is indeed possible to be connected to different databases in the same Symfony Bundle. This answer led me to a possible solution:
https://stackoverflow.com/a/15110867/2174832
#services.yml
acme_app.dynamic_connection:
class: %acme.dynamic_doctrine_connection.class%
calls:
- [setDoctrineConnection, #doctrine.dbal.default_connection]]
<?php
namespace Acme\Bundle\AppBundle;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
use Exception;
class DynamicDoctrineConnection
{
/**
* #var Connection
*/
private $connection;
/**
* Sets the DB Name prefix to use when selecting the database to connect to
*
* #param Connection $connection
* #return SiteDbConnection $this
*/
public function setDoctrineConnection(Connection $connection)
{
$this->connection = $connection;
return $this;
}
public function setUpAppConnection()
{
if ($this->request->attributes->has('appId')) {
$connection = $this->connection;
$params = $this->connection->getParams();
// we also check if the current connection needs to be closed based on various things
// have left that part in for information here
// $appId changed from that in the connection?
// if ($connection->isConnected()) {
// $connection->close();
// }
// Set default DB connection using appId
//$params['host'] = $someHost;
$params['dbname'] = 'Acme_App'.$this->request->attributes->get('appId');
// Set up the parameters for the parent
$connection->__construct(
$params, $connection->getDriver(), $connection->getConfiguration(),
$connection->getEventManager()
);
try {
$connection->connect();
} catch (Exception $e) {
// log and handle exception
}
}
return $this;
}
}
With the solution above one can write a service that can be called to change the database that the current entity manager is connected to to a different database.
Unfortunately the solution above only works for the PDO driver (Mysql).
Since our technology stack includes mongodb and we use the doctrine-mongodb bundle I had to look for a differnt solution.
The doctrine-mongodb documentation has a section about setting up a custom document manager here:
http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/introduction.html
I was unable to get the right doctrine-mongodb odm document mappings to work with the instructions in the doc and therefore the only solution for me was to create a simple PHP mongoclient connection:
$mongo = new \Mongo('mongodb://localhost:27017');
$legacyDbDocumentMangager = $mongo->selectDB('backed_up_prod_db');
$legacyUserCollection = $legacyDbDocumentMangager->selectCollection('User');
$user = $legacyUserCollection->findOne(array('email' => 'matyas#stackoverflow.com'));
The only differnce between this simple php mongodb driver and the doctrine-mongodb odm is that the results of the queries of this driver are associative arrays and the results of the doctrine-mongodb odm diver are objects.
I hope this information is useful to someone.

DQL in config.yml not overwritten in config_test.yml

EDIT: Original title" Text environment: "Function "year" does not supported for platform "sqlite""
Incorporating beberlei\DoctrineExtensions in the test environment results in
Uncaught PHP Exception Doctrine\ORM\Query\QueryException: "[Syntax
Error] Function "year" does not supported for platform "sqlite""...
composer show -i includes
beberlei/DoctrineExtensions v1.0.5
The extensions are installed: they exist at "...vendor\beberlei\DoctrineExtensions".
The month() function does NOT throw an error.
config_test.yml
doctrine:
dbal:
default_connection: test
connections:
test:
driver: pdo_sqlite
path: %kernel.cache_dir%/test.sqlite
orm:
dql:
string_functions:
Soundex: Truckee\VolunteerBundle\DQL\Soundex
month: DoctrineExtensions\Query\Sqlite\Month
datetime_functions:
year: DoctrineExtensions\Query\Sqlite\Year
config.yml DQL
dql:
string_functions:
Soundex: Truckee\VolunteerBundle\DQL\Soundex
numeric_functions:
month: Oro\ORM\Query\AST\Functions\SimpleFunction
year: Oro\ORM\Query\AST\Functions\SimpleFunction
function call
public function expiringOppsNotSent()
{
$nextMonth = date_add(new \DateTime(), new \DateInterval('P1M'));
$expiryMonth = date_format($nextMonth, 'm');
$expiryYear = date_format($nextMonth, 'Y');
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('o')
->from('TruckeeVolunteerBundle:Opportunity', 'o')
->leftJoin('TruckeeVolunteerBundle:AdminOutbox', 'a', 'WITH', $qb->expr()->eq('a.oppId', 'o'))
->andWhere($qb->expr()->eq('month(o.expireDate)', ':month'))
->andWhere($qb->expr()->eq('year(o.expireDate)', ':year'))
->andWhere('a.id is NULL')
->setParameter(':month', $expiryMonth)
->setParameter(':year', $expiryYear)
;
$notSent = $qb->getQuery()->getResult();
return $notSent;
}
Edit:
The error does not occur if the dql in config.yml (as shown above) are commented out!
Edit #2:
Error persists in functional test with app_dev.php created.
Functional test uses Liip\FunctionalTestBundle\Test\WebTestCase
The correct namespace for the Sqlite Plugin is:
DoctrineExtensions\Query\Sqlite\Year
Update your config.yml to include:
doctrine:
orm:
dql:
datetime_functions:
year: DoctrineExtensions\Query\Sqlite\Year
I wish I had a rational explanation for why the error(s) have disappeared. I experimented with the order and labeling DQL to no effect. I deleted and re-entered the entries and now no errors. Best guess is there was at least one non-display character in an entry. For now all tests pass in SQLite!

Access Profiler without a request on a PHPUnit Symfony2 project

How can I access the profile on a unit test context where there is no request?
In my case, I'm making tests to a data access layer that uses some other DBAL, doctrine2 in my case, and I want to assure that only a certain number of queries are made for performance issues. Don't want to put http requests in this scenario.
If I just do code below the collectors don't collect anything, and at the last line an exception is throw because getQueryCount it tries to access a data member set to null:
public function testQueryCount() {
/* #var $profile Profiler */
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$profile = $this->getContainer()->get('profiler');
$profile->enable();
$eventDataAccess = new EventDataAccess($em);
$e = $eventDataAccess->DoSomething(1);
$a = $profile->get('db');
$numMysqlQueries = $a->getQueryCount();
$this->assertCount(1, $numMysqlQueries)
}
I've got the profiler in the config_test.yml with:
doctrine:
dbal:
connections:
default:
logging: true
framework:
profiler:
only_exceptions: false
collect: true

Symfony/Propel 1.4: Read from one, write to other DB

We have an existing project (SNS website+android/Iphone games) in Symfony 1.4/ Propel 1.4
We are experiencing extra load on DB server (say DB1). We are doing DB Optimization but as immediate solution we decided to create one more DB server in the way DB2 is exact replica of DB1 all the time. Currently we have only DB1, used for both read and write operations.
Now we need to move all read operations to DB2 and keep write operations (generally in transactions) on DB1 as it is now.
What are the possible ways to make those changes (On production server without much downtime) and if possible, with minimal code changes.
Edit after first comment
Based on link given by J0k and some other links, I'd done following on local dev environment.
Created a test symfony 1.4 project
Updated database.yml as follow
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: root
password: mysql
encoding: utf8
persistent: true
pooling: true
slaves:
slave1:
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: root
password: mysql
encoding: utf8
Where database wzoslv is exact replica of database wzo except change in one test entry. On table odd_play row 26 (PK) column result entries are WON1 and WON respectively.
run symfony tasks
php symfony propel:build-schema
php symfony propel:build-model
php symfony cc
Created a module and added following code:
class wzoActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$con_write = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
$con_read = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_READ);
$oddPlay = OddPlayPeer::retrieveByPK(26,0,$con_write);
echo "on write connection, result=".$oddPlay->getResult();
$oddPlayRead = OddPlayPeer::retrieveByPK(26,0,$con_read);
echo "<br/>on Read connection, result=".$oddPlayRead->getResult();
exit;
$this->setLayout('layout');
}
}
Run http://local.sftest.com/index.php/wzo/index in the browser, output was,
on write connection, result=WON //Correct expected output
on Read connection, result=WON //Not correct. That should be WON1
I guess passing OddPlayPeer::DATABASE_NAME while creating both read/write connection is the issue but that how it was suggested in online examples. Can someone please suggest where I'm making the mistake?
Edit: Few more input
I updated debug echos in lib\vendor\symfony\lib\plugins\sfPropelPlugin\lib\vendor\propel\Propel.php to check how it is returning the connection. Found that it is entering in following if (line 544-549)
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
if (empty($slaveconfigs)) {
echo "inelseifif<br/>";// no slaves configured for this datasource
self::$connectionMap[$name]['slave'] = false;
return self::getConnection($name, Propel::CONNECTION_WRITE); // Recurse to get the WRITE connection
}
where $slaveconfigs are empty so returning write connection. Now the question is, why slaveconfigs is empty?
I also try editing sfDatabaseConfigHandler.class.php as defined in old forums but doing so, break symfony somewhere and nothing gets display on web and even in logs.
I'm sure I'm doing some mistake but whatever suggested on official documents of Propel/symfony and even here at stackoverflow, seems not working for me. Probably official documents should take better care of programmers who do not have lot of symfony experience.
Although we do not prefer to edit core files of any framework/third party libraries but this force me to edit core files to make a working solution for me. The solution that worked for me is as follow:
database.yml
My database.yml file is as follow:
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: testuserwzo
password:
encoding: utf8
persistent: true
pooling: true
slave:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: testuserwzoslv
password:
encoding: utf8
persistent: true
pooling: true
After that, I edited Propel.php file as follow
For Propel 1.4
File: lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/vendor/propel/Propel.php
Change line 542-543
// we've already ensured that the configuration exists, in previous if-statement
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
with (added one line inbetween)
// we've already ensured that the configuration exists, in previous if-statement
self::$configuration['datasources'][$name]['slaves'] = isset(self::$configuration['datasources']['slave']) ? self::$configuration['datasources']['slave'] : null;
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
Then in same file, changed line 560
$con = Propel::initConnection($conparams, $name);
to
$con = Propel::initConnection($conparams, 'slave'); //I know its bad practive to put hard-coded value but at that moment, I was more interested in working solution without caring about best practices.
For propel 1.6 (We upgraded propel just to make this working but reverted back to propel 1.4 later as upgrade on production needs to be well tested.)
File: plugins/sfPropelORMPlugin/lib/vendor/propel/runtime/lib/Propel.php
Changed line 601
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
to (Added one line before)
self::$configuration['datasources'][$name]['slaves'] = isset(self::$configuration['datasources']['slave']) ? self::$configuration['datasources']['slave'] : null;
$slaveconfigs = isset(self::$configuration['datasources'][$name]['slaves']) ? self::$configuration['datasources'][$name]['slaves'] : null;
Then in same file, changed line 629
$con = Propel::initConnection($conparams, $name);
to
$con = Propel::initConnection($conparams, 'slave');
Then following test file was giving expected result
class kapsActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
$con_write = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
$con_read = Propel::getConnection(OddPlayPeer::DATABASE_NAME, Propel::CONNECTION_READ);
$oddPlay = OddPlayPeer::retrieveByPK(28,0,$con_write);
echo "on write connection, result=".$oddPlay->getResult().$oddPlay->getPlayscore();
$oddPlayRead = OddPlayPeer::retrieveByPK(27,0,$con_read);
echo "<br/>on Read connection, result=".$oddPlayRead->getResult().$oddPlayRead->getPlayscore();
exit;
//$this->setLayout('layout');
}
}
I still do not recommend editing core files but this solution worked for us as in emergency condition. Someone else, if needed, may use it in emergency condition. Still looking for perfect solution.
You should add a new level to slaves plus a connection entry, according to the doc (on github)
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=wzo;'
username: root
password: mysql
encoding: utf8
persistent: true
pooling: true
slaves:
connection:
slave1:
dsn: 'mysql:host=localhost;dbname=wzoslv;'
username: root
password: mysql
encoding: utf8

Categories