Symfony2 / Doctrine - create entity only in the test environment - php

I want to create some entities that would run only in test environment, during my unit tests. I dont think that there is some ebbded solution (am I wrong?), so probably another way is to create entities in the test folderand use them.
But there is something that I dont understand. Symfony sets the BundleName\Entity folders as folders where entities are, and Tests/Entity folder will not work with my entities. So, how do I explicitly set my Tests/Entity folder to work (read/install/register entities) in my test case? I assume this is made by configurating of doctrine entity manager?

Found this function in one of my projects, may be it can help you a bit. This function create new EntityManager, where you can define your entities namespace.
/**
* #return EntityManager
*/
public static function createTestEntityManager()
{
if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
self::markTestSkipped('This test requires SQLite support in your environment');
}
$config = new \Doctrine\ORM\Configuration();
$config->setEntityNamespaces(array('SerializerBundleTests' => 'Top10\SerializerBundle\Tests\Entity'));
$config->setAutoGenerateProxyClasses(true);
$config->setProxyDir(\sys_get_temp_dir());
$config->setProxyNamespace('SerializerBundleTests\Entity');
$config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader()));
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache());
$params = array(
'driver' => 'pdo_sqlite',
'memory' => true,
);
return EntityManager::create($params, $config);
}

I may be wrong, but I think that Doctrine's entity manager doesn't allow you to add mapping for additional entities on the fly.
As a "crutchy" workaround, you could create an additional bundle that contains entities you want to use with your tests, then leave your Symfony2 config for the test environment the same (if you have "auto_mapping" set to true; see this reference document), while changing you orm config for dev and prod environment. You'll need to disable auto mapping and specify the bundles explicitly, excluding the bundle with your test-only entities from "mappings".
Or maybe you don't even need to specify the value of "auto_mapping". The relevant part of your config ("entity_managers", "mappings") for the dev\prod environment should look somewhat similar to the one in the cookbook entry.
Again, I wish to emphasize that I'm not 100% sure that this answer is correct, as my knowledge of Symfony2 has become a bit fuzzy.

Related

Symfony registering services with ContainerControllerResolver

I am trying to use the Symfony dependency injection component and I was just wondering if anyone would be able to help with the registering of the services.
I have recently moved to ContainerControllerResolver opposed to ControllerResolver so that my routes correctly instantiate the dependencies.
This is working well enough however I have ran into an issue when a class is needed more than once.
So for ContainerControllerResolver to work you seem to have to use the full class path. Where as when you use ControllerResolver you can set your own unique string ID.
I can't see anyway to set a unique ID when using ContainerControllerResolver and you don't seem able to pass arguments to the Reference class.
Is there anyway I could rewrite the below so they are seperate instances? The ControllerResolver way of being able to set unique IDs makes sense to me but I'm just a little lost when you have to pass the full class.
$containerBuilder->register(App\Models\Database::class, App\Models\Database::class)->setArguments([
DB1, DB1USER, DB1PASS
]);
$containerBuilder->register(App\Models\News::class, App\Models\News::class)->setArguments([
new Reference(App\Models\Database::class)
]);
$containerBuilder->register(App\Models\Database::class, App\Models\Database::class)->setArguments([
DB2, DB2USER, DB2PASS
]);
$containerBuilder->register(App\Models\Reports::class, App\Models\Reports::class)->setArguments([
new Reference(App\Models\Database::class)
]);
I think I got the wrong gist of things from another post. They mentioned it is more ideal to use the controller name in the ID when using the ContainerControllerResolver. I've been able to use service IDs in the first param of the register and I've just updated my routes _controller param to go to that same name rather than the class namespace path:
$routes = new RouteCollection();
$routes->add('news', new Route('/news', [
'_controller' => 'controller.news::newsOutput',
]));

Symfony : Detached entity cannot be removed in test suite w/ phpunit

