Symfony 3 Testing - "AlreadySubmittedException" on form testing - forms

I have some problems for testing a creation form (for a simple Bank entity) in a SF3 project. Here the test code:
class BankControllerTest extends WebTestCase
{
/**
* Test the creation form action
*/
function testNewAction()
{
$client = static::createClient();
// User must be logged in
$client->request('GET', '/bank/bank/new');
$response = $client->getResponse();
$this->assertEquals(302, $response->getStatusCode());
// Page request test
$client = LoginControllerTest::createClientConnectedTest();
$crawler = $client->request('GET', '/bank/bank/new');
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
// Form testing
$formData = array(
'bank[name]' => 'BankTest '.uniqid(),
'bank[comment]' => 'Tast bank '.uniqid().' comment.',
);
$submitButtonCrawlerNode = $crawler->selectButton('Create');
$form = $submitButtonCrawlerNode->form();
$client->submit($form, $formData);
$response = $client->getResponse();
$container = $client->getContainer();
$this->assertEquals(301, $response->getStatusCode());
}
}
The part which interesting me is the last "Form testing" part, I pasted everything in case of I made a mistake before.
Instead of the "301" httpCode I want, I have a 500 error :
vendor/bin/phpunit src/BillBrother/BankAccountBundle
[...]
1) BillBrother\BankAccountBundle\Test\Controller\BankControllerTest::testNewAction
Failed asserting that 500 matches expected 301.
/home/developer/src/BillBrother/BankAccountBundle/Test/Controller/BankControllerTest.php:47
And saw it is an "AlreadySubmittedException" which generate this error :
[2016-06-30 08:00:18] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Form\Exception\AlreadySubmittedException: "You cannot add children to a submitted form" at /home/developer/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php line 802 {"exception":"[object] (Symfony\\Component\\Form\\Exception\\AlreadySubmittedException(code: 0): You cannot add children to a submitted form at /home/developer/vendor/symfony/symfony/src/Symfony/Component/Form/Form.php:802)"} []
Did I do something in a wrong way?
Thanks in advance for your help.
Here some other parts of code I used in this test:
The Login test file, from where I use the createClientConnectedTest() static function :
/**
* BillBrother\AuthBundle\Controller test class file.
*
* #TODO Write tests
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\AuthBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
/**
* BillBrother\AuthBundle\Controller test class.
*/
class LoginControllerTest extends WebTestCase
{
/**
* Create a connected client.
*
* #param string $login
* #param string $password
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
public static function createClientConnected($login, $password)
{
return static::createClient(array(), array(
'PHP_AUTH_USER' => $login,
'PHP_AUTH_PW' => $password
));
}
/**
* Create a client connected with test account
*
* #return \Symfony\Bundle\FrameworkBundle\Client
*/
public static function createClientConnectedTest()
{
return static::createClientConnected(
'test#email.com',
'password'
);
}
/**
* Test the login form
*/
public function testLoginform()
{
$client = static::createClient();
$crawler = $client->request('GET', '/login');
}
}
The BankType class :
/**
* Bank entity type
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
/**
* Bank type form
*/
class BankType extends AbstractType
{
/**
* Build the form
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('comment', TextareaType::class)
;
}
/**
* Configure the form default options.
*
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BillBrother\BankAccountBundle\Entity\Bank'
));
}
}
The Bank entity class :
/**
* BillBrother bank entity class file.
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use BillBrother\AuthBundle\Entity\User;
use Datetime;
/**
* Bank
*
* #ORM\Table(name="bank")
* #ORM\Entity(repositoryClass="BillBrother\BankAccountBundle\Repository\BankRepository")
*/
class Bank
{
/**
* The bank id.
*
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* Bank creator
*
* The user account who created the bank.
*
* If null, it means the bank was created by the system.
*
* #var User
*
* #ORM\JoinColumn(name="creator_id", nullable=true)
* #ORM\ManyToOne(targetEntity="BillBrother\AuthBundle\Entity\User")
*/
private $creator;
/**
* The bank name.
*
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Comments about the bank.
*
* #var string
*
* #ORM\Column(name="comment", type="text")
*/
private $comment;
/**
* Bank row creation date
*
* #var Datetime
*
* #ORM\Column(name="create_date", type="datetime")
*/
private $createDate;
/**
* Constuctor
*
* Initialize the creation date to the current date
*/
public function __construct()
{
$this->createDate = new Datetime;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name, nullable=tru
* #return Bank
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set comment
*
* #param string $comment
*
* #return Bank
*/
public function setComment($comment)
{
$this->comment = $comment;
return $this;
}
/**
* Get comment
*
* #return string
*/
public function getComment()
{
return $this->comment;
}
/**
* Set creator
*
* #param User $creator
*
* #return Bank
*/
public function setCreator(User $creator = null)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return User
*/
public function getCreator()
{
return $this->creator;
}
}
The controller called :
/**
* BillBrother bank account bundle bank controller class file.
*
* #author Neimheadh <contact#neimheadh.fr>
*/
namespace BillBrother\BankAccountBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use BillBrother\BankAccountBundle\Entity\Bank;
use BillBrother\BankAccountBundle\Form\Type\BankType;
/**
* BillBrother application bank account bank controller.
*/
class BankController extends Controller
{
/**
*/
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
return $form;
}
/**
* Bank account bundle bank list page action.
*
* #Route("/banks", name="billbrother_bankaccount_bank_list")
*/
public function listAction()
{
return $this->render('BillBrotherBankAccountBundle:Bank:list.html.twig');
}
/**
* Create a new bank page action.
*
* #param Request $request
*
* #Route("/bank/new", name="billbrother_bankaccount_bank_new")
*/
public function newAction(Request $request)
{
$bank = new Bank();
$form = $this->_buildForm($request, $bank);
if($form->isValid())
return $this->redirectToRoute(
'billbrother_bankaccount_bank_edit',
array('id'=>$bank->getId())
);
return $this->render(
'BillBrotherBankAccountBundle:Bank:form.html.twig',
array(
'form' => $form->createView()
)
);
}
/**
* Modify a bank page action.
*
* #Route("/bank/edit/{id}", name="billbrother_bankaccount_bank_edit")
*/
public function editAction($id)
{
return $this->render('BillBrotherBankAccountBundle:Bank:form.html.twig');
}
}
And about versions :
✗ bin/console --version
Symfony version 3.1.1 - app/dev/debug
✗ vendor/bin/phpunit --version
PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
✗ php --version
PHP 7.0.6 (cli) (built: May 24 2016 03:29:56) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

I saw where my problem was from. In my Controller :
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
return $form;
}
We see I add my "submit" button after the form handleRequest(). By moving the code :
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
just after my form creation, everything is ok.
The final function code :
private function _buildForm(Request $request, Bank $bank)
{
$form = $this->createForm(BankType::class, $bank);
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($bank);
$em->flush();
}
return $form;
}

