Test database isolation with Behat 3, Doctrine 2 and Symfony 2 - symfony

I am introducing functional behat tests on a Symfony2/Doctrine2 application and am deciding how to handle database isolation and data fixtures.
Are there any pitfalls to setting up a separate test environment with its own completely separate mysql database that gets populated by a dump import prior to execution of the behat test suite, and then emptied after suite execution? I'm trying to avoid using data fixtures unless I really need so as to save the time of manually writing foreign key relations and what not.
Any guidance is appreciated.

As far as Symfony/Doctrine/Behat is concerned and if you want to be one of those who follows the best practises then:
You should isolate your environment dev, test, prod, stag ...
Setting up isolated environments as a symfony application base
up to Build folder structure header.
You should isolate your test database and use sqlite instead of
MySQL for performance purposes. Using multiple SQLite entity
managers for multiple bundles and databases in test environment,
you can use only one.
You should use fixtures to give yourself flexibility and get rid of
burden of manual processing. Do not try to avoid using them!
Creating doctrine data fixtures in symfony
So on ..... just check the posts in this site which I often
read up on myself.
Behat 3 composer entries and the behat.yml

We currently have a separate test database and use a combination of both fixtures and prepopulated database.
The prepopulated database contains the minimum information that needs to be present in almost all tests. We used to do this with fixtures but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not the DB but doctrine's metadata caching. If you set up metadata caching in redis the speed of the tests increase dramatically.

In order to respond to #madness-method and to complete the following answer:
The prepopulated database contains the minimum information that needs
to be present in almost all tests. We used to do this with fixtures
but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like
Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not
the DB but doctrine's metadata caching. If you set up metadata caching
in redis the speed of the tests increase dramatically.
You should use instead:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->em->getConnection();
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
having the following code in your Context file:
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Do not forget to add the following lines in your behat configuration file regarding your contexts in order to be able to use the entity manager in your constructor and then in your initialiseStorage method :
- AppBundle\Features\Context\FeatureContext:
em: '#doctrine.orm.default_entity_manager'
Basically to get the connection, we have replaced:
$this->getService('database_connection');
by:
$this->em->getConnection();

I recommend you to read something about factory girl pattern. The idea is create a
factory for every class that you have and use a instance of it in the test. I use https://github.com/carlescliment/handy-tests-bundle
Other options will be create your own steps to create a instance or the class something like this:
/**
* #Given /^there are products:$/
*/
public function thereAreRoutes(TableNode $table)
{
$em = $this->getEntityManager();
foreach ($table->getHash() as $hash) {
$entity = new Product();
$entity->setName($hash['name']);
$entity->setDescription(isset($hash['description']) ? $hash['description'] : $hash['description']);
$em->persist($entity);
}
$em->flush();
}
And you can use it like:
Given there are products:
| name | description |
| Shoes | It is blue |

Related

Reset database at the end of test

I want to reset my test database after each test but somehow can't find a way to do it. Can delete the data added in each test but I am pretty sure this is not the correct approach. What I have as test is simply this:
<?php
declare(strict_types=1);
namespace Tests\Unit\Entity;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Tests\Shared\Factory\UserFactory;
final class UserTest extends KernelTestCase
{
public function testGettersReturnCorrectData(): void
{
self::bootKernel();
/** #var UserRepository $repo */
$repo = self::getContainer()->get(UserRepository::class);
$user = UserFactory::createUser();
$repo->add($user, true);
$this->assertSame($user->getEmail(), UserFactory::EMAIL);
$this->assertSame($user->getPassword(), UserFactory::PASSWORD);
$this->assertSame($user->getReferralCode(), UserFactory::REFERRAL_CODE);
}
}
The newly created user stays in the db. I am using MySQL test db because my production db will be MySQL and don't want to reach some corner cases because of the different dbs.
This is my env.test if needed.
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
DATABASE_URL="mysql://root:toma123#127.0.0.1:3306/api?serverVersion=5.7&charset=utf8mb4"
by the love of god, follow #craigh 's advice and look into foundry
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#database-reset
they do exactly that, and actually provide good stuff so you can build your test database inside each test case. Symfony's fixtures solution, in my subjective opinion, is worse than foundry.

Dynamically eager loading deep relationships with Doctrine

