I am trying to unit test a form which has 2 dependencies (ObjectManager and EventDispatcher)
I had tried to follow official doc but without success.
My testing file:
<?php
namespace Lch\MediaBundle\Tests\Form;
use Lch\MediaBundle\Form\AddImageType;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class AddImageTypeTest extends TypeTestCase
{
private $entityManager;
private $eventDispatcher;
protected function setUp()
{
$this->entityManager = $this->createMock(ObjectManager::class);
$this->eventDispatcher = $this->createMock(EventDispatcher::class);
parent::setUp();
}
protected function getExtensions()
{
$type = new AddImageType($this->entityManager, $this->eventDispatcher);
return array(
new PreloadedExtension(array($type), array()),
);
}
public function testSubmitValidData()
{
$form = $this->factory->create(AddImageType::class);
}
}
I got this error when I execute my test suite:
TypeError: Argument 1 passed to
LCH\MediaBundle\Form\AddImageType::__construct() must implement
interface Doctrine\Common\Persistence\ObjectManager, none given,
called in
/home/matthieu/www/lch/media/src/Lch/MediaBundle/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php
on line 85
It seems that the job I do in the getExtensions method is not working, but cannot figure it out.
Does anyone have a clue?
ObjectManager is an interface, meaning you can't instantiate or pass it directly to other constructors.
If you are using Doctrine, replace it with Doctrine\ORM\EntityManager which implements ObjectManager interface and can be instantiated, otherwise replace it with your own implementation.
<?php
namespace Lch\MediaBundle\Tests\Form;
use Lch\MediaBundle\Form\AddImageType;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class AddImageTypeTest extends TypeTestCase
{
private $entityManager;
private $eventDispatcher;
protected function setUp()
{
$this->entityManager = $this->createMock(EntityManager::class);
$this->eventDispatcher = $this->createMock(EventDispatcher::class);
parent::setUp();
}
protected function getExtensions()
{
$type = new AddImageType($this->entityManager, $this->eventDispatcher);
return array(
new PreloadedExtension(array($type), array()),
);
}
public function testSubmitValidData()
{
$form = $this->factory->create(AddImageType::class);
}
}
Related
In previous versions of symfony, you could fetch objects like this
`
public function someMethod()
{
$method = $this->getDoctrine()->getRepository(Method::class)->findOneBy(array('id' => 1));
return $method;
}
`
This was easy because it meant that you could easily make global variables in the twig.yaml file and have dynamic content all around your page.
Now in symfony as far as i know, an argument of ManagerRegistry has to be passed as a argument all the time. Am I being too close minded or is there a work around for this problem?
I tried extending classes and have it pass down that way but it gave me the same workaround errors.
In a controller you can do either this :
class MyController extends AbstractController {
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
}
or
class MyController extends AbstractController {
{ ... }
public function someMethod(EntityManagerInterface $em) {
$em->getRepository(SomeClass::class)
}
}
I took a controller as an example but you can do this into your services. If you work with multiple entity manager, you can use the ManagerRegistry that extends AbstractManagerRegistry and use the method getManager($name)
There is no real workaround for this as you always need to inject it. Depending on what you want to achieve, may be there is solution that can help.
you can also directly inject the repository:
public function someMethod(EntityRepository $repository) {
$entities = $repository->findAll();
}
and of course you can inject it in the construct as well:
class MyController extends AbstractController {
private EntityRepository $repository;
public function __construct(EntityRepository $repository) {
$this->repository = $repository;
}
}
if you are on php 8 you can write:
class MyController extends AbstractController {
public function __construct(private EntityRepository $repository) {}
}
I trying to load the api_platform.iri_converter but get an error:
The \"api_platform.iri_converter\" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.
This is the code:
declare(strict_types=1);
namespace App\Security\Authorization\Voter;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class BaseVoter extends Voter
{
public ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
}
declare(strict_types=1);
namespace App\Security\Authorization\Voter;
class VenueVoter extends BaseVoter
{
protected function voteOnAttribute(): bool
{
/** #var User $tokenUser */
$tokenUser = $token->getUser();
if (self::VENUE_CREATE === $attribute) {
$iri = $this->container->get('api_platform.iri_converter')->getItemFromIri($valueWithIri);
}
}
}
Do not inject the Container.
Instead, inject the IriConverter directly.
use ApiPlatform\Core\Bridge\Symfony\Routing\IriConverterInterface;
abstract class BaseVoter extends Voter
{
public IriConverterInterface $iriConverter;
public function __construct(IriConverterInterface $iriConverter)
{
$this->iriConverter = $iriConverter;
}
}
I am trying to test my form extending TypeTestCase class
class ProjectTypeTest extends TypeTestCase
{
private $entityManager;
private $securityContext;
private $translator;
private $formFactory;
protected function setUp()
{
// mock any dependencies
$this->entityManager = $this->createMock("Doctrine\ORM\EntityManagerInterface");
$this->securityContext = $this->createMock("Symfony\Component\Security\Core\SecurityContextInterface");
$this->translator = $this->createMock("Symfony\Component\Translation\TranslatorInterface");
$this->formFactory = $this->createMock("AppBundle\FormTemplate\Factory\FormFactory");
}
public function testSubmitValidData()
{
$type = new ProjectType($this->entityManager,$this->securityContext, $this->translator, $this->formFactory);
$this->factory->create($type);
}
}
But, When I called $this->factory->create($type), returns :
Call to a member function create() on null
the factory property is null !
I'am using Symfony\Component\Form\Test\TypeTestCase and also I've used use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase and returns the same outcome.
Or, What am I doing bad ?.
Or, How do I test a Form ?
The problem is $this->factory is not being initialized. That should be done by FormIntegrationTestCase, which is extended by TypeTestCase and then by your test class.
Your setUp() is overriding the original setUp(), which initializes $this->factory, so you should call the parent one:
protected function setUp()
{
parent::setUp();
// mock any dependencies
$this->entityManager = $this->createMock("Doctrine\ORM\EntityManagerInterface");
$this->securityContext = $this->createMock("Symfony\Component\Security\Core\SecurityContextInterface");
$this->translator = $this->createMock("Symfony\Component\Translation\TranslatorInterface");
$this->formFactory = $this->createMock("AppBundle\FormTemplate\Factory\FormFactory");
}
I'm trying to expose some data with soap.
here's my controller holding the server (everything is normal here):
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Json\Json;
use Zend\Soap\Server;
use Zend\Soap\AutoDiscover;
class ExportController extends AbstractActionController
{
private $_options = array('soap_version' => SOAP_1_2);
private $_URI = '/export';
private $_WSDL_URI = '/export?wsdl';
private $wsdl;
public function indexAction() {
if (isset($_GET['wsdl'])) {
$this->handleWSDL();
} else {
$this->handleSOAP();
}
return $this->getResponse();
}
private function handleWSDL() {
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$autodiscover = new AutoDiscover(new \Zend\Soap\Wsdl\ComplexTypeStrategy\ArrayOfTypeSequence());
$autodiscover->setClass('Application\WebService\ExportClass')
->setUri($serverUrl.$this->_URI)
->setServiceName('MySoapService');
$autodiscover->handle();
$this->wsdl = $autodiscover->generate();
}
private function handleSOAP() {
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$soap = new Server($serverUrl.$this->_WSDL_URI, $this->_options);
$soap->setClass('Application\WebService\ExportClass');
$soap->handle();
}
}
then here is the class I'm exporting:
namespace Application\WebService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceManagerInterface;
use Doctrine\ORM\EntityManager;
use Zend\Json\Json;
use Parcours\Entity\Parcours;
class ExportClass implements ServiceLocatorAwareInterface
{
protected $em;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
$this->em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
return $this->em;
}
/**
* Dit bonjour!
*
*
* #return string
*/
public function helloWorld(){
return 'coucou';
}
/**
* Retourne le titre d'un parcours
*
* #param integer $id
* #return array
*/
public function getParcours($id){
$parcours = $this->getEntityManager()->getRepository('Parcours\Entity\Parcours')->findOneBy(array('id'=>$id));
return $parcours->toArray();
}
}
I also have a test client, the first function: helloWorld() is working fine but the second one: getParcours($id) is returning the following error:
Call to a member function get() on a non-object
It seams like getServiceLocator() is returning null. I'm using a similar piece of code an AbstractActionController: ParcoursController which is working great. Why can't I do that here?
[EDIT]
Ok I've tried something else, instead of using the EntityManager in the ExportClass I've made a get function in my ParcoursController and call this function into the ExportClass. My ParcoursController is already using the EntityManager to display my data into pages so it should work. But the result is the same.
It seems like i should somehow pass my serviceLocator through the SOAP service. I don't think that's a good idea.
OK great I nailed it.
Here is my working conf, hope it helps someone.
All changes from the above example were:
A: added this to module.php (ExportModel is ExportClass from last example i just changed name and namespace)
return array(
'invokables' => array(
'Application\Model\ExportModel' => 'Application\Model\ExportModel',
),
)
B: I gave the instanciated model to my SoapServer
private function handleSOAP() {
$exportModel = $this->getServiceLocator()->get('Application\Model\ExportModel');
$serverUrl = strtolower(dirname($_SERVER['SERVER_PROTOCOL']))."://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT']."/Moving-BO/public";
$soap = new Server($serverUrl.$this->_WSDL_URI, $this->_options);
$soap->setClass('Application\Model\ExportModel');
$soap->setObject($exportModel);
$soap->handle();
That's all.
I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/