Dependency Injection with Fixtures - symfony

I am building a web application using Symfony 4. I am trying to load fixtures from my functional tests. I have created a fixture class:
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class TestFixtures extends Fixture
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function load(ObjectManager $em)
{
$user = new User();
$user->setUsername('user#gamil.com');
$user->setIsActive(true);
$user->setEmail('user#gmail.com');
$user->setFirstName('John');
$user->setLastName('Doe');
$user->setSchool($school);
$user->setRoles(['ROLE_USER']);
$password = $this->encoder->encodePassword($user, 'pass1234');
$user->setPassword($password);
$em->persist($user);
$em->flush();
return $user;
}
}
I have configured services_test.yaml to pass the service to the fixture:
services:
_defaults:
public: true
App\DataFixtures\TestFixtures:
arguments: ['#security.password_encoder']
However, when I try to run my tests I get the following error:
ArgumentCountError: Too few arguments to function App\DataFixtures\TestFixtures::__construct(), 0 passed in /Development/app/src/Test/ApiTestCase.php on line 38 and exactly 1 expected
This is the function from my test case:
protected function setUp()
{
$this->client = self::$staticClient;
$this->purgeDatabase();
$em = $this->getEntityManager();
$fixture = new TestFixtures();
$fixture->load($em);
}
Thanks in advance for any advice offered on this...

You are instantiating a class:
$fixture = new TestFixtures(); whereas you need it as a service.
You need to either, do this $fixture = new TestFixtures(encoder);
where encoder is either an instantiated class or a mock of your encoder class, or, retrieve TestFixtures class from the client's container. I'm not entirely sure but try
$fixture = $this->client->getContainer()->get('App\DataFixtures\TestFixtures');

Related

How to access the EntityManager in a Phpunit Testcase in Symfony 5.4?

on symfony 5.4, I'm testing on a rest api.
I always get errors when I try to access the entity manager
I followed this documentation:
https://symfony.com/doc/current/testing/database.html#functional-testing-of-a-doctrine-repository
here is my test code:
<?php
namespace App\Tests;
use App\Repository\LocationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response;
class DataTest extends WebTestCase // WebTestCase (not KernelTestCase)
{
private EntityManagerInterface $entityManager;
private LocationRepository $locationRepository;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager();
$this->locationRepository = $this->entityManager->get(LocationRepository::class);
}
protected function tearDown(): void
{
parent::tearDown();
// doing this is recommended to avoid memory leaks
$this->entityManager->close();
$this->entityManager = null;
}
public function test401(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/api/locations');
$response = $client->getResponse();
$this->assertResponseStatusCodeSame(401);
}
...
I get this error:
Testing App\Tests\DataTest
EEEE 4 / 4 (100%)
There were 4 errors:
1) App\Tests\DataTest::test401
Error: Call to undefined method ContainerDyLAo2g\EntityManager_9a5be93::get()
How can I solve this?
In WebTestCase you can access the entity manager like this
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class)
You can also check the documentation here https://symfony.com/doc/current/testing.html#retrieving-services-in-the-test
There is no method get() on the EntityManagerInterface. Use the method getRepository(string $className) to fetch the repository:
$this->locationRepository = $this->entityManager>getRepository(LocationEntity::class);

Doctrine query outside the controller Symfony 2

I have some trouble since two days to do a query using a UserRepository outside a controller. I am trying to get a user from the database from a class that I named ApiKeyAuthenticator. I want to execute the query in the function getUsernameForApiKey like in the docs. I think I am suppose to use donctrine as a service but I don't get how to do this.
Thanks for you help in advance!
<?php
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_API')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return User::class === $class;
}
}
You have to make your ApiKeyUserProvider a service and inject the UserRepository as a dependency. Not sure if repositories are services in 2.8, so maybe you'll have to inject the EntityManager .
class ApiKeyUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$repository = $this->em->getRepository(User::class);
// ...
Now register your class as a service in your services.yml file
services:
app.api_key_user_provider:
class: AppBundle\Security\ApiKeyUserProvider
arguments: ['#doctrine.orm.entity_manager']

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');
}
}
}

Can I create a more global instance of EntityManager in order to keep my code DRY?