I'm currently working on an API using the following stack;
Symfony (3)
FOSRestBundle
Fractal
I'm wanting to integrate the ability to specify, via query parameter, which relationships to include when retrieving an entity/collection, e.g;
[GET] /users?include=friends.addresses
Fractal comes with the ability to handle includes however, as this happens around the serialization point of the response building, each related entity is retrieved via lazy loading, thus triggering additional queries.
Is there a way to tell Doctrine, when retrieving a collection, to dynamically also retrieve relationships specified? Ive seen the following from the Doctrine docs which shows how to dynamically change the fetch mode however this only seems to work with associations on the target entity (friends in the example above) and not deeper relations (addresses of friends in the example).
Thanks!
If I remember correctly you can "preload" relations by joining them in rather than letting the lazy loading mechanism handle it. An idea could be to create a service that creates a query builder based on your criteria. This is a crude snippet of what I mean:
class EagerService
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function resolveIncludes($class, $alias, $includes)
{
// Parse includes into an array
if (strpos($includes, '.') !== false) {
$relations = explode('.', $includes);
} else {
$relations = [$includes];
}
// The next relation is owned by the previous one, so we keep track of the previous relation
$previousRelation = $alias;
$qb = $em->getRepository($class)->getQueryBuilder($previousRelation);
foreach ($relations as $relation) {
// Add inner joins to the query builder referencing the new relation
$qb->innerJoin("{$previousRelation}.{$relation}", $relation);
$previousRelation = $relation;
}
// Return query builder or the result of the query
return $qb;
}
}

Add data when running Symfony migrations

I have a Symfony project that is using the DoctrineMigrations bundle, and I have a really simple question: When I run a migration (e.g., when I'm pushing an update to production), how can I insert data to the database?
For example: I have an Entity which is the type of an add. The entity is:
private $addType; // String
private $type1; // Boolean
private $type2; // Boolean
private $type3; // Boolean
I add another field ($type4), and I want to add a new record to the database, with this values:
$addType = 'Type number 4';
$type1 = false;
$type2 = false;
$type3 = false;
$type4 = true;
How can this be done with DoctrineMigrations? Is it possible?
Using the Entity Manager as suggested in another answer is not a good idea, as it leads to troubles later.
In the first migration, I created a table with users and populated some users via $em->persist($user); which seemed fine at the beginning.
But after a month, I added a phone column to my User model. And Doctrine generates INSERT statements with this column within the first migration, which fails due to the non-existing column phone. Of course it doesn't exist yet in the first migration. So it is better to go with pure SQL INSERTs.
I just asked a related related question.
It is possible to use the migrations bundle to add data to the database. If you add a new property and use the doctrine mapping then the
php app/console doctrine:migrations:diff
command will generate a new migration file. You can just put your insert statements inside this file using the syntax:
$this->addSql('INSERT INTO your_table (name) VALUES ("foo")');
Make sure you put it after the auto-generated schema changes though. If you want to separate your schema changes and your data changes then you can use
php app/console doctrine:migrations:generate
to create an empty migrations file to put your insert statements in.
Like I said in my related question, this is one way to do it, but it requires manually creating these if you want to change this data in the database.
Edit:
Since this answer seems to get a few views I think it's worth adding that to more clearly separate the data changes from the schema changes there is a postUp method that can be overridden and that will be called after the up method.
https://www.doctrine-project.org/projects/doctrine-migrations/en/3.0/reference/migration-classes.html#postup
I've "found" the correct way to solve my problem (insert data after running migrations, using my entity classes).
Here is: https://stackoverflow.com/a/25960400
The idea is to declare the migration as ContainerAware, and then, from the postUp function, call the DI to get the EntityManager. It's really easy, and you can use all your entities and repositories.
// ...
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... migration content
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
}
}
when you make the new field you need to enter this annotation "options={"default":1}" and it should work.
/**
* #var boolean
* #ORM\Column(name="type4", type="boolean", options={"default":1})
*/
private $type4 = true;
Took me some time to figure this out :)
It does, if you know how to format the array;
$this->connection->insert('user', ['id' => 1, 'gender' => 'Male']);
this is good solution for me. Just use bin/console make:migration and when migration is generated just edit if and add "DEFAULT TRUE":
$this->addSql('ALTER TABLE event ADD active TINYINT(1) NOT NULL DEFAULT TRUE');
It doesn't sound a good idea to fill date in migration, not its responsibility, symfony has a way of doing that. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

