I'm using Fosuser and Fosrest for a webservice.
I created GetUser to get List user and the registration to register an user and it work perfectly !
But, now I try to make the update user but i got a problem !
I copy the function from Fosuser controller
In My controller :
public function updatePostAction(Request $request,$id)
{
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
/** #var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.profile.form.factory');
$form = $formFactory->createForm();
$form->setData($user);
$form->handleRequest($request);
if ($form->isValid()) {
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_profile_show');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
return $this->render('FOSUserBundle:Profile:edit.html.twig', array(
'form' => $form->createView()
));
}
it doesn't work ! anyone can help me ?
When i try to post :
{
"fos_user_profile_form": {
"username":"jacky"
}
}
it returns : https://gist.github.com/gaticho/34ae4fff5492fdecd48f
The database doesn't change :-(
My User entity :
<?php
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
private $dateCreated;
/**
* #var DateTime
*
* #ORM\Column(name="date_modified", type="datetime", nullable=true)
*/
private $dateModified;
/**
* #var DateTime
*
* #ORM\Column(name="date_deleted", type="datetime", nullable=true)
*/
private $dateDeleted;
public function __construct()
{
parent::__construct();
}
}`
Thanks !
Related
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;
}
In AppBundle\Etity\Image I have:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="images")
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\NotBlank
*/
private $path;
/**
* #Assert\Image(maxSize="10M", mimeTypes="image/jpeg", minWidth = 600, minHeight = 400)
* #Assert\NotBlank
*/
private $file;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $alt;
private $temp;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getPath()
{
return $this->path;
}
/**
* #param mixed $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* #return mixed
*/
public function getAlt()
{
return $this->alt;
}
/**
* #param mixed $alt
*/
public function setAlt($alt)
{
$this->alt = $alt;
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir() . '/' . $this->path;
}
public function getUploadRootDir()
{
return __DIR__ . '/../../../../web/' . $this->getUploadDir();
}
public function getUploadDir()
{
return 'images/full';
}
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
// check if we have an old image path
if (isset($this->path)) {
// store the old name to delete after the update
$this->temp = $this->path;
$this->path = null;
} else {
$this->path = 'initial';
}
}
/**
* #return mixed
*/
public function getFile()
{
return $this->file;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->path = $filename.'.'.$this->getFile()->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->getFile()) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->getFile()->move($this->getUploadRootDir(), $this->path);
// check if we have an old image
if (isset($this->temp)) {
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->temp);
// clear the temp image path
$this->temp = null;
}
$this->file = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
$file = $this->getAbsolutePath();
if ($file) {
unlink($file);
}
}
}
Which is used in AppBundle\Entity\Post.php like this:
/**
* #ORM\ManyToOne(targetEntity="Image", cascade="all")
* #ORM\JoinColumn(name="image_id", referencedColumnName="id")
*/
private $teaserImage;
In AppBundle\Form\Type\PostType.php I have this:
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Post;
/**
* Defines the form used to create and manipulate blog posts.
*/
class PostType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', null, array('label' => 'Title'))
->add('summary', null, array('label' => 'Summary'))
->add('teaserImage', 'AppBundle\Form\Type\ImageType', array('label' => 'Image'))
->add('content', null, array(
'attr' => array('rows' => 20),
'label' => 'Content',
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Post',
));
}
}
For some reason the images are not uploaded to the specified directory (or anywhere else) and I am not sure what I did wrong. I would be grateful for any insights.
Thank you.
The issue is related to
return __DIR__ . '/../../../../web/' . $this->getUploadDir();
In this case I changed the previous line to:
return __DIR__ . '/../../../web/' . $this->getUploadDir();
That is because my entity is located in src/AppBundle/Entity and to go to root directory it needs to hop 3 directories back.
Also it is a bad idea to hard-code paths in entities. I modified my example accordingly.
I'm still learning on how to setup Swiftmailer as a service, I believe I have a working solution but need some help on how to call this in the controller.
How do I call this service in my controller? (service code, original code before service and service.yml below)
Edit:
I am trying to call it like so:
$emailManager = $this->container->get('email_manager');
$content = $emailManager->sendMail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText);
But am getting a undefined variable error:
Notice: Undefined variable: subject in /.../DefaultController.php line 58
EmailManager service
namespace Acme\EmailBundle\Service;
use Symfony\Component\HttpFoundation\RequestStack;
class EmailManager
{
private $request;
private $mailer;
public function __construct(RequestStack $requestStack, \Swift_Mailer $mailer)
{
$this->request = $requestStack->getCurrentRequest();
$this->mailer = $mailer;
}
public function sendMail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText)
{
/* #var $mailer \Swift_Mailer */
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject);
$message->setBody($bodyHtml, 'text/html');
$message->addPart($bodyText, 'text/plain', 'UTF8');
$message->addTo($recipientEmail, $recipientName);
$message->setFrom( array('example#gmail.com' => 'Chance') );
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
}
Original controller code for sending emails prior to putting it in as a service
/**
* #Route("/", name="contact")
* #Template("AcmeEmailBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject($form->get('subject')->getData())
->setFrom($form->get('email')->getData())
->setTo('example#gmail.com')
->setBody(
$this->renderView(
'AcmeEmailBundle:Default:index.html.twig',
array(
'ip' => $request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
)
);
$this->get('mailer')->send($message);
$request->getSession()->getFlashBag()->add('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}
return array(
'form' => $form->createView()
);
}
services.yml
services:
email_manager:
class: Acme\EmailBundle\Service\EmailManager
arguments: [#request_stack, #mailer]
scope: request
When your controller extends Controller like so
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
//...
/**
* DemoController
*/
class DemoController extends Controller
{
// ...
}
You can access the services like this:
$emailManager = $this->container->get('email_manager');
You can then send your email like this:
$emailManager->sendEmail($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText);
Complete Overview
1 Create an Email Manager that will compose your emails and send them
<?php
namespace Acme\EmailBundle\Manager;
//...
/**
* Composes and Sends emails
*/
class EmailManager
{
/**
* The mailer
*
* #var \Swift_Mailer
*/
protected $mailer;
/**
* The email address the mailer will send the emails from
*
* #var String
*/
protected $emailFrom;
/**
* #param Request $mailer;
*/
public function __construct(\Swift_Mailer $mailer, $emailFrom)
{
$this->mailer = $mailer;
$this->emailFrom = $emailFrom;
}
/**
* Compose email
*
* #param String $subject
* #param String $recipientEmail
* #param String $bodyHtml
* #return \Swift_Message
*/
public function composeEmail($subject, $recipientEmail, $bodyHtml)
{
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject)
->setBody($bodyHtml, 'text/html')
->setTo($recipientEmail)
->setFrom($this->emailFrom);
return $message;
}
/**
* Send email
*
* #param \Swift_Message $message;
*/
public function sendEmail(\Swift_Message $message)
{
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
}
3 Declare it as a service
parameters:
acme_email.email_from: example#gmail.com
services:
email_manager:
class: Acme\EmailBundle\Service\EmailManager
arguments: [#mailer,%acme_email.email_from%]
]
4 Create a Form Handler that will handle your contact forms
<?php
namespace Acme\ContactBundle\Form\Handler;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Acme\EmailBundle\Manager\EmailManager;
/**
* Handles Contact forms
*/
class ContactFormHandler
{
/**
* The request
*
* #var Symfony\Component\HttpFoundation\Request;
*/
protected $request;
/**
* The Template Engine
*/
protected $templating;
/**
* The email manager
*/
protected $emailManager;
/**
* #param Request $request;
* #param TwigEngine $templating
* #param EmailManager $emailManager
*/
public function __construct(Request $request, TwigEngine $templating, EmailManager $emailManager)
{
$this->request = $request;
$this->templating =$templating;
$this->emailManager = $emailManager;
}
/**
* Processes the form with the request
*
* #param Form $form
* #return Email|false
*/
public function process(Form $form)
{
if ('POST' !== $this->request->getMethod()) {
return false;
}
$form->bind($this->request);
if ($form->isValid()) {
return $this->processValidForm($form);
}
return false;
}
/**
* Processes the valid form, sends the email
*
* #param Form
* #return EmailInterface The email sent
*/
public function processValidForm(Form $form)
{
/** #var EmailInterface */
$email = $this->composeEmail($form);
/** Send Email */
$this->emailManager->sendEmail($email);
return $email;
}
/**
* Composes the email from the form
*
* #param Form $form
* #return \Swift_Message
*/
public function composeEmail(Form $form)
{
$subject = $form->get('subject')->getData();
$recipientEmail = $form->get('email')->getData();
$bodyHTML = $this->templating->renderView(
'AcmeEmailBundle:Default:index.html.twig',
array(
'ip' => $this->request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
);
/** #var \Swift_Message */
return $this->emailManager->composeEmail($subject, $recipientEmail, $bodyHTML);
}
}
3 Declare it as a service:
services:
acme_contact.contact_form_handler:
class: Acme\ContactBundle\FormHandler\ContactFormHandler
arguments: [#request, #templating, #email_manager]
scope: request
4 Which gives you sthg short and sweet in your controller
/**
* #Route("/", name="contact")
* #Template("AcmeContactBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
/** BTW, you could create a service to create the form too... */
$form = $this->createForm(new ContactType());
$formHandler = $this->container->get('acme_contact.contact_form_handler');
if ($email = $formHandler->process($form)) {
$this->setFlash('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}
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.
Project with symfony 2 and mongoDB.
I'm following this tutorial: http://symfony.com/doc/current/cookbook/form/form_collections.html
But once I save the form I get this error:
Cannot create a DBRef, the document is not an object
Line of crash:
https://github.com/doctrine/mongodb-odm/blob/master/lib/Doctrine/ODM/MongoDB/DocumentManager.php#L691
Form code:
namespace Fonts\FontsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class FamilyType extends AbstractType
{
public function __construct($dm)
{
$this->dm = $dm;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', array('max_length' => 50, 'error_bubbling' => true));
$builder->add('fonts', 'collection', array(
'type' => new FontType($this->dm),
'allow_add' => true,
'by_reference' => false,
));
}
public function getName()
{
return 'Family';
}
}
Controller code:
namespace Fonts\FontsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Fonts\FontsBundle\Document\Family;
use Fonts\FontsBundle\Document\Font;
use Fonts\FontsBundle\Form\Type\FamilyType;
class FamilyBackendController extends BaseController
{
public function newAction()
{
try {
$family = new Family();
$form = $this->createForm(new FamilyType($this->getMongoService()), $family);
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$this->persist($family);
$this->get('session')->setFlash('notice', 'Item successfully created.');
return ($request->request->get('save') === 'Save') ?
new RedirectResponse($this->generateUrl('backend_familys_list')) :
new RedirectResponse($this->generateUrl('backend_familys_new'));
}
}
return $this->render('FontsBundle:Backend:newFamily.html.twig', array(
'form' => $form->createView(),
));
} catch(\Exception $e) {
return new Response($e->getMessage());
}
}
}
Document:
<?php
namespace Fonts\FontsBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Fonts\FontsBundle\Service\SlugService;
/**
* #MongoDB\Document(repositoryClass="Fonts\FontsBundle\Repository\FamilyRepository")
*/
class Family
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
* #MongoDB\UniqueIndex(safe=true)
* #Assert\NotBlank(message="FamilyName value should not be blank.")
* #Assert\MinLength(limit=3,message="FamilyName must have at least {{ limit }} characters.")
* #Assert\MaxLength(limit=50,message="FamilyName must have maximum {{ limit }} characters.")
*/
protected $name;
/**
* #MongoDB\ReferenceMany(targetDocument="Font", simple=true)
* #Assert\NotBlank(message="Fonts should not be blank.")
*/
protected $fonts;
/**
* #MongoDB\String
* #MongoDB\UniqueIndex(safe=true)
*/
protected $slug;
/**
* #MongoDB\Int
*/
protected $createdAt;
public function __construct()
{
$this->fonts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Family
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string $name
*/
public function getName()
{
return $this->name;
}
/**
* Add fonts
*
* #param Fonts\FontsBundle\Document\Font $fonts
*/
public function addFonts(\Fonts\FontsBundle\Document\Font $fonts)
{
$this->fonts[] = $fonts;
}
/**
* Set fonts
*
* #param Doctrine\Common\Collections\Collection $fonts
*/
public function setFonts($fonts)
{
$this->fonts = $fonts;
}
/**
* Get fonts
*
* #return Doctrine\Common\Collections\Collection $fonts
*/
public function getFonts()
{
return $this->fonts;
}
/**
* Set slug
*
* #param string $slug
* #return Family
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* #return string $slug
*/
public function getSlug()
{
return $this->slug;
}
/**
* Set createdAt
*
* #param int $createdAt
* #return Family
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return int $createdAt
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* #MongoDB\PrePersist
*/
public function prePersist()
{
$this->setCreatedAt(time());
$slugService = new SlugService();
$this->setSlug($slugService->slug($this->getName()));
}
/**
* #MongoDB\PreUpdate
*/
public function preUpdate()
{
$slugService = new SlugService();
$this->setSlug($slugService->slug($this->getName()));
}
}
The form crash when I try to persist the object in the controller action_
$this->persist($family);
I tried lot of options but no one with good results. If you have some idea, please reply.