Symfony2: Adding a collection based on a table-inheritance structure to a FormView - forms

I am working on a Symfony2/Doctrine app which uses class-table-inheritance (http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html#class-table-inheritance) to manage Complaints in a Consult. Each Consult can have many Complaints (OneToMany), and each different type of Complaint has a different structure and appearance. Complaints are a collection and are added dynamically with JS.
At this point, I am able to persist the Complaints and link them to the Consults by recasting them as the appropriate types in the Controller before persistence. I have run into some issues with this and I'm planning on migrating this to a form event (http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html) or something of that nature to streamline the process.
The problem that I am running into at this point, however, is that I am unable to display existing Complaints in a view using the FormView because the form builder demands that I set the type of the collection to be displayed. If each Consult had only one type of Complaint, that would be fine, but they can have multiple types, and setting the type in the form builder limits me to that one type.
Is there some approach that I can take to stop the FormView from tyring to convert to string in the absence of a type or some way to dynamically detect and assign the type on a per-Complaint basis (using $complaint->getComplaintType(), perhaps)?
<?php
namespace Acme\ConsultBundle\Entity;
class Consult
{
/**
* #ORM\OneToMany(targetEntity="Acme\ConsultBundle\Entity\ComplaintBase", mappedBy="consult", cascade={"persist", "remove"})
*/
protected $complaints;
}
?>
<?php
namespace Acme\ConsultBundle\Entity;
/**
* Acme\ConsultBundle\Entity\ConsultBase
*
* #ORM\Entity
* #ORM\Table(name="ConsultComplaintBase")
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="complaint_name", type="string")
* #ORM\DiscriminatorMap({
* "ComplaintDefault" = "Acme\ConsultBundle\Entity\ComplaintDefault",
* "ComplaintRosacea" = "Acme\ConsultBundle\Entity\ComplaintRosacea",
* "ComplaintBotox" = "Acme\ConsultBundle\Entity\ComplaintBotox",
* "ComplaintAcne" = "Acme\ConsultBundle\Entity\ComplaintAcne",
* "ComplaintUrticaria" = "Acme\ConsultBundle\Entity\ComplaintUrticaria",
* })
*/
abstract class ComplaintBase
{
/**
* #ORM\ManyToOne(targetEntity="Acme\ConsultBundle\Entity\Consult", inversedBy="complaints")
* #ORM\JoinColumn(name="consult_id", referencedColumnName="id")
*/
protected $consult;
/**
* #ORM\Column(type="string", length="255")
*/
protected $complaintType;
}
?>
<?php
namespace Acme\ConsultBundle\Form\Type;
class ConsultType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('complaints', 'collection', array(
// 'type' => new ComplaintUrticariaType(),
'error_bubbling' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
}
}
?>

Not exactly sure that it will work with the collection, but it certainly works with a single form. Please try this idea.
First make a form for your basic entity ComplaintBase
class ComplaintForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$subscriber = new ComplaintSubscriber($builder);
$builder->addEventSubscriber($subscriber);
/* your fields */
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\ConsultBundle\Entity\ComplaintBase',
));
}
}
Then in subscriber you can define additional fields based on submitted entity type.
class ComplaintSubscriber implements EventSubscriberInterface
{
private $factory;
private $builder;
public function __construct(FormBuilderInterface $builder)
{
$this->factory = $builder->getFormFactory();
$this->builder = $builder;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$class = get_class($data);
if( $class === 'Acme\ConsultBundle\Entity\ComplaintDefault' ) {
$this->processDefault($data, $form);
}
elseif( $class === 'Acme\ConsultBundle\Entity\ComplaintRosacea' ) {
$this->processRosacea($data, $form);
}
elseif( $class === 'Acme\ConsultBundle\Entity\ComplaintBotox' ) {
$this->processBotox($data, $form);
}
else {
#NOP
}
}
protected function processDefault(Entity\ComplaintDefault $node, FormInterface &$form)
{
#NOP
}
protected function processRosacea(Entity\ComplaintRosacea $node, FormInterface &$form)
{
$form->add($this->factory->createNamed('some_field', 'text'));
}
protected function processBotox(Entity\ComplaintBotox $node, FormInterface &$form)
{
$form->add($this->factory->createNamed('other_field', 'text'));
}
}