Related

How to write the data of my entity in my form in Symfony 2

I'm working on a form in Symfony 2 and what I want is to know if there is a way to write the data of my entity in the field of my form, but I would like to do it inside of my form class.
I know that I could just pass those data to my form from my controller, but since my form knows to which entity it is mapped, I tought that it might be possible to get those info inside of my form and put it in my fields there. My form is mapped to my entity "infos". It as no join and it will always have only one entry in that table, so would just need to get the first entry (if there is at least one).
<?php
namespace AdminBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
Class ModifierInfosType extends AbstractType
{
public function buildForm(FormBuilderInterface $constructeur, array $options)
{
$constructeur
->add('travailFr', 'text', array(
'label'=>'Travail (Fr)',
//'data'=>'Mes données'
))
->add('travailEn', 'text', array(
'label'=>'Travail (En)',
//'data'=>'Mes données'
))
->add('lien', 'url', array(
'label'=>'Lien travail',
//'data'=>'Mes données'
))
->add('linkedin', 'url', array(
'label'=>'LinkedIn',
//'data'=>'Mes données'
))
->add('Modifier', 'submit');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AdminBundle\Entity\Infos',
));
}
public function getName()
{
return 'portfolio_modifier_info';
}
}
My entity:
<?php
namespace PublicBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Projet Inter
*
* #ORM\Table(name="pt_infos");
* #ORM\Entity
* #ORM\Entity(repositoryClass="PublicBundle\Entity\InfosDepot")
*/
class Infos
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
//ID du projet
protected $id;
/**
* #ORM\Column(name="inf_travail_fr", type="text",length=100)
*/
//Poste occupé
protected $travailFr;
/**
* #ORM\Column(name="inf_travail_en", type="text",length=100)
*/
//Poste occupé
protected $travailEn;
/**
* #ORM\Column(name="inf_lien", type="text",length=100)
*/
//Lien vers l'emploie
protected $lien;
/**
* #ORM\Column(name="inf_linkedin", type="text",length=100)
*/
//Lien vers la page linkedin
protected $linkedin;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set travailFr
*
* #param string $travailFr
* #return Infos
*/
public function setTravailFr($travailFr)
{
$this->travailFr = $travailFr;
return $this;
}
/**
* Get travailFr
*
* #return string
*/
public function getTravailFr()
{
return $this->travailFr;
}
/**
* Set travailEn
*
* #param string $travailEn
* #return Infos
*/
public function setTravailEn($travailEn)
{
$this->travailEn = $travailEn;
return $this;
}
/**
* Get travailEn
*
* #return string
*/
public function getTravailEn()
{
return $this->travailEn;
}
/**
* Set lien
*
* #param string $lien
* #return Infos
*/
public function setLien($lien)
{
$this->lien = $lien;
return $this;
}
/**
* Get lien
*
* #return string
*/
public function getLien()
{
return $this->lien;
}
/**
* Set linkedin
*
* #param string $linkedin
* #return Infos
*/
public function setLinkedin($linkedin)
{
$this->linkedin = $linkedin;
return $this;
}
/**
* Get linkedin
*
* #return string
*/
public function getLinkedin()
{
return $this->linkedin;
}
}
Is there a way to do that, or should I just pass the info to my controller?
I did just like DerStoffel suggested. I passed my entity to my form like this:
<?php
namespace AdminBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AdminBundle\Form\ModifierInfosType;
class InfoController extends Controller
{
public function indexAction(Request $request)
{
//Tente de trouver l'enregistrement de la table info
$infos = $this->getDoctrine()->getRepository('PublicBundle:Infos')->find(1);
//Formulaire pour modifier les infos
$form = $this->createForm(new ModifierInfosType(), $infos);
//On surveille le formulaire
$form->handleRequest($request);
return $this->render('AdminBundle::info.html.twig', array(
'form'=> $form->createView()
));
}
}