I'm actually doing some functionnal testing on my api and I'm facing a problem which I don't really understand.
I want to test an API which interact with a remote web hosting server. The goal is to manage VirtualHosts, DNS zones, database etc...
I have a test remote server and to avoid conflicts, I remove the created stuff after the test (in the TearDown() function) and create the base (in the setUp() function). In the setUp() I also load fixtures in the setup.
After a test which add a child of my main entity, I want to clean the remote server :
$service = $this->fixtures->getReference('service-web');
$this->container->get('webmanager')->deleteHosting($service, true);
The deleteHosting() function deletes all remote stuff (the true parameter is the "force" parameter, which ensure that the function doesn't stop after an error.
The $service variable contains my main service entity. I also have in this entity a One-To-Many relationship with addonDomain's entity.
My functionnal test creates an addonDomain.
The test is OK, but when I try to delete my service, the attached entity makes Doctrine yell as hell : Doctrine\ORM\ORMInvalidArgumentException: Detached entity AppBundle\Entity\Service\Web\AddonDomain#0000000054814da900000000073c524a cannot be removed.
I tried a lot of things but none of them works (ie : using the doctrine manager to retrieve the entity instead of using the fixture.
Thanks a lot for your help,
best regards.
SOLVED !
In fact, I'm using the container and the entity manager of the test class and not of the client himself. That was the problem...
Although this is an old question, I had the same problem recently while working with Symfony 4.4 and Detached entity error in Doctrine helped me solve the problem. Basically, as #Can Vural suggested, calling merge before remove is solving the problem, but the missing bit is that you have to assign the entity again so:
$entity = $doctrineManager->merge($detachedEntity);
$doctrineManager->remove($entity);
$doctrineManager->flush();
This solved my problem.
Please keep in mind that merge is deprecated and will be removed in Doctrine 3: https://github.com/doctrine/orm/blob/master/UPGRADE.md#bc-break-removed-entitymanagermerge-and-entitymanagerdetach-methods
Ive came accross the same problem and merge did not solve it for me in functional test so instead I am querying the entities again and then remove them:
class ControllerTest extends WebTestCase {
public function testCanGet() {
$em = static::getContainer()->get(EntitiyManagerInterface::class);
$entity = new Entity();
$em->persist($entity);
$em->flush();
$client = static::createClient();
$client->request("GET", "/route");
$this->assertResponseIsSuccessful();
// Remove entity
$em->remove($em->getRepository(Entity::class)->find($entity->getId()));
$em->flush();
}
}

Doctrine2: Dependency graph for a set of entities

Using Symfony and Doctrine, I’m writing a component that populates the database with entries of arbitrary entities during the deployment of an application.
I want to make this component generic, so I’m trying to resolve dependencies between entities automatically and insert them in the correct order.
I am currently get the dependencies of each single entity through the entity metadata, recursively:
public function getEntityDeps($eName)
{
$deps = [];
foreach ($this->entityManager->getClassMetadata($eName)->getAssociationMappings() as $mapping)
{
$deps[] = $mapping['targetEntity'];
$deps = array_merge($deps, $this->getEntityDeps($mapping['targetEntity']));
}
return $deps;
}
The result is obviously a list of the following kind:
// NOTE: The real list of course contains class names instead of entity aliases.
[
"FooBundle:EntityA" => [],
"FooBundle:EntityB" => ["FooBundle:EntityA", "FooBundle:EntityC"],
"FooBundle:EntityC" => ["FooBundle:EntityA"],
"BarBundle:EntityA" => ["BarBundle:EntityB"],
"BarBundle:EntityB" => []
]
The next step would be apply some type of topological sorting to the list, I guess.
However, I’m not sure if I can use a generic algorithm here or if I’m forgetting something. Especially as entities are not necessarily related (so that, in fact, we may have more than one dependency graph).
Also, I’d hope that there’s some internal Doctrine functionality which can do the sorting for me.
So what would be the most reliable way to sort an arbitrary set of Doctrine entities? Is there some reusable Doctrine feature?
So, after digging through the Doctrine source code for a while, I found that they have indeed an implementation of topological sorting (DFS for that matter). It's implemented in Doctrine\ORM\Internal\CommitOrderCalculator.
And where is this CommitOrderCalculator used? In UnitOfWork, of course.
So, instead of manually calculating the correct commit order, I just needed … to move the call to $em->flush() outside the foreach loop that went through the entities and let UOW do the work itself.
Duh.

ZF2: Custom user mapper for ZfcUser module

I`ve added module ZfcUser on my Zend Framework 2 application.
But I have to use existing database table,
which has slightly different column names than the default table structure for ZfcUser.
In ZfcUser wiki page it says that it is possible to use custom mapper if my model doesn`t conform to the provided interface.
And since my database table is different than default, my user entity class is also different than
standard ZfcUser\Entity\User. But I can tell ZfcUser to work with my own class easily
by overriding setting in file config/autoload/zfcuser.global.php:
'user_entity_class' => 'MyApp\Entity\MyUser',
But I`ve not found an easy way to tell ZfcUser to use my mapper class so far.
I have only found that the mapper is created by ZfcUser\Module::getServiceConfig()
inside which, I can see the mapper is returned from its factory function:
// ...
public function getServiceConfig()
{
return array(
// ...
'factories' => array(
// ...
'zfcuser_user_mapper' => function ($sm) {
$options = $sm->get('zfcuser_module_options');
$mapper = new Mapper\User();
$mapper->setDbAdapter($sm->get('zfcuser_zend_db_adapter'));
$entityClass = $options->getUserEntityClass();
$mapper->setEntityPrototype(new $entityClass);
$mapper->setHydrator(new Mapper\UserHydrator());
return $mapper;
},
// ...
Is there a way to make ZfcUser to use my custom user mapper class?
I've had the same problem as you, but managed to log in to my application at last. I followed Rob's advice and created my own service factory within my existing user module. Unfortunately Bernhard is also spot on. You kinda have to dig into the ZfcUser source code to get it to work. The project I am working on now has a MSSQL server and I must say that it's been tough getting a handle on things. I've ended up tweaking only one function in the ZfcUser source to get the login page to work.
I only need log in functionality for the current application, but the upcoming project is much more role driven. I was looking for something that wouldn't be too complicated to hook up quickly, and at the same time offer more options and possibilities for the future.
Here is what I did for now and what I've learned:
I copied the Entity and Mapper folders from the ZfcUser directory over to my existing b2bUser (my module) folder. Everything...even the Exception folder inside Mapper. It might not be necessary, but I wasn't in the mood to figure out dependencies.
In the zfcuser.global.php file, my active configuration looks as follows:
'user_entity_class' => 'b2bUser\Entity\User',
'enable_registration' => false,
'enable_username' => true,
'auth_identity_fields' => array( 'username' ),
'login_redirect_route' => 'home',
'enable_user_state' => false,
I left the rest of the settings on default. I removed the email option from auth identities because they won't use email addresses to log into the system. The user_entity_class is the one I copied over...
Module.php (b2bUser)
Copied the following to the service manager config:
'zfcuser_user_mapper' => function ($sm) {
$mapper = new Mapper\User();
$mapper->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
$mapper->setEntityPrototype(new Entity\User());
$mapper->setHydrator(new Mapper\UserHydrator());
return $mapper;
},
After the setup was done, I changed the namespaces, etc of the files in Entity and Mapper to reflect their new home. Changed the Entity and the Interface to reflect my own data structure. I did the same with the Mapper files and made sure the variable names in the Hydrator file is the same as my database column names.
I left the AbstractDbMapper file where it was. But this is the file I tweaked a bit.
Here is what mine looks like. The SQLSRV driver was full of pooh, complaining the whole time about an object or a string...
protected function select(Select $select, $entityPrototype = null, HydratorInterface $hydrator = null)
{
$this->initialize();
$selectString = $this->getSlaveSql()->getSqlStringForSqlObject($select);
$stmt = $this->getDbAdapter()->driver->createStatement($selectString);
$stmt->prepare();
$res = $stmt->execute($stmt);
$resultSet = new HydratingResultSet($hydrator ?: $this->getHydrator(),
$entityPrototype ?: $this->getEntityPrototype());
$resultSet->initialize($res);
return $resultSet;
}
And that is that. I hope it helps someone to get it up and running on their own system at least. I won't leave mine like this, but it was a bit of a mission to get it to work.
Ceate your own service factory for zfcuser_user_mapper and it will get used.
Currently there seems to be no easy way to change the table structure without modifying the ZfcUser source. There's a pull request on Github that should solve this problem:
https://github.com/ZF-Commons/ZfcUser/pull/174

Disable Cakephp's Auto Model "feature"

In cake 1.2 there is a feature that allows the developer to no have to create models, but rather have cake do the detective work at run time and create the model for you. This process happens each time and is neat but in my case very hazardous. I read about this somewhere and now I'm experiencing the bad side of this.
I've created a plugin with all the files and everything appeared to be just great. That is until i tried to use some of the model's associations and functions. Then cake claims that this model i've created doesn't exist. I've narrowed it down to cake using this auto model feature instead of throwing and error! So i have no idea what's wrong!
Does anybody know how to disable this auto model feature? It's a good thought, but I can't seem to find where i've gone wrong with my plugin and an error would be very helpful!
There's always the possibility to actually create the model file and set var $useTable = false.
If this is not what you're asking for and the model and its associations actually do exist, but Cake seems to be unable to find them, you'll have to triple check the names of all models and their class names in both the actual model definition and in the association definitions.
AFAIK you can't disable the auto modelling.
Cake 1.2
It's a hack and it's ugly cus you need to edit core cake files but this is how i do it:
\cake\libs\class_registry.php : line 127ish
if (App::import($type, $plugin . $class)) {
${$class} =& new $class($options);
} elseif ($type === 'Model') {
/* Print out whatever debug info we have then exit */
pr($objects);
die("unable to find class $type, $plugin$class");
/* We don't want to base this on the app model */
${$class} =& new AppModel($options);
}
Cake 2
Costa recommends changing $strict to true in the init function on line 95 of Cake\Utility\ClassRegistry.php
See Cake Api Docs for init
ClassRegistry.php - init function
Use
var $useTable = false;
in your model definition.
Delete all cached files (all files under app/tmp, keep the folders)
In most cases where models seem to be acting in unexpected ways, often they dont include changes you've made, it is because that cake is useing an old cached version of the model.
Uh...where do we start. First, as Alexander suggested, clear your app cache.
If you still get the same behaviour, there is probably something wrong with the class and/or file names.
Remember the rules, for controller:
* classname: BlastsController
* filename: blasts_controller.php
for model:
* classname: Blast
* filename: blast.php
Don't foget to handle the irregular inflections properly.

Categories