Can i use getDoctrine and getManager in an entity? I'm sorry but i don't understand how this works

I want to use the getDoctrine and getManager functions in an entity. Is this possible? or is there any way arround this? I want to insert something in a database like this :
$history = new Policy();
$history->setName($file1->getClientOriginalName());
$history->setPolicyNumber($this->getPolicyNumber());
$history->setOrderId($this->getOrderId());
$history->setPath($this->getPathFile1());
$history->setDocumentType($this->getDocument1Type());
$history->setPrintAction($this);
$em = $this->getDoctrine()->getManager();
$em->persist($history);
$em->flush();
With Doctrine ORM, Entities have an unique role : data containers!
According to Doctrine architecture, there is no reason to inject EntityManager inside.
If you need to do that, you're trying to put some code of the Business layer into layer.
So try to move your code into a service, like a manager for your Entity or if you're lazy in a controller but it's a bit crapy.
I would venture to first answer the question, and then give out advice.
If you look into source code of Doctrine2, you may to find this method in Doctrine\ORM\UnitOfWork:
/**
* #param ClassMetadata $class
*
* #return \Doctrine\Common\Persistence\ObjectManagerAware|object
*/
private function newInstance($class)
{
$entity = $class->newInstance();
if ($entity instanceof \Doctrine\Common\Persistence\ObjectManagerAware) {
$entity->injectObjectManager($this->em, $class);
}
return $entity;
}
So... it means, if your entity implements \Doctrine\Common\Persistence\ObjectManagerAware you will have EntityManager inside Doctrine2 entity. That's it.
Now advice:
IT'S REALLY BAD PRACTICE, AND NOT RECOMMENDED FOR USE.
From PhpDoc of \Doctrine\Common\Persistence\ObjectManagerAware interface:
Word of Warning: This is a very powerful hook to change how you can work with your domain models.
Using this hook will break the Single Responsibility Principle inside your Domain Objects
and increase the coupling of database and objects.

When to use Entity Manager in Symfony2

At the moment I am learning how to use Symfony2. I got to the point where they explain how to use Doctrine.
In the examples given they sometimes use the entity manager:
$em = $this->getDoctrine()->getEntityManager();
$products = $em->getRepository('AcmeStoreBundle:Product')
->findAllOrderedByName();
and in other examples the entity manager is not used:
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product')
->find($id);
So I actually tried the first example without getting the entity manager:
$repository = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product');
$products = $repository->findAllOrderedByName();
and got the same results.
So when do i actually need the entity manager and when is it OK to just go for the repository at once?
Looking at Controller getDoctrine() equals to $this->get('doctrine'), an instance of Symfony\Bundle\DoctrineBundle\Registry. Registry provides:
getEntityManager() returning Doctrine\ORM\EntityManager, which in turn provides getRepository()
getRepository() returning Doctrine\ORM\EntityRepository
Thus, $this->getDoctrine()->getRepository() equals $this->getDoctrine()->getEntityManager()->getRepository().
Entity manager is useful when you want to persist or remove an entity:
$em = $this->getDoctrine()->getEntityManager();
$em->persist($myEntity);
$em->flush();
If you are just fetching data, you can get only the repository:
$repository = $this->getDoctrine()->getRepository('AcmeStoreBundle:Product');
$product = $repository->find(1);
Or better, if you are using custom repositories, wrap getRepository() in a controller function as you can get auto-completition feature from your IDE:
/**
* #return \Acme\HelloBundle\Repository\ProductRepository
*/
protected function getProductRepository()
{
return $this->getDoctrine()->getRepository('AcmeHelloBundle:Product');
}
I think that the getDoctrine()->getRepository() is simply a shortcut to getDoctrine()->getEntityManager()->getRepository(). Did not check the source code, but sounds rather reasonable to me.
If you plan to do multiple operations with the entity manager (like get a repository, persist an entity, flush, etc), then get the entity manager first and store it in a variable. Otherwise, you can get the repository from the entity manager and call whatever method you want on the repository class all in one line. Both ways will work. It's just a matter of coding style and your needs.

Resources