Related

Bootstrap TagsInput with EntityType dropdown values (Symfony)

I'm making a form to register books (Symfony 4), each with a unique publisher (ManyToOne) and one or many authors (ManyToMany). Both fields are filled in the form as tags, with Bootstrap TagsInput, so that if the user types in those fields, they will suggest the list of values in the DB tables, and if not, the new values will be inserted when submitting, along with the other book data.
I followed the official example of Best Practices on the Symfony website, and the tagging system works for authors, because it's a CollectionType, but not with the publisher, because it's an EntityType. How could I adapt it? Fails to transform publisher values into comma-separated strings so it can be recognized by TagsInput.
Book Entity (App\Entity\Book.php)
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
class Book
{
/**
* #var \Publisher
*
* #ORM\ManyToOne(targetEntity="Publisher", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(nullable=false, name="publisher_id", referencedColumnName="id")
* })
*/
private $publisher;
/**
* #var Author[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Author", cascade={"persist"})
* #ORM\JoinTable(name="authors_books")
*/
private $authors;
public function __construct()
{
$this->authors = new ArrayCollection();
}
public function getPublisher()
{
return $this->publisher;
}
public function getAuthors(): Collection
{
return $this->authors;
}
}
Book Form (App\Form\BookType.php)
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use App\Form\Type\PublisherType;
use App\Form\Type\AuthorType;
class BookType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('publisher', PublisherType::class, array(
'label' => 'Publisher',
))
->add('authors', AuthorType::class, array(
'label' => 'Author/s'
))
}
}
AuthorType
namespace App\Form\Type;
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
use App\Form\DataTransformer\AuthorToStringTransformer;
use App\Repository\AuthorRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class AuthorType extends AbstractType {
private $authors;
public function __construct(AuthorRepository $authors_repo)
{
$this->authors = $authors_repo;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->addModelTransformer(new CollectionToArrayTransformer(), true)
->addModelTransformer(new AuthorToStringTransformer($this->authors), true)
;
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['authors'] = $this->authors->findAll();
}
public function getParent()
{
return TextType::class;
}
}
PublisherType
namespace App\Form\Type;
use App\Form\DataTransformer\EntityToArrayTransformer;
use App\Form\DataTransformer\PublisherToStringTransformer;
use App\Repository\PublisherRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class PublisherType extends AbstractType {
private $publishers;
public function __construct(PublisherRepository $publisher_repo) {
$this->publishers = $publisher_repo;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->addModelTransformer(new EntityToArrayTransformer(), true)
->addModelTransformer(new PublisherToStringTransformer($this->publishers), true);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$publishers = $this->publishers->findAll();
}
public function getParent()
{
return TextType::class;
}
}
CollectionToArrayTransformer
namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Form\DataTransformerInterface;
class CollectionToArrayTransformer implements DataTransformerInterface
{
public function transform($collection)
{
if (null === $collection) {
return [];
}
if (\is_array($collection)) {
return $collection;
}
return $collection->toArray();
}
}
EnityToArrayTransformer
namespace App\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
class EntityToArrayTransformer implements DataTransformerInterface
{
public function transform($entity)
{
if (null === $entity) {
return [];
}
return [$entity];
}
}
AuthorToStringTransformer
namespace App\Form\DataTransformer;
use App\Entity\Author;
use App\Repository\AuthorRepository;
use Symfony\Component\Form\DataTransformerInterface;
class AuthorToStringTransformer implements DataTransformerInterface
{
private $authors;
public function __construct(AuthorRepository $authors)
{
$this->authors = $authors;
}
public function transform($authors): string
{
/* #var Author[] $authors */
return implode(',', $authors);
}
public function reverseTransform($string): array
{
...
}
}
PublisherToStringTransformer
namespace App\Form\DataTransformer;
use App\Entity\Publisher;
use App\Repository\PublisherRepository;
use Symfony\Component\Form\DataTransformerInterface;
class PublisherToStringTransformer implements DataTransformerInterface
{
private $publishers;
public function __construct(PublisherRepository $publishers)
{
$this->publishers = $publishers;
}
public function transform($publisher): string
{
/* #var Publisher[] $publisher */
return implode(',', $publisher);
}
public function reverseTransform($publisher): string
{
...
}
}
Form Twig
{{ form_widget(form.publisher, {'attr': {'class': class, 'data-toggle': 'tagsinput', 'data-publishers': form.publisher.vars.publishers|json_encode } }) }}
{{ form_widget(form.publisher, {'attr': {'class': class, 'data-toggle': 'tagsinput', 'data-authors': form.publisher.vars.authors|json_encode } }) }}
This is the code I use for Editors and Authors, so that you can compare the one that works and the one that doesn't work. They are not so different, but something is wrong, and I don't know what it is or what I need to change.
Ok, I've found a solution. I have made all the changes in both Transformers, because the problem was the format to which I converted the entity and vice versa.

