How to load Symfony2 fixtures from migration class? - symfony

We've built up a set of data fixtures to seed the database with all our reference values. We are also using the DoctrineMigrationsBundle to manage schema updates. We would like to trigger the fixture load within our initial schema migration class so the system gets populated before running any additional schema updates.
I found in the docs that you can make migration classes container aware, but I can't figure out how to jump from that to calling/running the data fixtures. I haven't found any good answers on Stackoverflow or via google. Has anyone done this and can point me in the right direction? (or have suggestions on a better way to manage seed data in conjunction with schema migrations). Thanks.
This is using Symfony Version: 2.4

This is interesting question. I've found the "dirty" solution, but it works well.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Version20140811164659 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... your code here
}
public function postUp(Schema $schema)
{
// here you have to define fixtures dir
$this->loadFixtures('src/Acme/BlogBundle/DataFixtures/ORM');
}
public function down(Schema $schema)
{
// ... your code here
}
public function loadFixtures($dir, $append = true)
{
$kernel = $this->container->get('kernel');
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->setAutoExit(false);
//Loading Fixtures
$options = array('command' => 'doctrine:fixtures:load', "--fixtures" => $dir, "--append" => (boolean) $append);
$application->run(new \Symfony\Component\Console\Input\ArrayInput($options));
}
}
This solution simply running console command php app/console doctrine:fixtures:load --fixtures=src/Acme/BlogBundle/DataFixtures/ORM --append after "up" migration.
Sorry for poore English. If you'll find clear solution, share it ;)

I've made a migration class to address this very problem. The code is essentially inspired from the doctrine:fixtures:load command.
<?php
namespace AppBundle\Migrations;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AbstractFixturesAwareMigration extends AbstractMigration implements ContainerAwareInterface
{
private $container;
private $fixtures;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
protected function getContainer()
{
return $this->container;
}
protected function addFixture(FixtureInterface $fixture)
{
if(null === $this->fixtures) {
$this->fixtures = new ContainerAwareLoader($this->getContainer());
}
$this->fixtures->addFixture($fixture);
return $this;
}
protected function executeFixtures($em = null, $append = true, $purgeMode = ORMPurger::PURGE_MODE_DELETE)
{
$em = $this->getContainer()->get('doctrine')->getManager($em);
$purger = new ORMPurger($em);
$purger->setPurgeMode($purgeMode);
$executor = new ORMExecutor($em, $purger);
$executor->execute($this->fixtures->getFixtures(), $append);
$this->fixtures = null;
return $this;
}
}
Usage is pretty straightforward:
<?php
namespace Application\Migrations;
use AppBundle\Migrations\AbstractFixturesAwareMigration
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170726102103 extends AbstractFixturesAwareMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
// [...]
}
public function postUp(Schema $schema)
{
// LoadMyData can be any fixture class
$this->addFixture(new LoadMyData());
$this->executeFixtures();
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
// [...]
}
}

Related

Do Subscribers work while loading Fixtures in Symfony?

I tried to run the fixture below on Symfony 5 using the command php bin/console d:f:l.
I get this error: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'contact_email' cannot be null
The same code logic is working fine for Post entities when creating them manually through the CRUD. Are fixtures not compatible with subscribers (events) or did i make a mistake?
Thank you.
Edit: I'm also using EasyAdmin Bundle 3.
App\DataFixtures.php\AppFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\User;
use App\Entity\Author;
use Doctrine\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AppFixtures extends Fixture
{
/** #var User[] */
private $users = [];
/** #var Author[] */
private $authors = [];
/** #var UserPasswordHasherInterface */
private $hasher;
public function __construct(UserPasswordHasherInterface $hasher)
{
$this->hasher = $hasher;
}
public function load(ObjectManager $manager): void
{
$this->createUsers();
foreach($this->users as $user) $manager->persist($user);
$this->createAuthors();
foreach($this->authors as $author) $manager->persist($author);
$manager->flush();
}
public function createUsers(): void
{
$admin = (new User)
->setUsername('admin')
->setEmail('admin#admin.com')
->setRoles(['ROLE_ADMIN'])
->setFirstname('Edouard')
->setLastname('Proust');
$admin->setPassword($this->hasher->hashPassword($admin, 'admin'));
$this->users[] = $admin;
}
public function createAuthors(): void
{
foreach($this->users as $user) {
if(in_array('ROLE_ADMIN', $user->getRoles())) {
$author = (new Author)
->setUser($user)
->setAvatar('#')
->setBio('Bio')
// The line i want to get rid of:
// ->setContactEmail($user->getEmail())
;
$this->authors[] = $author;
}
}
}
}
App\EventListener\AuthorSubscriber.php
<?php
namespace App\EventListener;
use App\Entity\Author;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
class AuthorSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BeforeEntityPersistedEvent::class => 'setContactEmail',
];
}
public function setContactEmail(BeforeEntityPersistedEvent $event)
{
/** #var Author */
$entity = $event->getEntityInstance();
if($entity instanceof Author) {
if(!$entity->getContactEmail()) {
$user = $entity->getUser();
$contactEmail = $user ? $user->getEmail() : '#';
$entity->setContactEmail($contactEmail);
}
}
}
}
EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent:class is not proper Symfony event name. You probably should use Doctrine\ORM\Events::prePersist.
Also please check your DoctrineBundle version. If you're using the default services.yaml configuration and DoctrineBundle lower than 2.1, you have to configure services.yaml with:
App\EventListener\AuthorSubscriber:
tags:
- name: 'doctrine.event_subscriber'
You can read something more here: https://symfony.com/doc/current/doctrine/events.html#doctrine-lifecycle-subscribers