From the examples I'm finding in the Symfony docs, it looks like the typical thing to do when needing to save data is something like in the controller class:
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->render('index.html.twig');
}
It would be really great to not have to type those 3 $em lines in every single controller method! And it would be even sweeter to move all of this logic to a class somewhere else and then just call $product->saveProduct($data)! What is the best option here?
I usually create a manager class e.g. ProductManager and register it as service. I inject the EntityManager via setter injection and implement all the methods I need.
In your case this would look similar to this:
AppBundle/Product/ProductManager
namespace AppBundle\Product;
use Doctrine\ORM\EntityManager;
class ProductManager {
/** #var EntityManager */
private $entityManager;
public function setEntityManager (EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function getAll()
{
return $this->entityManager->createQuery('SELECT p FROM '.Product::class.' p')
->getResult();
}
public function add(Product $product, $flush = true)
{
$this->entityManager->persist($product);
if ( $flush ) {
$this->entityManager->flush($product);
}
}
public function byId($id)
{
// Fetch a product by id (note: No need to use DQL or the EntityRepository here either!)
return $this->entityManager->find(Product::class, $id);
}
}
app/config/services.yml
services:
app.product_manager:
class: AppBundle\Product\ProductManager
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
Controller
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
// add the product
$this->get('app.product_manager')->add($product);
return $this->render('index.html.twig');
}
Take a look at the Propel project if you want something like $product->save() but it is a totally different approach. This is the official bundle https://github.com/propelorm/PropelBundle/blob/3.0/README.markdown

Symfony2 access user and doctrine in a service

I'm running the equivalent of this code in lots and lots of controller actions, basically it grabs the user's username, and if that username is attached to a blog entity it will allow the user to see the blog entity(s):
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
return $this->render('MySiteBundle:Blog:index.html.twig', array(
'entities' => $entities,
I want to move it into a service so I can cut down on code repetition. I want to avoid doing as much logic in my controllers as possible.
That being said, I'm not sure how I can access the user session and doctrine in a service.
Here's my services.yml:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
And here's how I was attempting to call it in the controller:
public function testAction() {
$response = $this->get('mysite.user.blog');
return new Response($response);
}
I did try using an event subscriber/listener tag, but that doesn't seem to accomplish the task I want.
Here is my completely horrible attempt at a service. I couldn't get any response from it without using a constructor.
namespace MySite\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
protected $entities;
public function __construct(){
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Am I going about this the completely wrong way? Is there a better way that I'm missing?
EDIT/ANSWER:
modified my naming convention a little:
//services.yml
mysite.user.blog.entities:
class: Mysite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]
In the controller action:
$userEntities = $this->get('mysite.user.blog.entities');
$entities = $userEntities->getEntities();
In the service itself:
class BlogUser {
protected $entities;
public function __construct($em, $securityContext){
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
public function getEntities(){
return $this->entities;
}
}
Still needs two lines to get the $entities variable in the controller, but this is way better than defining the same thing over and over.
"Security.context" has been deprecated since Symfony 2.6
After some community discussions, it was decided that SecurityContext gives too many dependencies to retrieve a simple Token/User object. That's why, starting with Symfony 2.6, thesecurity.context service has been deprecated and split into two new services:security.authorization_checker and security.token_storage.
Source
Thus, the new way to do it would be, first configure your service as:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.token_storage"]
Then in the service class constructor:
class BlogUser
{
protected $user;
protected $entities;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Yes, you are doing it in wrong way. Let's look at your code:
# call to undefined object method getDoctrine()
$em = $this->getDoctrine()->getManager();
# call to undefined object method get()
$user = $this->get('security.context')->getToken()->getUser();
You cannot call getting entitymanager and security.context in your service in the same way like in your controller. Instead, you have to inject entitymanager and security.context services. Example:
# services.yml
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
calls:
- [ setUserFromSecurityContext, [ #security.context ]]
- [ setEntityManager, [ #doctrine.orm.entity_manager ]]
And improved service:
namespace Catablog\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
private $entityManager;
private $user;
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function setUserFromSecurityContext(SecurityContext $securityContext)
{
# notice, there are a cases when `getToken()` returns null, so improve this
$this->user = $securityContext->getToken()->getUser();
}
public function getEntities(){
# your code here
}
}
More info about Dependency injection
You are looking on how to 'inject' other services into your custom service. Take a look at Service Container documentation.
In your case, you can inject doctrine.orm.entity_manager and security.context services into your BlogUser class via constructor injection. For example:
class BlogUser {
public function __construct($em, $securityContext) {
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
And configure your service as the following:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]

Resources