Embedding a dynamic form in Symfony2

General info
I am working with Symfony 2.8. In this question two entities are the subject. ObjectElementTask and Activity. I have embedded Activity as a collection inside ObjectElementTask. So far, everything is fine and up and running.
Problem
I added a evenlistener in ActivityType. Because a part of the ActivityForm inside the ObjectElementTaskForm has to be dynamic (based on the authenticated user that is using the form...)
When I view the ActivityForm direct: through the ActivityController (which calls the form with this code: $editForm = $this->createForm('AppBundle\Form\ActivityType', $activity);) the eventlistener is working.
When I view the ObjectElementTaksForm I get a exception:
Type error: Argument 1 passed to AppBundle\Form\ActivityType::__construct() must implement interface Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface, none given, called in /home/vpcont1q/public_html/test/src/AppBundle/Form/ObjectElementTaskType.php on line 62
My Guess
I guess because inside the ObjectElementTaksForm, the ActivityForm gets called on a other way then inside the ActivityController. And then the argument (registered in the services.yml) doesn't get passed. How to make sure the registered service gets called when embedding the form? Or... am I looking in the wrong direction.
Code
Below my Types and the services.yml part:
This is my ActivityType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ActivityType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('executionDate')
->add('status'),
->add('scheduledDate')
// grab the user, do a quick sanity check that one exists
$user = $this->tokenStorage->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'You are NOT an authenticated user!'
);
}
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($user) {
//TODO
});
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Activity'
));
}
}
And this is my ObjectElementTaskType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ObjectElementTaskType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('activities', 'collection', array(
'entry_type' => new ActivityType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\ObjectElementTask',
));
}
}
I have registered the ActivityForm as a service with the token as a argument:
app.form.activity:
class: AppBundle\Form\ActivityType
arguments: ['#security.token_storage']
tags:
- { name: form.type }
EDIT
This seems to work:
Instead of registering the ActivityType as a service I registered the ObjectElementTask as a service (also with the Tokenstorage).
I don't use the ActivityType other then embedded in the ObjectElementTaskType so that's why I removed the ActivityType as a service in services.yml.
app.form.object_element_task:
class: AppBundle\Form\ObjectElementTaskType
arguments: ['#security.token_storage']
tags:
- { name: form.type }
And in the ObjectElementTaskType I added the construct:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ObjectElementTaskType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$tokenStorage = $this->tokenStorage;
And in the buildform I added the tokenstorage as a parameter in the ActivityType class declaration:
->add('activities', 'collection', array(
'entry_type' => new ActivityType($tokenStorage),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
))
The ActivityType I haven't changed. Is this a workaround or the way to do it?

How to register View helper