Symfony 6 - Attempted to call an undefined method named "getDoctrine" [duplicate]

As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.

Circular reference for Repository in Symfony 5

I try to follow this tutorial : https://www.thinktocode.com/2018/03/05/repository-pattern-symfony/.
It's suppose to help structure your Repository.
But when i get to this point :
final class ProductRepository
{
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* #var ObjectRepository
*/
private $objectRepository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->objectRepository = $this->entityManager->getRepository(Product::class);
}
public function find(int $productId): Product
{
$product = $this->objectRepository->find($productId);
return $product;
}
public function findOneByTitle(string $title): Product
{
$product = $this->objectRepository
->findOneBy(['title' => $title]);
return $product;
}
public function save(Product $product): void
{
$this->entityManager->persist($product);
$this->entityManager->flush();
}
}
And testing my Repository with this test case :
<?php
namespace App\Tests\Repository;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ProductRepository_KernelTest extends KernelTestCase
{
private ?ProductRepository $_productRepository;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->_productRepository = self::$container->get(ProductRepository::class);
}
public function test_findAllProductNatByLabelForLabelEmptyReturnTenProduct()
{
dump($this->_productRepository->findAllProductsByLabel('AACIFEMINE'));
die();
}
}
It loop endlessly.
I think it's due to this code :
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->objectRepository = $this->entityManager->getRepository(Product::class); // <-----
}
As it call the ProductRepository constructor inside of this same constructor... So i guess it's why that loop
So I don't know. Is this tutorial just wrong or not up to date ?
https://www.thinktocode.com/2018/03/05/repository-pattern-symfony/#comment-4155200782
Maciej,
You are correct that in these example we are using 2 repositories. The
object repository from doctrine inside our own custom repository. This
allows use to be decoupled from doctrine's repository and still change
this in the future. This means to not set your custom repository as
the default repository in your entity.
You can get rid of inject the object repository, and in so only be
using 1 repository by implementing a BaseRepository class in which you
create the basic findBy, findOneBy, createQueryBuilder yourself. Take
a look at the EntityRepository in Doctrine/ORM. This might be a good
follow up topic to go over in a future article to create a better
solution then I suggested in here.

How to get entity instance when doing unit test using Symfony and PHPUnit?

I'm trying to do a unit test in the environment as the title says. However, the method contained in the class under test requires an entity instance as an argument. So I'm trying to get the above entity in a generic homebrew test class that extends the TestCase class but I can't figure out how to do it.
I have little experience with functional testing. I used fixtures at that time, so I'm guessing that I should use fixtures this time too. Is that correct? I would appreciate if you could teach me how to do that as well.
Please let me know, even a little information.
below is the test class, and the interface of the test target.
ps. we are using nelmio/alice bundle.
<?php
declare(strict_types=1);
namespace xxx\xxxx\Tests\Customize\Server\AlladinOffice;
use Codeception\PHPUnit\TestCase;
use Customize\Service\AlladinOffice\CodeFormatter;
use Customize\Service\AlladinOffice\CustomerCode;
use Doctrine\Common\Persistence\ObjectManager;
use Eccube\Repository\CustomerRepository;
use PHPUnit\Framework\MockObject\MockObject;
use Proxies\__CG__\Eccube\Entity\Customer;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CustomerCodeTest extends KernelTestCase
{
/**
* #var CustomerCode
*/
private $sut;
/**
* #var CustomerRepository|MockObject
*/
private $customerRepository;
protected function setUp()
{
$codeFormatter = new CodeFormatter();
$this->sut = new CustomerCode($codeFormatter);
$this->customerRepository = $this->createMock(CustomerRepository::class);
parent::setUp();
}
public function testGetCustomerCode()
{
$actual = $this->sut->getCustomerCode(null);
$expected = 1000000000;
$this->assertEquals($expected, $actual);
// $customer = $this->customerRepository->findBy([], ['id' => 'ASC']);
// dump($customer);exit();
// $actual = $this->sut->getCustomerCode($customer);
$customer = new Customer;
//$customer->set
//$this->customerRepository
//->expects($this->any())
//->method('find')
//->willReturn($);
}
}
<?php
declare(strict_types=1);
namespace Customize\Service\AlladinOffice;
use Eccube\Entity\Customer;
interface CustomerCodeInterface
{
public function getCustomerCode(?Customer $customer): string;
public function getChannelCode(?Customer $customer): string;
public function getRankCode(?Customer $customer): string;
}
for example a detailed method to test.
public function getCustomerCode(?Customer $customer): string
{
if (!$customer) {
return self::DEFAULT_CUSTOMER_CODE;
}
$customerRank = $customer->getXxxCustomer()->getCustomerRank();
$customerChannel = $customerRank ? $customerRank->getCustomerChannel() : null;
if (!$customerChannel || !$customerChannel->isExportToAO()) {
return self::DEFAULT_CUSTOMER_CODE;
}
return $this->formatter->getCustomerCodeFromCustomerId($customer->getId());
}