Symfony2 Collection Form type - Not saving new records

I have implemented the 'collection' form type in my app, and it works great, including removing related records that are removed from the submitted data, however in order to save any newly added link record I have to manually loop through the links entities & set the owner as the entity being edited. I'm sure it should do this automatically, so what have I missed?
You'll see a...
/*****************************************************************
* This is the bit I've had to add to persist the new entries!!! *
*****************************************************************
... where I've had to manually link the entities in the AppController file.
The files
App Entity
<?php
namespace ...\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* App
*
* #ORM\Table()
* #ORM\Entity()
*/
class App
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #var AppAppStoreData[]|ArrayCollection
*
* We use orphan removal here to remove the link data when deleting an app
* #ORM\OneToMany(targetEntity="AppAppStoreData", mappedBy="app", cascade={"all"}, orphanRemoval=TRUE)
*/
private $appAppStoreData;
// ...
/**
* #return ArrayCollection|AppAppStoreData[]
*/
public function getAppAppStoreData()
{
return $this->appAppStoreData;
}
/**
* #param ArrayCollection|AppAppStoreData[] $appAppStoreData
*
* #return $this
*/
public function setAppAppStoreData( $appAppStoreData )
{
$this->appAppStoreData = $appAppStoreData;
return $this;
}
}
AppStore Entity
<?php
namespace ...\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* AppStore
*
* #ORM\Table()
* #ORM\Entity()
*/
class AppStore
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #var AppAppStoreData[]|ArrayCollection
*
* We use orphan removal here to remove the link data when deleting an app
* #ORM\OneToMany(targetEntity="AppAppStoreData", mappedBy="appStore", cascade={"all"}, orphanRemoval=TRUE)
*/
private $appAppStoreData;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
// ...
/**
* #return AppAppStoreData[]|ArrayCollection
*/
public function getAppAppStoreData()
{
return $this->appAppStoreData;
}
/**
* #param AppAppStoreData[]|ArrayCollection $appAppStoreData
*
* #return $this
*/
public function setAppAppStoreData( $appAppStoreData )
{
$this->appAppStoreData = $appAppStoreData;
return $this;
}
}
AppAppStoreData Entity
<?php
namespace ...\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* AppAppStoreData
*
* #ORM\Table()
* #ORM\Entity()
*/
class AppAppStoreData
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var App
*
* #ORM\ManyToOne(targetEntity="App", inversedBy="appAppStoreData")
* #ORM\JoinColumn(name="app_id", referencedColumnName="id")
*/
private $app;
/**
* #var AppStore
*
* #ORM\ManyToOne(targetEntity="AppStore", inversedBy="appAppStoreData")
* #ORM\JoinColumn(name="app_store_id", referencedColumnName="id")
*/
private $appStore;
/**
* #var string
*
* #ORM\Column(name="app_store_uri", type="string", length=1083)
*/
private $appUri;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set app
*
* #param App $app
*
* #return AppAppStoreData
*/
public function setApp(App $app)
{
$this->app = $app;
return $this;
}
/**
* Get app
*
* #return App
*/
public function getApp()
{
return $this->app;
}
/**
* Set appStore
*
* #param AppStore $appStore
*
* #return AppAppStoreData
*/
public function setAppStore(AppStore $appStore)
{
$this->appStore = $appStore;
return $this;
}
/**
* Get appStore
*
* #return AppStore
*/
public function getAppStore()
{
return $this->appStore;
}
/**
* Set appStoreLink
*
* #param string $appUri
*
* #return AppAppStoreData
*/
public function setAppUri( $appUri)
{
$this->appUri = $appUri;
return $this;
}
/**
* Get appStoreLink
*
* #return string
*/
public function getAppUri()
{
return $this->appUri;
}
}
AppType Form
<?php
namespace ...\Form\Type;
use ...\Entity\App;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class AppType extends AbstractType
{
/**
* Builds the App admin edit form
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
// ...
->add( 'appAppStoreData', 'collection', [
'type' => new ,
'label' => 'App Store Links',
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'widget_add_btn' => array( 'label' => "Add App Store Details" ),
'widget_remove_btn' => array( 'label' => "Remove App Store Details" ),
'show_legend' => false, // dont show another legend of subform
'options' => array( // options for collection fields
'label_render' => false,
'widget_addon_prepend' => array(
'text' => '#',
),
'horizontal_input_wrapper_class' => "col-lg-8",
),
] );
}
/**
* Sets the Entity data class
*
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions( OptionsResolverInterface $resolver )
{
$resolver->setDefaults(
array(
'data_class' => '...\Entity\App',
)
);
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'app';
}
}
AppAppStoreDataFieldsType
<?php
namespace ...\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class AppAppStoreDataFieldsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add( 'appStore', 'entity', [
'class' => '...Bundle:AppStore',
'property' => 'name',
'label' => 'App Store'
] )
->add( 'appUri', 'url', [
'label' => 'App URI'
] );
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions( OptionsResolverInterface $resolver )
{
$resolver->setDefaults( array(
'data_class' => '...\Entity\AppAppStoreData'
) );
}
/**
* #return string
*/
public function getName()
{
return 'app_store_data_fields';
}
}
App Controller
<?php
namespace ...\Controller;
use ...\Entity\UploadableInterface;
use ...\Exception\RecordNotFoundException;
use ...\Exception\RedirectException;
use ...\Entity\App;
use ...\Entity\AppRepository;
use ...\Entity\ClientRepository;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class AppController extends ClientAssetController
{
/**
* #param Request $request
* #param null $id
*
* #return Response
*/
public function editAction( Request $request, $id = null )
{
$app = #$this->getAppRepository()->find( $id );
// ...
$form = $this->createForm( 'app', $app )
->add( 'save', 'submit', [ 'label' => 'Save' ] );
$form->handleRequest( $request );
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist( $app );
/*****************************************************************
* This is the bit I've had to add to persist the new entries!!! *
*****************************************************************
/
foreach ($app->getAppAppStoreData() as $appAppStoreDatum) {
$appAppStoreDatum->setApp( $app );
}
$em->flush();
// ... Redirect
}
// ... Render
}
// ...
}
initialize the collection of appAppStoreData:
...
public function __construct()
{
$this->appAppStoreData = new ArrayCollection();
}

How to edit embedded form with file upload in symfony2 and doctrine mongodb

I have document called aboutMe
and it has another embedded document called projects to add many projects (prototype)
The projects has project name and image for the project.
i created a formType for aboutMe document and i embedded the project form inside the aboutMe form to be able to add many projects prototype.
The problem is updating the project->image when the user didn't change the old project image.
doctrine updating the old embedded project document with a null image.
I Need to keep the old image name if the user didn't upload a new one
/**
* #MongoDB\Document
* #MongoDB\HasLifecycleCallbacks
*/
class AboutMeIndex {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var array
*
* #MongoDB\EmbedMany(targetDocument="AboutMeProjects", strategy="set")
*/
protected $projects = array();
public function __construct()
{
$this->projects = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Remove project
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project
*/
public function removeProject(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project)
{
$this->projects->removeElement($project);
}
/**
* Get projects
*
* #return Doctrine\Common\Collections\Collection $projects
*/
public function getProjects()
{
return $this->projects;
}
/**
* Add certification
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification
*/
public function addCertification(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification)
{
$this->certifications[] = $certification;
}
//=================================================================================//
public function fileGetter($file){
if(method_exists($this, 'get' . ucfirst($file))) {
return call_user_func(array($this, 'get' . ucfirst($file)));
}
else {
throw new \Exception("Couldn't Find Method name get" . ucfirst($file));
}
}
protected function getUploadRootDir($uploadDir)
{
return __DIR__.'/../../../../../../web/uploads/'.$this->getUploadDir($uploadDir);
}
protected function getUploadDir($uploadDir)
{
return $uploadDir;
}
public function uploadEmbeddedPhotos($file, $uploadDir)
{
if (null === $this->fileGetter($file)) {
return;
}
foreach ($this->fileGetter($file) as $galleryPhoto){
$pictureName = uniqid().'.'.$galleryPhoto->getImage()->guessExtension();
$galleryPhoto->getImage()->move($this->getUploadRootDir($uploadDir),$pictureName);
$this->path = $galleryPhoto->getImage()->getClientOriginalName();
$galleryPhoto->setImage($pictureName);
}
}
public function deleteImage($image, $uploadDir){
#unlink($this->getUploadRootDir($uploadDir).$image);
}
//=================================================================================//
/**
* #MongoDB\EmbeddedDocument
*
*/
class AboutMeProjects {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var string
*
* #MongoDB\String
*/
protected $projectName;
/**
* #var string
*
* #Assert\Image(
* maxSize = "20000k",
* mimeTypes = {"image/gif", "image/jpeg", "image/png"},
* mimeTypesMessage = "Please upload a valid picture"
* )
* #Assert\Regex(
* pattern="/[a-zA-Z0-9]+/",
* match=true,
* message="Special characters are not allowed"
* )
*
* #MongoDB\String
*/
protected $image;
/**
* #var string
*
* #MongoDB\String
*/
protected $desc;
/**
* Get id
*
* #return int_id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set projectName
*
* #param string $projectName
* #return self
*/
public function setProjectName($projectName)
{
$this->projectName = $projectName;
return $this;
}
/**
* Get projectName
*
* #return string $projectName
*/
public function getProjectName()
{
return $this->projectName;
}
/**
* Set image
*
* #param string $image
* #return self
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string $image
*/
public function getImage()
{
return $this->image;
}
/**
* Set desc
*
* #param string $desc
* #return self
*/
public function setDesc($desc)
{
$this->desc = $desc;
return $this;
}
/**
* Get desc
*
* #return string $desc
*/
public function getDesc()
{
return $this->desc;
}
}
class AboutMeIndexType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName')
->add('projects', 'collection', array(
'type' => new ProjectsType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeIndex'
));
}
/**
* #return string
*/
public function getName()
{
return 'AbourMe';
}
}
class ProjectsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('projectName','text',array('attr'=> array('class'=>'form-control', 'placeholder' => 'Project name') ))
->add('image','file',array('data_class' => null,'attr'=> array('class'=>'form-control col-lg-2 file-inputs') ))
->add('desc','textarea',array('attr'=> array('class'=>'form-control', 'data-provide' => 'markdown', 'placeholder' => 'Description') ))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeProjects'
));
}
/**
* #return string
*/
public function getName()
{
return 'ProjectsType';
}
}
And here's the controller that i want to be able to keep the old image
/**
* Edits an existing aboutMeIndex document.
*
* #Route("/profile/about-me/update", name="profile_about_me_update")
* #Method("PUT")
* #Template()
*/
public function updateAction(Request $request)
{
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$user = $this->getUser();
$entity = $dm->getRepository('AboutMeIndex')->findOneBy(array('user.$id' => (int)$user->getId()));
if (!$entity) {
throw $this->createNotFoundException('Unable to find entity Document.');
}
$editForm = $this->createForm(new AboutMeIndexType(), $entity);
$editForm->submit($request);
if ($editForm->isValid()) {
if($entity->getProjects()->getImage() is newImage){
$entity->uploadEmbeddedPhotos('projects', 'profile/aboutMe/');
}else{
// Keep the old Image
}
$dm->persist($entity);
$dm->flush();
}
}