How to register View helper using factories?
View\Helper\GenerateAnchor::class => GenerateAnchorFactory::class,
I guess you just need settings in configuration file, but I'll show full road to registering view helpers in Zend 3
First, we need view helper:
namespace MyNamespace\View\Helper;
use Zend\View\Helper\AbstractHelper;
class TestViewHelper extends AbstractHelper
{
public function __invoke()
{
// your code...
}
}
If we need inject some dependencies, then we need to create custom factory, however if our view helper doesn't need other services/class then we can skip this step
namespace MyNamespace\Factory\View\Helper;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use MyNamespace\View\Helper\TestViewHelper;
class TestViewHelperFactory implements FactoryInterface
{
/**
*
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
* #return TestViewHelper
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$class = $requestedName ? $requestedName : TestViewHelper::class;
$auth = $container->get('MyCustomAuth');
$viewHelper = new $class($auth);
return $viewHelper;
}
/**
* Provided for backwards compatibility; proxies to __invoke().
*
* #param ContainerInterface|ServiceLocatorInterface $container
* #return TestViewHelper
*/
public function createService(ServiceLocatorInterface $container)
{
return $this($container, TestViewHelper::class);
}
}
Last step is to register our view helper in module.config.php
'view_helpers' => [
'aliases' => [
'viewPlugin' => View\Helper\TestViewHelper::class,
],
'factories' => [
View\Helper\TestViewHelper::class => InvokableFactory::class // Or use your own factory
]
],
And... in our view template we can use it like this:
index.phtml
<?php echo $this->viewPlugin(); ?>

This value should not be blank

I know that could be a noob error, but I wasted a lot of time in this.
I have a entity that modified with a form. This is my entity:
class RetiroResiduo
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="GestionResiduos\SolicitudRetiroBundle\Entity\SolicitudRetiro")
* #ORM\JoinColumn(name="numeroSolicitudRet_id", onDelete="CASCADE", referencedColumnName="numeroSolicitudRet")
*/
protected $numeroSolicitudRet;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="GestionResiduos\ResiduoBundle\Entity\Residuo")
* #ORM\JoinColumn(name="siglaRepresentativa_id", onDelete="CASCADE",referencedColumnName="siglaRepresentativa")
*/
protected $siglaRepresentativa;
/**
* #ORM\ManyToOne(targetEntity="GestionResiduos\ResiduoBundle\Entity\Contenedor")
* #ORM\JoinColumn(name="nombreContenedor_id", onDelete="CASCADE", referencedColumnName="nombreContenedor")
*/
protected $nombreContenedor;
...
}
This entity is handled with this form:
<?php
// src/Gestionresiudos/SolicitudRetiroBundle/Form/SolicitudRetiroType.php
namespace Gestionresiduos\SolicitudRetiroBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class RetiroResiduoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('siglaRepresentativa')
->add('nombreContenedor')
->add('cantidadContenedores')
->add('peso')
->add('volumen')
->add('solicitar', 'submit')
->add('addotro', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Gestionresiduos\SolicitudRetiroBundle\Entity\RetiroResiduo',
'cascade_validation' => true,
));
}
public function getName()
{
return 'retiroresiduo';
}
}
my action into my controller look like:
public function RetirarResiduoAction($idsol, Request $request)
{
$numSolRetiro = $idsol;
$residuoARetirar = new RetiroResiduo();
//echo gettype($residuoARetirar);
$formulario = $this->createForm(new RetiroResiduoType(), $residuoARetirar);
$formulario->handleRequest($request);
if ($formulario->isValid())
{
....
}
...
}
This is my form into view:
{{ form_start(formulario, {'attr': {'class': 'form-horizontal'}})}}
{{ form_errors(formulario)}}
{{ form_rest(formulario) }}
{{ form_end(formulario)}}
Finally I give you a print from the error to give my interface.
As you can see when I send my form doesn't enter into if(formulario->isvalid()). Obviously, I selected one option into choice. I saw similar questions but don't working for me. I need some advice or something that help me with this bug.
Note: I know that problem should be into my entity but I still don't see.
NOTE2: I'm using symfony2.3

How to unit test a Symfony2 form when it uses a transformer linked to a database