Testing fixtures with addReference in Symfony 4

I'm working on a Symfony's project and i have some issues while testing with phpunit.
I have StatusFixtures with addReference to be used in BriefFixtures and this work correctly when i do doctrine:fixtures:load (with correct dependency to load Status before Brief).
But, when i run my tests, using those fixtures, the following error is coming : Error: Call to a member function addReference() on null
My StatusFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\Status;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class StatusFixtures extends Fixture
{
const Status_Reference = 'status';
public function load(ObjectManager $manager)
{
// some code to assign values
$manager->persist($activeStatus);
$this->addReference(self::Status_Reference, $activeStatus);
$manager->flush();
}
}
My BriefFixtures.php
<?php
namespace App\DataFixtures;
use App\Entity\Brief;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
class BriefFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager)
{
// some code to assign values
$briefValid->setStatus($this->getReference(StatusFixtures::Status_Reference));
$manager->persist($briefValid);
$manager->flush();
}
public function getDependencies()
{
return array(
StatusFixtures::class,
);
}
}
And i'm loading fixtures this way in my tests
private $entityManager;
protected function setUp()
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
$status = new StatusFixtures();
$status->load($this->entityManager);
$fixture = new BriefFixtures();
$fixture->load($this->entityManager);
}
And my error Error: Call to a member function addReference() on null
$this from StatusFixtures seems to be null, but i don't understand why this correctly works when i'm loading fixtures and no more when i'm running my tests.
Maybe something is missing in setUp() ?
Thank you for your help
The problem lies in the Symfony documentation for Fixtures. It makes you feel like
$fixture->load($this->entityManager); will simply load the fixture, but that is not true. It is simple when you use the command doctrine:fixtures:load because it does more than just above load function call.
Going with third party solutions will be the quickest and probably the best solution. Here are few libraries that you can use:
liip/LiipTestFixturesBundle
hautelook/AliceBundle (thanks michal)
The error you are getting comes from ReferenceRepository object that is supposed to store the references, but it is null. Who actually sets up this repository, it is Doctrine\Common\DataFixtures\Executor\AbstractExecutor. What you need is a Loader that loads the fixture by creating everything needed for it to work. One of those loaders is Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader that your command doctrine:fixtures:load is using. You can use that loader or write your own loader. You can see that what this loader has to do to provide you the expected results. But that still is not it, you also need Doctrine\Common\DataFixtures\Executor\ORMExecutor because your Fixture is a database entity and you need to persist it. You can see that
how doctrine:fixtures:load makes use of SymfonyFixturesLoader and ORMExecutor to provide you expected result. So, you will have to write your own solution for this. I worte a Loader for myself before because I didn't want to go with third party solutions. I am putting it below. It may not serve your purpose exactly, but it will give you ideas how to write your own Loader if you want to.
namespace App\Tests\Extra;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\ORM\EntityManagerInterface;
use App\Tests\Extra\Exception\FixtureNotLoadedException;
class FixtureLoader
{
private $entityManager;
private $loader;
private $registry;
public function __construct(
EntityManagerInterface $entityManager,
ManagerRegistry $registry
) {
$this->entityManager = $entityManager;
$this->registry = $registry;
}
public function loadFixtures(array $classNames) : void
{
$this->loader = new Loader();
foreach ($classNames as $className) {
$this->loader->addFixture(new $className());
}
$executor = new ORMExecutor($this->entityManager, new ORMPurger());
$executor->execute($this->loader->getFixtures());
}
public function getFixture(string $className) : Fixture
{
if ($this->loader == null) {
throw new FixtureNotLoadedException(
sprintf(
'The fixture %s must be loaded before you can access it.',
$className
)
);
}
return $this->loader->getFixture($className);
}
private function getPurger() : ORMPurger
{
$purger = new ORMPurger($this->entityManager);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
return $purger;
}
public function cleanDatabase() : void
{
$connection = $this->entityManager->getConnection();
$mysql = ('ORM' === $this->registry->getName()
&& $connection->getDatabasePlatform() instanceof MySqlPlatform);
if ($mysql) {
$connection->query('SET FOREIGN_KEY_CHECKS=0');
}
$this->getPurger()->purge();
if ($mysql) {
$connection->query('SET FOREIGN_KEY_CHECKS=1');
}
}
}

Resources