Symfony 2 Exception: Call to a member function on a non object when using Form Events

I'm trying to create a form which dinamically load all "sites" related to a "project", It seems like this would be of use, so I tried it:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class EngineeringType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project','entity',array(
'class' => 'tBundle:Project',
'label' => 'Project'
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$sites = $data->getProject()->getSites();
$form->add('site', 'entity', array('choices' => $sites));
}
);
}
My problem comes when I try to access the form, I get:
FatalErrorException: Error: Call to a member function getSites() on a non-object in ... tBundle\Form\EngineeringType.php line 41
Here are my entities:
namespace tBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Engineering
*
* #ORM\Table(name="engineerings")
* #ORM\Entity
*/
class Engineering
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="tBundle\Entity\Project")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id",nullable=false)
*/
private $project;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set project
*
* #param string $project
* #return Engineering
*/
public function setProject(\tBundle\Entity\Project $project)
{
$this->project = $project;
return $this;
}
/**
* Get project
*
* #return string
*/
public function getProject()
{
return $this->project;
}
Project:
namespace tBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Project
*
* #ORM\Table(name="projects")
* #ORM\Entity
*/
class Project
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="tBundle\Entity\Site")
* #ORM\JoinTable(name="project_sites",
* joinColumns={#ORM\JoinColumn(name="site_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="project_id", referencedColumnName="id")}
* )
*/
private $sites;
public function __construct()
{
$this->sites = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Project
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get Sites
*
* #return array
*/
public function getSites()
{
return $this->sites;
}
/* Returns Project's Name */
public function __toString()
{
return $this->name;
}
What am I doing wrong?
EDIT
Controller:
/**
* Creates a form to create a Engineering entity.
*
* #param Engineering $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Engineering $entity)
{
$form = $this->createForm(new EngineeringType(), $entity, array(
'action' => $this->generateUrl('engineering_create'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Create'));
return $form;
}
/**
* Creates a form to edit a Engineering entity.
*
* #param Engineering $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Engineering $entity)
{
$form = $this->createForm(new EngineeringType(), $entity, array(
'action' => $this->generateUrl('engineering_update', array('id' => $entity->getId())),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Update'));
return $form;
}
The PRE_SET_DATA event is actually fired twice. The first time will not have any data. There used to be a blurb in the manual explaining why but I could not find it again.
So just:
function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if ($data)
{
$sites = $data->getProject()->getSites();
$form->add('site', 'entity', array('choices' => $sites));
}
}
=======================================================
Updated answer to show how to handle non-existent $project:
if ($data)
{
$project = $data->getProject();
$sites = $project ? $project->getSites() : array();
$form->add('site', 'entity', array('choices' => $sites));
}
There is an easy solution, why don't you use the "property" property on the form builder?
$builder
->add('project','entity',array(
'class' => 'tBundle:Project',
'label' => 'Project',
'property' => 'sites');
Or even you can use a query builder if this is not enough:
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
));
You can find here more description, if this is not enough:
Symfony documentation
EDIT:
So your problem that with the first solution, that it's going to be an array, so use my second option, and in the query builder, specify what will reflect your needs.
Or use it like the class is not Project but Sites.

symfony2 validation of child entity prevents editing of parent entity

I have run into this problem with a couple of my entities now so I thought to try and get a hang of what really goes on, and I turn to my best source here (will add a bounty to this question as soon as it is eligible).
My user is part of a user group. I have a validator for the userGroup entity to make sure no two userGroups have the same name.
The problem is that when I go to editing a user, and try to select that userGroup for the user, symfony2 is behaving as if I were trying to create another userGroup with that same name, when in reality all I am doing is I am trying to select that userGroup for the user.
A user entity
<?php
// src/BizTV/UserBundle/Entity/User.php
namespace BizTV\UserBundle\Entity;
use BizTV\UserBundle\Validator\Constraints as BizTVAssert;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use BizTV\BackendBundle\Entity\company as company;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser implements AdvancedUserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//TODO: Add constraint on $name * #BizTVAssert\NameExists (and finish coding this constraint)
/**
* #var object BizTV\BackendBundle\Entity\company
*
* #ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
*/
protected $company;
/**
* #var object BizTV\UserBundle\Entity\UserGroup
* #ORM\ManyToOne(targetEntity="BizTV\UserBundle\Entity\UserGroup")
* #ORM\JoinColumn(name="userGroup", referencedColumnName="id", nullable=true)
*/
protected $userGroup;
/**
* #ORM\ManyToMany(targetEntity="BizTV\ContainerManagementBundle\Entity\Container", inversedBy="users")
* #ORM\JoinTable(name="access")
*/
private $access;
/**
* #var object BizTV\ContainerManagementBundle\Entity\Container
*
* This only applies to the BizTV server user accounts or "screen display accounts". Others will have null here.
*
* #ORM\ManyToOne(targetEntity="BizTV\ContainerManagementBundle\Entity\Container")
* #ORM\JoinColumn(name="screen", referencedColumnName="id", nullable=true)
*/
protected $screen;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
protected $isServer;
public function __construct()
{
parent::__construct();
$this->access = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set company
*
* #param BizTV\BackendBundle\Entity\company $company
*/
public function setCompany(\BizTV\BackendBundle\Entity\company $company)
{
$this->company = $company;
}
/**
* Get company
*
* #return BizTV\BackendBundle\Entity\company
*/
public function getCompany()
{
return $this->company;
}
/**
* Add access
*
* #param BizTV\ContainerManagementBundle\Entity\Container $access
*/
public function addContainer(\BizTV\ContainerManagementBundle\Entity\Container $access)
{
$this->access[] = $access;
}
/**
* Get access
*
* #return Doctrine\Common\Collections\Collection
*/
public function getAccess()
{
return $this->access;
}
/**
* Set screen
*
* #param BizTV\ContainerManagementBundle\Entity\Container $screen
*/
public function setScreen(\BizTV\ContainerManagementBundle\Entity\Container $screen)
{
$this->screen = $screen;
}
/**
* Get screen
*
* #return BizTV\ContainerManagementBundle\Entity\Container
*/
public function getScreen()
{
return $this->screen;
}
/**
* Set isServer
*
* #param boolean $isServer
*/
public function setIsServer($isServer)
{
$this->isServer = $isServer;
}
/**
* Get isServer
*
* #return boolean
*/
public function getIsServer()
{
return $this->isServer;
}
/**
* Set userGroup
*
* #param BizTV\UserBundle\Entity\UserGroup $userGroup
*/
public function setUserGroup(\BizTV\UserBundle\Entity\UserGroup $userGroup = null)
{
$this->userGroup = $userGroup;
}
/**
* Get userGroup
*
* #return BizTV\UserBundle\Entity\UserGroup
*/
public function getUserGroup()
{
return $this->userGroup;
}
}
The UserGroup entity that the User is linked to:
<?php
namespace BizTV\UserBundle\Entity;
use BizTV\UserBundle\Validator\Constraints as BizTVAssert;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* BizTV\UserBundle\Entity\UserGroup
*
* #ORM\Table()
* #ORM\Entity
*/
class UserGroup
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
* #BizTVAssert\NameExists
* #ORM\Column(name="name", type="string", length=255)
* #Assert\NotBlank(message = "Du måste ange ett gruppnamn")
*/
private $name;
/**
* #var object BizTV\BackendBundle\Entity\company
*
* #ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
*/
protected $company;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set company
*
* #param BizTV\BackendBundle\Entity\company $company
*/
public function setCompany(\BizTV\BackendBundle\Entity\company $company)
{
$this->company = $company;
}
/**
* Get company
*
* #return BizTV\BackendBundle\Entity\company
*/
public function getCompany()
{
return $this->company;
}
}
The NameExistsValidator
<?php
namespace BizTV\UserBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManager as EntityManager;
class NameExistsValidator extends ConstraintValidator
{
private $container;
private $em;
public function __construct(Container $container, EntityManager $em) {
$this->container = $container;
$this->em = $em;
}
public function isValid($value, Constraint $constraint)
{
$em = $this->em;
$container = $this->container;
$company = $this->container->get('security.context')->getToken()->getUser()->getCompany();
//Fetch entities with same name
$repository = $em->getRepository('BizTVUserBundle:UserGroup');
//$repository = $this->getDoctrine()->getRepository('BizTVContainerManagementBundle:Container');
$query = $repository->createQueryBuilder('c')
->where('c.company = :company')
->setParameter('company', $company)
->orderBy('c.name', 'ASC')
->getQuery();
$groups = $query->getResult();
foreach ($groups as $g) {
if ($g->getName() == $value) {
$this->setMessage('Namnet '.$value.' är upptaget, vänligen välj ett annat', array('%string%' => $value));
return false;
}
}
return true;
}
}
User edit form
<?php
namespace BizTV\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormValidatorInterface;
use Symfony\Component\Form\FormError;
use Doctrine\ORM\EntityRepository;
class editUserType extends AbstractType
{
function __construct($company)
{
$this->company = $company;
}
public function buildForm(FormBuilder $builder, array $options)
{
$company = $this->company;
$builder
->add('locked', 'checkbox', array('label' => 'Kontot är låst, användaren kan inte logga in '))
->add('username', 'text', array('label' => 'Användarnamn '))
;
$builder
->add('userGroup', 'entity', array(
'label' => 'Användargrupp',
'empty_value' => 'Ingen grupptillhörighet',
'property' => 'name',
'class' => 'BizTV\UserBundle\Entity\UserGroup',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) use ($company) {
$qb = $er->createQueryBuilder('a');
$qb->where('a.company = :company');
$qb->setParameters( array('company' => $company) );
$qb->orderBy('a.name', 'ASC');
return $qb;
}
));
$builder
->add('email', 'email', array('label' => 'Epost '))
->add('plainPassword', 'repeated', array('type' => 'password', 'first_name' => 'Nytt lösenord ', 'second_name' => 'Upprepa lösenord ',));
$builder
->add('roles', 'choice', array(
'label' => 'Roller',
'expanded' => true,
'multiple' => true,
'choices' => array(
'ROLE_CONTENT' => 'Innehåll (Användaren kan lägga till, redigera och ta bort innehåll där du nedan beviljar åtkomst)',
'ROLE_LAYOUT' => 'Skärmlayout (Användaren kan skapa ny skärmlayout, redigera befintlig eller ta bort gällande skärmlayout där du nedan beviljar åtkomst)',
'ROLE_VIDEO' => 'Videouppladdning (Användaren har rätt att ladda upp videofiler till företagets mediabibliotek)',
'ROLE_ADMIN' => 'Administratör (Användaren är administratör med fulla rättigheter till allt precis som det konto du nu är inloggad på, var mycket restriktiv med att tilldela denna behörighet).',
),
))
;
$builder
->add('access', 'entity', array(
'label' => 'Behörigheter',
'multiple' => true, // Multiple selection allowed
'expanded' => true, // Render as checkboxes
'property' => 'select_label',
'class' => 'BizTV\ContainerManagementBundle\Entity\Container',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) use ($company) {
$qb = $er->createQueryBuilder('a');
$qb->innerJoin('a.containerType', 'ct');
$qb->where('a.containerType IN (:containers)', 'a.company = :company');
$qb->setParameters( array('containers' => array(1,2,3,4), 'company' => $company) );
$qb->orderBy('ct.id', 'ASC');
return $qb;
}
));
$builder-> addValidator(new CallbackValidator(function(FormInterface $form){
$email = $form->get('email')->getData();
if (empty( $email )) {
$form['email']->addError(new FormError("Du måste ange en epostadress för användaren"));
}
}));
$builder-> addValidator(new CallbackValidator(function(FormInterface $form){
$username = $form->get('username')->getData();
if (strpos($username,'#') !== false) {
$form['username']->addError(new FormError("Användarnamnet får inte innehålla tecknet #"));
}
}));
$builder-> addValidator(new CallbackValidator(function(FormInterface $form){
$username = $form->get('username')->getData();
if (empty($username)) {
$form['username']->addError(new FormError("Du måste ange ett namn för användaren"));
}
}));
//TODO check if username exists
}
public function getName()
{
return 'biztv_userbundle_newusertype';
}
}
Your NameExistsValidator does this:
Fail if I find any user-group with the name I'm checking.
But I think you want it to do this:
Fail if I find another user-group with the name I'm checking.
In other words: the validator needs the complete UserGroup entity (or at least its id and name) to check for a user-group with the same name but different id.
Symfony 2 already has a UniqueEntity validator, why don't you use it?
Using annotations this would look something like this:
/**
* #ORM\Entity
* #AssertUniqueEntity(fields={"name"}, message="This name already exists")
*/
class UserGroup
{
One possible and simplest solution is to define Validation Groups. For example, when you create a group, you can use the validation group named 'create' or 'groups' and when you create a user does not specify a group. Then validator will not apply to user creation process.
Validation Groups can be assigned dynamically in the form class. An example of this you can see in the documentation.