TLDR: I am new to unit tests and I have few questions:
Are my transformer tests well written?
Is there a way to decoupled my transformer tests from the database?
How to test my form with the transformer using the database?
Should I decouple my form from my transformer?
I don't know if my classes are too coupled, if my design is flawed or if my understanding of the unit tests is bad.
Here is some background.
I have a form object with different widgets. One of them is used within a model transformer.
This model transformer uses a connection to the database to retrieve the proper object.
Here is my code:
class BookToStringTransformer implements DataTransformerInterface {
private $om;
public function __construct(ObjectManager $om) {
$this->om = $om;
}
public function transform($book) {
if (!$book instanceof Book) {
return "";
}
return $book->getName();
}
public function reverseTransform($string) {
if (!is_string($string) || !$string) {
return null;
}
$book = $this->om
->getRepository('MyBundle:Book')
->findOneBy(array('name' => $string))
;
if (null === $book) {
throw new TransformationFailedException(sprintf(
'The book "%s" does not exist!', $string
));
}
return $book;
}
}
class ItemType extends AbstractType {
private $om;
public function __construct(ObjectManager $om) {
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$bookTransformer = new BookToStringTransformer($this->om);
$builder->add($builder->create('book', 'text', array(
'required' => false,
))->addModelTransformer($bookTransformer));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Item',
));
}
public function getName() {
return 'mybundle_item';
}
}
I wrote unit tests for the transformer using the KernelTestCase
class BookToStringTransformerTest extends KernelTestCase {
private $name = 'existing name';
private $em;
public function setUp() {
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
}
public function testReverseTransform_whenNameExists_returnsBookObject() {
$transformer = new BookToStringTransformer($this->em);
$book = $transformer->reverseTransform($this->name);
$this->assertInstanceOf('MyBundle\Entity\Book', $book, 'Should return a Book object');
$this->assertEquals($this->name, $book->getName(), 'Should return a Book object with the selected name');
}
/**
* #expectedException Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransform_whenNameDoesNotExist_throwsException() {
$transformer = new BookToStringTransformer($this->em);
$transformer->reverseTransform('unknown name');
}
/**
* #param mixed $invalid_parameter
* #dataProvider provideInvalidParameter
*/
public function testReverseTransform_whenParameterIsInvalid_returnsNull($invalid_parameter) {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertNull($transformer->reverseTransform($invalid_parameter), 'Should return a NULL value');
}
/**
* #return array
*/
public function provideInvalidParameter() {
return [
[null],
[false],
[true],
[''],
[[]],
[new \stdClass()],
];
}
public function testTransform_whenParameterIsBookObject_returnsName() {
$book = $this->em->getRepository('MyBundle:Book')
->findOneBy(array('name' => $this->name));
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertEquals($this->name, $transformer->transform($book), 'Should return a string containing the name');
}
/**
* #param mixed $not_book
* #dataProvider provideInvalidBookObject
*/
public function testTransform_whenParameterIsNotBookObject_returnsEmptyString($not_book) {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertEquals("", $transformer->transform($not_book), 'Should return an empty string to be chained');
}
/**
* #return array
*/
public function provideInvalidBookObject() {
return [
[null],
[123],
['123'],
[[]],
[true],
[new \stdClass()],
];
}
}
As I am new to unit tests, I don't even know if it is the proper way to test that transformer.
I start writing tests for the form object. I am using the TypeTestCase but there is no simple way to get the connection to the database and I can't use the KernelTestCase.
class ItemTypeTest extends TypeTestCase {
/**
* #expectedException \PHPUnit_Framework_Error
*/
public function test_whenCreatedWithNoParameters_raiseException() {
new ItemType();
}
/**
* #expectedException \PHPUnit_Framework_Error
*/
public function test_whenCreatedWithBadParameters_raiseException() {
new ItemType(123);
}
public function test_whenCreatedWithGoodParameters_createsFormObject() {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$type = new ItemType($om);
$form = $this->factory->create($type);
$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}
public function test_whenSubmittedWithGoodData() {
$formData = array(
'name' => 'existing name',
);
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$type = new ItemType($om);
$form = $this->factory->create($type);
$form->submit($formData);
}
}
The last test fails because the transformer does get access to the database since I am passing a mock to the form. So should I get a real object (meaning classes are too coupled) or should I find an other way.
Thank you
The approach is good, in the last method you must mock the repo object and the repo response. In example try this code:
public function test_whenSubmittedWithGoodData() {
$formData = array(
'name' => 'existing name',
);
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$repoMock= $this->getMock('Doctrine\ORM\EntityRepository', array(), array(), '', false);
$om
->expects($this->atLeastOnce())
->method('getRepository')
->withAnyParameters()
->will($this->returnValue($repoMock));
$repoMock
->expects($this->atLeastOnce())
->method('findOneBy')
->withAnyParameters()
->will($this->returnValue($mockedBook));
$type = new ItemType($om);
$form = $this->factory->create($type);
$form->submit($formData);
}