I am trying to build a validator that will check against the database for some values. For this I need to inject inside a service the entityManager and give an alias to my Validation method as documented in Symfony official documentation.
The problem is that after doing everything by the book I am still getting an error saying that the entityManager is null:
Catchable Fatal Error: Argument 1 passed to XXX\CommonBundle\Validator\Constraints\IsSingleEntryValidator::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /var/www/XXX/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php on line 71 and defined
My service:
XXX.validators.is_single_entry:
class: XXX\CommonBundle\Validator\Constraints\IsSingleEntryValidator
arguments:
- "#doctrine.orm.default_entity_manager"
tags:
- { name: validator.constraint_validator, alias: single_entry_validation }
And the validator class:
class IsSingleEntryValidator extends ConstraintValidator
{
/**
* #var EntityManager
*/
protected $em;
/**
* Constructor
*
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
...
}
public function validateBy()
{
return 'single_entry_validation';
}
}
And the use of validator:
/**
* #ORM\Column(name="is_primary", type="boolean", nullable=true)
* #SiteAssert\IsSingleEntry(message="validator.single.entry")
*/
protected $isPrimary;
#Ragdata - doctrine.orm.default_entity_manager Doctrine\ORM\EntityManager
There are actually 2 mistakes in my code.
Calling of validatedBy() function
This function should be called inside the IsSingleEntry class and not IsSingleEntryValidator
Method name should be diferent
I call the method validateBy() but the correct function name should be validatedBy()
So the code should be looking like this now:
IsSingleEntry
class IsSingleEntry extends Constraint
{
public $message = "The value already exists in the database";
/**
* #return string
*/
public function validatedBy()
{
return 'single_entry_validation';
}
}
IsSingleEntryValidator
class IsSingleEntryValidator extends ConstraintValidator
{
/**
* #var EntityManager
*/
protected $em;
/**
* Construct
*
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Validate
*
* #param mixed $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
$oActiveExists = $this->em->getRepository('DatabaseBundle:Languages')->findOneByIsPrimary(true);
if ($oActiveExists) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Related
I'm tracing a weird error in a Symfony 2 app and I'd like to know if there's a way to print log messages from a Repository PHP file. For example:
class OrderEntityRepository extends EntityRepository
{
/**
*
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
[...]
/* I'D LIKE TO LOG SOME VARIABLES FROM HERE */
}
}
I've tried using error_log() but nothing happens.
Is this possible? Thanks in advance,
It's possible but it's usually not a good practice. The good thing to do is to send back the Repository result to your Controller or Service and you log from them an error or something else.
But if you still want to do it, Repository are like services (when you implements ServiceEntityRepository see this slide for more information). If you want to log something specific inside you have to inject the LoggerInterface into your Repository configuration (like you do with service).
In your service.yml (or xml) if you don't use autowire:
Your\Repository:
arguments: ['#logger']
In your repository class:
/**
* #var LoggerInterface
*/
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
On symfony 3.8 I have
class BlahRepository extends ServiceEntityRepository
{
/* #var ContainerInterface $container */
private $container;
/* #var LoggerInterface $logger */
private $logger;
public function __construct(RegistryInterface $registry, ContainerInterface $container, LoggerInterface $logger)
{
parent::__construct($registry, Blah::class);
$this->container = $container;
$this->logger = $logger;
}
}
and I am able to use $this->logger->info("text")
I think the trick may be extending ServiceEntityRepository
In order to use dependency injection for Doctrine entity repositories, you can create a custom RepositoryFactory.
Tested on Symfony 3.4.
<?php
namespace AppBundle\Doctrine;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class RepositoryFactory implements RepositoryFactoryInterface, LoggerAwareInterface
{
/** #var DefaultRepositoryFactory */
protected $defaultRepositoryFactory;
/** #var LoggerInterface */
private $logger;
/**
* #required (for autowiring)
* #param LoggerInterface $logger (Monolog will be the default one)
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #see Configuration::getRepositoryFactory()
*/
public function __construct()
{
$this->defaultRepositoryFactory = new DefaultRepositoryFactory();
}
/**
* Gets the repository for an entity class.
*
* #param EntityManagerInterface $entityManager
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
$repository = $this->defaultRepositoryFactory->getRepository($entityManager, $entityName);
if ($repository instanceof LoggerAwareInterface && $this->logger !== null) {
$repository->setLogger($this->logger);
}
return $repository;
}
}
Declare it in Doctrine configuration.
# app/config.yml
doctrine:
# ...
orm:
repository_factory: AppBundle\Doctrine\RepositoryFactory
And finally, make your repository class implement LoggerAwareInterface.
class OrderEntityRepository extends EntityRepository implements LoggerAwareInterface
{
/** #var LoggerInterface */
private $logger;
/**
* #param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
//[...]
$this->logger->alert('message');
}
}
You can also make a LoggerAwareTrait trait to spare yourself some code duplication.
I have the following phpunit test for testing the controllers:
The DefaultControllerTest:
namespace Tests\AppBundle\Controller;
use Tests\AppBundle\Controller\BasicHttpController;
use AppBundle\DataFixtures\Test\DummyUserFixtures;
/**
* #testtype Functional
*/
class DefaultControllerTest extends BasicHttpController
{
/**
* {#inheritdoc}
*/
public function setUp()
{
$fixture = new DummyUserFixtures();
$fixture->load($this->entityManager);
}
/**
* Testing the Behavior when visiting the index page
*/
public function testIndex()
{
$client = $this->client;
$router=$client->getContainer()->get('router');
$crawler = $client->request('GET', '/');
$response=$client->getResponse();
$this->assertTrue($client->getResponse()->isRedirect());
$this->assertEquals($router->getRouteCollection()->get('fos_user_security_login')->getPath(),$response->headers->get('Location'));
//#todo Create Dummy Users
// $this->checkPanelAfterSucessfullLogin($crawler);
}
}
That extends the following test BasicHttpController (try to apply the DRY principle):
namespace Tests\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
class BasicHttpController extends WebTestCase
{
protected $entityManager=null;
protected $client=null;
/**
* {#inheritdoc}
*/
public function __construct()
{
parent::__construct();
$this->client = static::createClient();
$container = $this->client->getContainer();
$doctrine = $container->get('doctrine');
$this->entityManager=$doctrine->getManager();
}
/**
* Remove all entities from the database
*/
protected function truncateEntities()
{
$purger = new ORMPurger($this->entityManager());
$purger->purge();
}
/**
* {#inheritdoc}
*/
public function tearDown()
{
$this->truncateEntities();
}
/**
* #param username String the user's username
* #param passwoρd String the user's password
*/
protected function checkPanelAfterSucessfullLogin($crawler,string $username,string $password)
{
//Submitting the form
$form=$crawler->selectButton('_submit')->form();
$form['_username']=$username;
$form['_password']=$password;
$crawler=$crawler->submit($form);
$response=$client->getResponse();
$this->assertTrue($client->getResponse()->isRedirect());
$client->followRedirect();
//Checking header
$headerDom=$crawler->filter('header')->childen()->filter('nav.navbar')->children();
$this->assertCount(1,$headerDom->find('a.navbar-brand')); //homepage link
$this->assertCount(1,$headerDom->find('a.btn-danger')); //Logout button
}
}
As you can see I try to load the following fixture:
namespace AppBundle\DataFixtures\Test;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\Common\Persistence\ObjectManager;
class DummyUserFixtures extends AbstractFixture implements OrderedFixtureInterface,ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
private $container=null;
/**
* {#inheritDoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* Generic function that creates a user with provided information.
* #param $name {String} The user's name
* #param $surname {String} The user's surname
* #param $username {String} The user's username
* #param $password {String} The user's password
* #param $email {String} The user's recovery email
* #param $role {String} The user's system role
* #param $phone {String | null} The user's phone number
* #param $organization {String|null} The user's organization
* #param $occupation {String|null} The user's occupation
*
* #return AppBundle\Entity\User
*/
private function createUser($name,$surname,$username,$password,$email,$role,$phone=null,$organization=null,$occupation=null)
{
$fosUserManager=$this->container->get('fos_user.user_manager');
/**
* #var AppBundle\Entity\User
*/
$user=$fosUserManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
$user->setEnabled(true);
$user->setRoles(array($role));
$user->setName($name);
$user->setSurname($surname);
if($phone){
$user->setPhone($phone);
}
if($organization){
$user->setOrganization($organization);
}
if($occupation){
$user->setOccupation($occupation);
}
$fosUserManager->updateUser($user, true);
return $user;
}
/**
* {#inheritDoc}
*/
public function load(ObjectManager $manager)
{
$this->createUser('John','Doe','jdoe','simplepasswd','jdoe#example.com','ROLE_USER','+3021456742324','Acme Products','Soft Engineer');
$this->createUser('Jackie','Chan','jchan','thesimplepasswd','jackiechan#example.com','ROLE_ADMIN','+302141232324','Holywood','Actor');
$this->createUser('Chuck','Norris','chuck_norris','unhackablepasswd','chucknorris#example.com','ROLE_SUPERADMIN',null,'Universe','Master');
}
public function getOrder()
{
return 1;
}
}
But for some reason I get the following error:
There was 1 error:
1) Tests\AppBundle\Controller\DefaultControllerTest::testIndex
Error: Call to a member function get() on null
/home/vagrant/code/src/AppBundle/DataFixtures/Test/DummyUserFixtures.php:50
/home/vagrant/code/src/AppBundle/DataFixtures/Test/DummyUserFixtures.php:87
/home/vagrant/code/tests/AppBundle/Controller/DefaultControllerTest.php:19
Further debugging has proved that the error is triggered by the following line in DummyUserFixtures:
$fosUserManager=$this->container->get('fos_user.user_manager');
So do you know how to load the data via fixtures?
In order to get it working you should set the service container you generate from the static::createClient() method and pass it via the $fixture->setContainer($container)
So a good approach is to define the container as protected instance variable to the BasicHttpController so any Test class (eg. the DefaultControllerTest in your case) is able to load the fixtures accordingly.
So using the setUp method and instance variables of BasicHttpController should be the following:
//Namespace declaration goes there
class BasicHttpController extends WebTestCase
{
protected $entityManager=null;
protected $client=null;
protected $container=null;
/**
* {#inheritdoc}
*/
public function setUp()
{
$this->client = static::createClient();
$this->container = $this->client->getContainer();
$doctrine = $this->container->get('doctrine');
$this->entityManager=$doctrine->getManager();
}
// Rest methods here
}
Note: on classes that are getting inherited from BasicHttpController you can define the setUp like that:
public function setUp()
{
parent::setUp();
// Add extra stuff here
}
So you can do more setUp bootstrapping before tests.
I need to use a service in one of my entities but I don't know how to get the container. My attributes $numHeure and $numSem are conversions of $dateDebut.
<?php
namespace Agnez\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* EdtHeure
*
* #ORM\Table(name="agnez_edt_heure")
* #ORM\Entity(repositoryClass="Agnez\CoreBundle\Repository\EdtHeureRepository")
*/
class EdtHeure
{
/**
*#var datetime
*#ORM\Column(type="datetime", name="dateDebut")
*/
private $dateDebut;
/**
*#var int
*#ORM\Column(type="int", name="numHeure")
*/
private $numHeure;
/**
*#var int
*#ORM\Column(type="int", name="numSem")
*/
private $numSem;
/**
* Set dateDebut
*
* #param \DateTime $dateDebut
*
* #return EdtHeure
*/
public function setDateDebut($dateDebut)
{
$this->dateDebut = $dateDebut;
$servicedate = $this->container->get('agnez_core.servicedate');
$this->numSem=$servicedate->numSem($date);
$this->numHeure=$servicedate->numHeure($date);
return $this;
}
}
I got the error:
Notice: Undefined property:
Agnez\CoreBundle\Entity\EdtHeure::$container
I don't think you need a service in your entity, and you should avoid it.
1) You can use a doctrine Event [documentation]
public function __construct(ServiceDate servicedate)
{
$this->servicedate = $servicedate
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof EdtHeure) {
return;
}
$entityManager = $args->getEntityManager();
// Call your service here
}
2) An other way is to call the service outside your entity
public function setDateDebut($dateDebut, $numSem, $numHeure)
And to call it outside, in a service EdtHeureUpdater. Its responsability will be to call various needed services and made change to your entity.
public function __construct(ServiceDate servicedate)
{
$this->servicedate = $servicedate
}
public function updateHeure(EdtHeure $edt, \DateTime $date)
{
$numSem = $this->servicedate->numSem($date);
$numHeure = $this->servicedate->numHeure($date)
$edt->setDateDebut($dateDebut, $numSem, $numHeure)
}
I am currently working on developing a project in SAAS, each client can access his platform by a personalized url (site1.com, site2.com, etc.).
For each domain name a set of template customization data is defined in the back office and I must be able to access it from my Twig files. So I defined a listener on the kernerl.request event that adds a global variable to Twig based on the current domain name. Everything works fine in most cases, except when a page is first displayed, Twig must be run upstream and I get the following error:
Unable to add global "site" as the runtime or the extensions have
already been initialized.
Listener class
class SiteListener
{
public function __construct(
SiteHelper $siteHelper,
\Twig_Environment $twig
) {
$this->siteHelper = $siteHelper;
$this->twig = $twig;
}
/**
* Add current contexts to twig global.
*/
public function addContextsToTwigGlobal(GetResponseEvent $event)
{
$this->twig->addGlobal('site', $this->siteHelper);
}
}
Listener service declaration
multisite.listener.site:
class: MultisiteBundle\Listener\SiteListener
arguments:
- "#multisite.helper.site"
- "#twig"
tags:
- { name: kernel.event_listener, event: kernel.request, method: addContextsToTwigGlobal }
SiteHelper service
class SiteHelper
{
/**
* #var RequestStack
*/
protected $requestStack;
/**
* #var ContextConfigManager;
*/
protected $contextConfigManager;
/**
* #var ContextConfig
*/
protected $contextConfig;
public function __construct(
RequestStack $requestStack,
ContextConfigManager $contextConfigManager
) {
$this->requestStack = $requestStack;
$this->contextConfigManager = $contextConfigManager;
$this->contextConfig = $this->contextConfigManager
->findByHostOrStandard($this->getHost());
}
/**
* Get host from current request.
*
* #return string|null
*/
public function getHost()
{
$request = $this->requestStack->getCurrentRequest();
return ($request) ? $request->getHost() : null;
}
/**
* Get current context config
*
* #return ContextConfig
*/
public function getContextConfig()
{
return $this->contextConfig;
}
}
Any idea ?
I decided to write a Twig function to avoid this kind of problem. This seem to be a good solution.
class SiteExtension extends \Twig_Extension
{
/**
* #var SiteHelper
*/
private $siteHelper;
/**
* Constructor.
*
* #param SiteHelper $siteHelper
*/
public function __construct(SiteHelper $siteHelper)
{
$this->siteHelper = $siteHelper;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_Function('site', array($this->siteHelper, 'getContext')),
);
}
}
I have the following unit test code in symfony:
<?php
// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Shopious\MainBundle\Tests;
class ShippingCostTest extends \PHPUnit_Framework_TestCase
{
public function testShippingCost()
{
$em = $this->kernel->getContainer()->get('doctrine.orm.entity_manager');
$query = $em->createQueryBuilder();
$query->select('c')
->from("ShopiousUserBundle:City", 'c');
$result = $query->getQuery()->getResult();
var_dump($result);
}
}
and I am trying to access the entity manager here, howver it always gives me this error:
Undefined property: Acme\MainBundle\Tests\ShippingCostTest::$kernel
To achieve this you need to create a base test class (let's call it KernelAwareTest) with following contents:
<?php
namespace Shopious\MainBundle\Tests;
require_once dirname(__DIR__).'/../../../app/AppKernel.php';
/**
* Test case class helpful with Entity tests requiring the database interaction.
* For regular entity tests it's better to extend standard \PHPUnit_Framework_TestCase instead.
*/
abstract class KernelAwareTest extends \PHPUnit_Framework_TestCase
{
/**
* #var \Symfony\Component\HttpKernel\Kernel
*/
protected $kernel;
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* #var \Symfony\Component\DependencyInjection\Container
*/
protected $container;
/**
* #return null
*/
public function setUp()
{
$this->kernel = new \AppKernel('test', true);
$this->kernel->boot();
$this->container = $this->kernel->getContainer();
$this->entityManager = $this->container->get('doctrine')->getManager();
$this->generateSchema();
parent::setUp();
}
/**
* #return null
*/
public function tearDown()
{
$this->kernel->shutdown();
parent::tearDown();
}
/**
* #return null
*/
protected function generateSchema()
{
$metadatas = $this->getMetadatas();
if (!empty($metadatas)) {
$tool = new \Doctrine\ORM\Tools\SchemaTool($this->entityManager);
$tool->dropSchema($metadatas);
$tool->createSchema($metadatas);
}
}
/**
* #return array
*/
protected function getMetadatas()
{
return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}
Then your own test class will be extended from this one:
<?php
namespace Shopious\MainBundle\Tests;
use Shopious\MainBundle\Tests\KernelAwareTest;
class ShippingCostTest extends KernelAwareTest
{
public function setUp()
{
parent::setUp();
// Your own setUp() goes here
}
// Tests themselves
}
And then use parent's class methods. In your case, to access entity manager, do:
$entityManager = $this->entityManager;