I have a unittests in my Symfony 4 project which show a inconsistent behaviour and I suspect the doctrine caching is responsible for this. So I am looking for a way to disable caching for Unittests.
My current packages/test/doctrine.yaml file for the environment test looks like this:
doctrine:
orm:
auto_generate_proxy_classes: false
metadata_cache_driver:
type: service
id: doctrine.system_cache_provider
query_cache_driver:
type: service
id: doctrine.system_cache_provider
result_cache_driver:
type: service
id: doctrine.result_cache_provider
services:
doctrine.result_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '#doctrine.result_cache_pool'
doctrine.system_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '#doctrine.system_cache_pool'
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system
If I want to disable any caching in doctrine for tests, is there any cache driver, which is a non-caching dummy? If I leave those entries empty I guess my prod settings or the general settings would be used, which means the results would also be cached. Can I explicitly non-cache? I hope I would not have to write an explicit dummy cache provider, who doesn't cache? This could be a solution, but it looks to me like this should be achieved more easily.
config
\__packages
|
|__ prod
| \__doctrine.yaml
|
|__ dev
| \__doctrine.yaml
|
|__ test
| \__doctrine.yaml
|
\__doctrine.yaml
This is my extended KernelTestCase class:
protected EntityManagerInterface $em;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
$kernel = static::createKernel();
$kernel->boot();
$em = $kernel
->getContainer()
->get('doctrine')
->getManager();
$schemaTool = new SchemaTool($em);
$metadata = $em
->getMetadataFactory()
->getAllMetadata();
// Drop and recreate tables for all entities
$schemaTool->dropSchema($metadata);
$schemaTool->createSchema($metadata);
}
protected function setUp(): void
{
parent::setUp();
self::ensureKernelShutdown();
$kernel = static::createKernel();
$kernel->boot();
$this->em = $kernel
->getContainer()
->get('doctrine')
->getManager();
// loads tests data for every tests
$projectDir = $kernel->getProjectDir();
system('php ' . $projectDir . '/bin/console doctrine:fixtures:load --env=test -n -q');
}
....
protected function tearDown(): void
{
parent::tearDown();
if (null !== $this->em) {
$this->em->close();
}
}
cache configuration
If I leave those entries empty I guess my prod settings or the general settings would be used
No, the settings from prod will never ever be used in test environment. Test uses the configuration from config/packages/test/ and does fallback to the general settings in config/packages/ but never to prod.
In a typical setup, general doctrine stuff like mappings are defined in config/packages/doctrine.yaml, cache is only defined in config/packages/prod/doctrine.yaml. See official symfony demo application
You can also verify your configuration using bin/console debug:config doctrine --env=test.
test isolation
Your problem is most probably not caused by doctrine cache configuration but by poor isolation of your testcases.
Have a look at Functional Testing of A Doctrine Repository. Symfonys official docs recommend to boot the kernel at setUp() and to close the entity manager during tearDown().
If that complete isolation is not a feasible approach for you, you will have to debug to find out which testcase causes the breach of isolation. If you pinned the causing testcase you should check its execution path and look out for missing doctrine flushes and for transactions that are left open longer than expected.
Thanks to the advice of everybody here it looks like I have finally fixed my problem. At least all tests are green in now 40 runs.
What I did:
Before I was manually loading my fixtures for each test and experimented with recreating the db for each testclass and then for each test.
I threw that away completely and now rely solely on the Dama Bundle and the transaction rollback to get unmodified test data.
dama_doctrine_test:
enable_static_connection: true
enable_static_meta_data_cache: false
enable_static_query_cache: false
In the configuration of the Dama Bundle I did set the caches for the queries and meta data to false. I am not sure if this is necessary though. But since it didn't slow down my tests I keep it.
This combination finally did do the trick.
Thanks to #domis86 #simon.ro for your support and patience!
https://symfony.com/doc/4.4/testing/database.html#resetting-the-database-automatically-before-each-test
https://github.com/dmaicher/doctrine-test-bundle
Related
I have to persist new entities when app users make modifications on a bunch of existing entities (traceability issue). I created an EventListener on Doctrine onFlush event. The problem is: This is not supposed to happen on fixtures loading.
I did this to prevent fixtures from triggering the listener but I wonder if it is a good solution:
In my services.yaml:
App\DataFixtures\:
class: App\DataFixtures\LoadFixtures
tags: [name: doctrine.fixture.orm]
arguments:
- '#doctrine.orm.default_entity_manager.event_manager'
In my App\DataFixtures\LoadFixtures:
public function __construct(EventManager $eventManager)
{
$this->eventManager = $eventManager;
}
public function load(ObjectManager $manager)
{
$historizationManager = null;
foreach ($this->eventManager->getListeners() as $event =>$listeners){
foreach ($listeners as $key => $listener){
if($listener instanceof HistorizationManager){
$historizationManager = $listener;
}
}
}
if($historizationManager){
$this->eventManager->removeEventListener(array('onFlush'),$onFlushHistoryListener);
}
// doing some work
}
This is the simplest solution I've come with, please let me know if there is something wrong with that.
If this is still an issue, you could try the following:
add an enabled flag to the EventSubscriber class, default true
use autowiring to make that subscriber available in your fixture loader
set the flag to false before loading the first fixture
finally, check for that flag before handling the event
But if you've found a better way, I'd be happy to hear from you
To clarify about how to set the flag to false: you are usually running your tests in the test environment. By using a configuration file like config_test.yaml, you can override the default service configuration like:
services:
App\EventSubscribers\YourSubscriber:
calls:
- ['setEnabled', [false]]
I activated user confirmation for FOSUserBundle. But I don't want to take the response from the original listener
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
I want to chose another route. I tried to extend the EventListener
namespace Acme\MyBundle\EventListener;
use FOS\UserBundle\EventListener\EmailConfirmationListener as BaseListener;
// ...
class EmailConfirmationListener extends BaseListener
{
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
}
Unfortunately, EventListeners don't seem to be extendable, just as Controllers or Forms are. (Just in case you wonder: of course my bundle is a child of the FOSUserBundle.)
So I want to avoid editing those two lines directly in the vendor folder (as it would be very bad practice to do so!). So what are my ways out of this calamity?
Just override the service fos_user.listener.email_confirmation by creating a service with the same name in your config.yml ...
# app/config/config.yml
services:
fos_user.listener.email_confirmation:
class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
arguments: ["#fos_user.mailer", "#fos_user.util.token_generator", "#router", "#session"]
tags:
- { name: kernel.event_subscriber }
... or even cleaner - create a parameter that's being used by your service:
parameters:
my.funky_parameter.class: "Acme\MyBundle\EventListener\EmailConfirmationListener"
services:
fos_user.listener.email_confirmation:
class: "%my.funky_parameter.class%"
# ...
... or inside your bundle's xml/yml/php configuration file loaded by the bundle's extension. Make sure your bundle is being registered after FOSUserBundle in AppKernel.php when choosing this way.
... or the best method:
change the original service's class name in a compiler pass as the documentation chapter How to Override any Part of a Bundle suggests.
Maybe take a dive into the chapter How to work with Compiler Passes before choosing this option.
I have a plugin that has a bunch of domain classes. This plugin is used by multiple applications.
My problem is that I can't specify the mapping->cache setting in the domain classes themselves (as they need to have different values depending up the application that uses them). For example, in Application A, I'd like to have domain class X read-only cached, and domain class Y not cached. In Application B, I'd like to have domain class X transactional cached, and domain class Y read-only cached.
What I'd like (I'm hoping this is already available) is something like:
grails.gorm.default.mapping { cache true }
But instead of being global, I can apply to just a specific domain class, something like:
grails.gorm.com.integralblue.domain.User.mapping { cache true }
Someone had suggested having each domain class checking the grails config, something like:
static mapping = {
cache: Holders.grailsApplication.config.com.package.Person.cache ?: false
}
And the Config:
com.package.Person.cache = true
but I'd like to avoid that if possible
Thanks!
The approach which you have mentioned should be used in the ideal case scenario.
I agree that changes has to be made in the plugin and the application as well.
Ideally, domain classes in the plugin should provide a lenient mapping which can be overriden.
If you have used grails.gorm.default.mapping { cache true } in the plugin or cache: true or which ever mapping in the domain class, then it can easliy be overriden in the application according to need. For example:
//Domain class in Plugin
class Person{
static mapping = {
cache: 'read-only'
}
}
Since mapping is nothing but a static block in a groovy object it can easily by metaClassed in concerned application at runtime like
//Application A:
Person.metaClass.'static'.mapping = {
cache 'transactional'
}
//Application B:
Person.metaClass.'static'.mapping = {
cache 'read-write'
}
(Untested)
If you want to do the same thing collectively for all the domain classes then the domain class artefact can be used as below
//Application A BootStrap
grailsApplication.domainClasses.each{
it.clazz.metaClass.'static'.mapping = {cache 'transactional'}
}
selectively:
//Application A BootStrap
def personClazz =
grailsApplication.domainClasses.find{it.clazz.simpleName == 'Person'}.clazz
personClazz .metaClass.'static'.mapping = {cache 'transactional'}
*Either of the cases you have to do some modification in Applications using the plugin.
I am using Symfony2 and when i try to generate the schema ($ php app/console doctrine:generate:schema) i got an error..
[Doctrine\ORM\Mapping\MappingException]
No mapping file found named 'xxx.UserBundle.Entity.User.php' for class 'xxx\UserBundle\Entity\User'.
I only have 2 Bundles in the proyect:
UserBundle
FileBundle
I connect the FileBundle with the UserBundle with this code:
/**
* #ORM\ManyToOne(targetEntity="xxx\UserBundle\Entity\User")
**/
protected $user;
The headers of the files are something like this:
namespace xx\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
**/
class User
{ ###...###}
FileBundle is very similar..
Thanks!
You are mixing Doctrine mapping formats, you have annotations and at least one XML file in Resources/config/doctrine
From the symfony docs:
"A bundle can accept only one metadata definition format. For example,
it's not possible to mix YAML metadata definitions with annotated PHP
entity class definitions."
So the solution is:
You cannot mix different Doctrine mapping formats in a given bundle. So either use annotations for all entities or use XML for all.
It is somehow strange to me that you're using PHPDriver for ORM and not AnnotationDriver, since your database info in classes is in annotations.
Anyhow, if php app/console doctrine:mapping:info command gives you only 1 entity that means that your other bundle containing User class is not loaded in app/AppKernel.php file. Load your UserBundle by adding line
new xxx\UserBundle\xxxUserBundle(),
to the $bundles array in registerBundles() function. After that, this function should look something like this:
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
new xx\FileBundle\xxFileBundle(),
new xxx\UserBundle\xxxUserBundle(),
);
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
}
return $bundles;
}
Of course, change 'xx' and 'xxx' with your real names.
Hope this helps.
I agree with #ilanco 100%. In addition, the solution is to REMOVE any .xml file in folder, for example:
C:\xampp\htdocs\localxyz\src\AppBundle/Resources/config/doctrine/Comment.orm.xml
these xml files created when you run such command:
C:\xampp\htdocs\localxyz>php app/console doctrine:mapping:import --force AppBundle xml
Dung.
Good day,
Although I know that this has been posted years ago, but will just like to post my answer here, perhaps it could help someone :)
just simply clear the cache, it works for me though
php bin/console cache:clear
Thanks
I can't think of a reason why this is happening but I will take a leap here.
Try editing your config.yml. doctrine section should look like this:
doctrine:
dbal:
default_connection: my_connection_name
connections:
fmefb:
host: %database_host%
dbname: %database_name%
user: %database_user%
password: %database_password%
driver: %database_driver%
port: %database_port%
charset: UTF8
orm:
default_entity_manager: my_entity_manager
entity_managers:
my_entity_manager:
connection: my_connection_name
mappings:
CompanyNameSiteBundle: ~
CompanyNameAdminBundle: ~
CompanyNameSomethingElse: ~
Notice the mappings list on the bottom. All bundles which are "linked" are included here.
Hope this helps....
I'm binding Memcache to Doctrine and it seems I have to useResultCache explicitly in every query. Is it possible to make it true by default, with the ability to useResultCache(false) where it's not needed?
Create a wrapper class/function that explicitly sets useResultCache(true) and use that everywhere instead of the native function.
I know this question is old, but I'll write up the best answer that comes into my mind.
1) Abstract away your dependency to interface ( i.e. - use dependency injection pattern to inject EntityManager into your class that creates queries and use EntityManagerInterface instead )
Now, either:
a) [ Better, but longer ] Create a new composition-related implementation for EntityManagerInterface, that will proxy calls to original entityManager and will set result cache flag to true:
<?php
class CachedEntityManager implements EntityManagerInterface {
private $proxiedManager;
public function __construct(EntityManagerInterface $proxiedManager) {
$this->proxiedManager = $proxiedManager;
}
public function createQuery($dql = '') {
$query = $this->proxiedManager->createQuery($dql);
$query->useResultCache(true);
}
[... proxy all the calls forth to proxiedManager ...]
}
b) [ Not as good, but shorter ] Extend the EntityManager class and override the createQuery. Remember that this in general is not a good practice and you should definitely not write anything in that class anymore but instead refactor into a) :
<?php
class CachedEntityManager extends EntityManager {
public function createQuery($dql = '') {
$query = parent::createQuery($dql);
$query->useResultCache(true);
}
}
You can hack the Doctrine core a little by setting the default value of $_useResultCache to TRUE in \Doctrine\ORM\AbstractQuery. This will make all queries use the resultCacheDriver by default, and you can easily turn the cache off for individual queries using $query->useResultCache(FALSE)
It's a useful little hack that saves you a lot of typing, but be careful; I've found that the caching driver won't cache lazy-loaded associations that haven't been initialized (which is obvious now I think about it). Sometimes it's safer to just turn result caching on for each individual query.