Editing a form -> Forced to modify image [Easy to answer i think] - forms

I am trying to modify my profile entity but in it i have a variable for the profile image path ($avatarPath). But when i am trying to modify my entity with a form i am forced to "upload" a new file for validated the form (the value of the path file is at null for default i think, so when i accept the form without an image the tells me that my form is not valid and it lack the image
so my goal is to set by default the image of the profile in the edit form or to not put the button upload but making the form work (and i will put the upload file in an other page)
(that's for sure an idiot error but i don't see where)
My $avatarPath
/**
* #var string
*
* #Assert\NotBlank(message="Please enter an image")
* #Assert\Image()
* #ORM\Column(name="avatar_path", type="string", length=255, nullable=true)
*/
protected $avatarPath;
My controller :
/**
* Creates a new profile entity.
*
* #Route("/edit/{id}", name="profile_edit")
*/
public function editProfileAction(Request $request, User $user, Profile $profile)
{
$loggedAs = $this->getUser();
$form = $this->createForm(ProfileType::class, $profile);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/**
* #var UploadedFile $file
*/
$file = $form->get('avatar_path')->getData();
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move($this->getParameter('image_directory'), $fileName);
$profile->setAvatarPath($fileName);
if ($profile->getAvatarPath() == NULL)
$profile->setAvatarPath('NULL');
$em = $this->getDoctrine()->getManager();
$em->persist($profile);
$em->flush();
$user->setIdProfile($profile);
$em2 = $this->getDoctrine()->getManager();
$em2->persist($user);
$em2->flush();
return $this->redirectToRoute('user_list');
}
return $this->render('admin/user/new_profile.html.twig', array(
'profile' => $profile,
'form' => $form->createView(),
'loggedAs' => $loggedAs,
));
}
My form :
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('resume')
->add('is_male')
->add('birth', BirthdayType::class, array(
'years' => range(1930,2018)
))
->add('email', EmailType::class, array(
'label' => 'form.email'
))
->add('phone')
->add('language')
->add('travel')
->add('pets')
->add('avatar_path', FileType::class, array(
'data_class' => null
))
->add('avatar_path', FileType::class, array(
'data_class' => null,
'required' => false
));
}
Thx for anyone who will try to help :p

You could try to set the property mapped => false to the avatar_path field in your Form.
Doing this will cause symfony to do two things. The first is to ignore the avatar_path field when validating the form which happens in your controller when form->isValid() is called. Secondly symfony will not assign any value to the field which happens when you call $form->handleRequest($request).
If you choose to use the mapped => false property you will need to manually set the avatar_path on the Profile entity.
Here is the documentation from symfony http://symfony.com/doc/3.4/reference/forms/types/file.html (these docs are for symfony 3.4)
Hope this helps.
Edit: I assume the form is failing the validation checks because of the two #Assert Statements in your Entity. Looking at your code I think you can safely remove those since you are already allowing the property $avatarPath to be nullable anyway.

I put that :
->add('avatar_path', FileType::class, array(
'data_class' => null,
'mapped' => false,
'required' => false
));
but nothing appens always the same :
That just refresh the page so the form is not valid ...

Related

Create a simple form with Symfony

I'm getting crazy with a veeery simple thing! I just want to make a small form with one text input field and a validation button and get the value entered by the user. And it doesn't work. Absolutely crazy... I'm sure it's a very small thing, but I cannot solve it!
Here is my code:
<?php
namespace PublicBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* Description of AccountRecoveryController
*
*/
class AccountRecoveryController extends Controller {
/**
*
* #Route("/account_recovery", name="account_recovery")
*/
function accountRecoveryAction(Request $request){
$form = $this->createFormBuilder()
->add('username', TextType::class, array(
'label' => 'Adresse e-mail',
'attr' => array('placeholder' => 'E-MAIL'),
'mapped' => false
))
->add('submit', SubmitType::class, array(
'label' => 'Submit'
))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted()){
echo 'ok';
$eMail = $form->get('username')->getData();
echo $eMail;
}
return $this->render('accountRecoveryRequest.html.twig', array(
'form' => $form->createView(),
));
}
}
The form is rendered and works. The "ok" is displayed after clicking on "Submit". Buuuut $eMail is always empty... Why??
Any idea?
Thanks in advance.
You should also check if the form is valid in your if
if ($form->isSubmitted() && $form->isValid()){
....
If it is not valid, you can check for errors with
$form->getErrors(true)
The profiler should also give you enough pointers as to why you have no data, for example invalid values etc.
And you can always use dump() to check what your values are at any point in your code.

How to make a Symfony GET form redirect to route with parameter?

I want to create a form for searching a profile by username which redirect then to the profile page of the user. Btw, I use Symfony 3.2.
I reckon the natural way for doing this would be a GET action form. It would even allow a customer to change the url directly with the good username to see its profile.
Here is the code of my controller :
ProfileController.php
//...
/** #Route("/profil/search", name="profil_search") */
public function searchAction() {
$builder = $this->createFormBuilder();
$builder
->setAction($this->generateUrl('profil_show'))
->setMethod('GET')
->add('username', SearchType::class, array('label' => 'Username : '))
->add('submit', SubmitType::class, array('label' => 'Search'));
$form = $builder->getForm();
return $this->render('profils/profil_search.html.twig', [
'form' => $form->createView(),
]);
}
/** #Route("/profil/show/{username}", name="profil_show") */
public function showAction($username) {
$repository = $this->getDoctrine()->getRepository('AppBundle:User');
$searchedUser = $repository->findOneByUsername($username);
return $this->render('profils/profil_show.html.twig', [
'searchedUser' => $searchedUser,
]);
}
//...
This code will lead to the following error message :
Some mandatory parameters are missing ("username") to generate a URL for
route "profil_show".
I read the documentation thoroughly but couldn't guess, how can I pass the username variable to the profil_show route as a parameter ?
If my way of doing is not the good one, thanks for telling me in comments but I'd still like to know how to use GET forms.
EDIT :
Thanks to #MEmerson answer, I get it now. So for future noobs like me, here is how I did it :
/** #Route("/profil/search", name="profil_search") */
public function searchAction(Request $request) {
$data = array();
$builder = $this->createFormBuilder($data);
$builder
//->setAction($this->generateUrl('profil_show'))
//->setMethod('GET')
->add('username', SearchType::class, array('label' => 'Username : '))
->add('submit', SubmitType::class, array('label' => 'Search'));
$form = $builder->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
return $this->redirectToRoute('profil_show', array('username' => $data["username"]));
}
return $this->render('profils/profil_search.html.twig', [
'method' => __METHOD__,
'form' => $form->createView(),
'message' => $message,
]);
}
If you take a look at the error message it says that the problem is where you are trying to generate the URL for the path 'profil_show'.
Your controller annotations require that the URL be populated with a user name
/** #Route("/profil/show/{username}", name="profil_show") */
this means that Symfony is expecting http://yoursite.com/profil/show/username for the route. But if you want to pass it as a GET form posting it really should be expecting http://yoursite.com/profil/show?username
you can add a second route or change your existing route to be
/** #Route("/profil/show", name="profil_show_search") */
that should solve your problem.

Symfony2 form setting, unsetting associations

I have Company and Number entity which are related
/**
* #var Comapany
*
* #ORM\ManyToOne(targetEntity="Company", inversedBy="numbers", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=true, onDelete="RESTRICT")
* #Assert\NotBlank(groups={"client"})
* #Assert\Valid()
*/
private $company;
/**
* #var Number[]
* #ORM\OneToMany(targetEntity="Number", mappedBy="company", fetch="EXTRA_LAZY", cascade={"persist", "remove"})
* #Assert\Count(min="1")
*/
private $numbers;
I have created a form for creating and updating Company entity. This form should allow to set Number entities to it as well as unset them. This is how it looks rendered
And this is how it looks in code:
$builder
->add('name', 'text', [
'required' => false
])
->add('numbers', 'entity', [
'class' => 'AppBundle:Number',
'property' => 'number',
'placeholder' => '',
'required' => false,
'multiple' => true,
'query_builder' => function (EntityRepository $er) use ($builder) {
if ($builder->getData() && $id = $builder->getData()->getId()) {
return $er->createQueryBuilder('n')
->where('n.company is NULL')
->orWhere('n.company = :id')
->setParameter('id', $id);
}
return $er->createQueryBuilder('n')
->where('n.company is NULL');
}
]);
The problem is when creating new Company record, the form assigns Number entities, but the Number entities have property "company" which doesn't get assigned and so no relation is made. I have worked around this with form events:
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
foreach ($event->getData()->getNumbers() as $number) {
$number->setCompany($event->getData());
}
});
Which works for creating record, however when updating I have another issue, since I remove Number associations I have no access to them and thus can't update them in database. I could again select all Number entities assigned to form, and then filter out which were assigned to company and which were not and then manually update them, but this feels dirty and I would like to work it out in a clean way.
Finally found solution, as is turns out it's quite well documented:
http://symfony.com/doc/current/cookbook/form/form_collections.html
The changes I Had to make was:
1.Add by_reference property to entity form field: More information on that with an example: http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype
Basically what I figured that without this options symfony2 form uses own means of adding associations, and with this option set it calls methods "addNumber" and "removeNumber" inside Entity in which I had to manually add inverse side "number" association which goes to 2nd change I had to make.
$builder
->add('name', 'text', [
'required' => false
])
->add('numbers', 'entity', [
'class' => 'AppBundle:Number',
'property' => 'number',
'placeholder' => '',
'required' => false,
'multiple' => true,
'by_reference' => false, //
'query_builder' => function (EntityRepository $er) use ($builder) {
if ($builder->getData() && $id = $builder->getData()->getId()) {
return $er->createQueryBuilder('n')
->where('n.company is NULL')
->orWhere('n.company = :id')
->setParameter('id', $id);
}
return $er->createQueryBuilder('n')
->where('n.company is NULL');
}
]);
2.I had explicitly set Inverse side association to owning side by calling method setComapany($this) from owning (Company Entity) side.
/**
* Add numbers
*
* #param \AppBundle\Entity\Number $numbers
* #return Company
*/
public function addNumber(\AppBundle\Entity\Number $numbers)
{
$numbers->setCompany($this); //!Important manually set association
$this->numbers[] = $numbers;
return $this;
}
These 2 changes are enough to make form automatically add associations. However with removing associations there's a little bit more.
3.Change I had to make to correctly unset associations was inside controller action itself: I had to save currently set associations inside new ArrayCollection variable, and after form validation manually go through each item in that collection checking if it exists after form was validated. Important note:
I had also manually unset inverse side association to owning side by calling:
"$number->setCompany(null);"
public function editAction(Request $request, Company $company)
{
$originalNumbers = new ArrayCollection();
foreach ($company->getNumbers() as $number) {
$originalNumbers->add($number);
}
$form = $this->createForm(new CompanyType(), $company);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($originalNumbers as $number) {
if (false === $company->getNumbers()->contains($number)) {
$company->getNumbers()->removeElement($number);
$number->setCompany(null); //!Important manually unset association
}
}
$em->persist($company);
$em->flush();
return $this->redirectToRoute('companies');
}
return $this->render('AppBundle:Company:form.html.twig', [
'form' => $form->createView()
]);
}
All of these steps are required to make this kind of logic function properly, luckily I was able to really good documentation for that.
PS. Note that I call
$em->persist($company);
$em->flush();
without persisting each "Number" Entity iterated inside loop which you can be seen in given Symfony2 documentation example, and which in this case would look like this:
$company->getNumbers()->removeElement($number);
$number->setCompany(null);
$em->persist($number);
This is because I setup Cascading Relations options inside my Entity class
/**
* #var Number[]
* #ORM\OneToMany(targetEntity="Number", mappedBy="company", fetch="EXTRA_LAZY", cascade={"persist", "remove"})
* #Assert\Count(min="1")
*/
private $numbers;
My advise for anyone struggling with this is to read whole http://symfony.com/doc/current/cookbook/form/form_collections.html thoroughly especially sections marked by special signs ✎✚❗☀💡

How to set as a default value in a Symfony 2 form field the authentified username from the FOSUserBundle

i find this snippet useful indeed to put a default value in my form while creating it
$builder
->add('myfield', 'text', array(
'label' => 'Field',
'data' => 'Default value'))
;
what if i want to replace 'default value' with an authentified person from the FOSUser bundle? ( that return true to is_granted("IS_AUTHENTICATED_REMEMBERED"))
i can retrieve that name on a twig file with
{{ app.user.username }}
i have also done it in a controller method with
$username=$this->container->get('security.context')->getToken()->getUser()->getUsername()
but i can't manage to make this working in my form!
i am not sure i understand that container thing well ...neither how to transfer variables betweenn classes and controller...
something around this maybe??
->add('myfield', 'text', array(
'label' => 'Field',
'data' => FOS\UserBundle\Model::$this->getUsername()))
You can passe variable from your controller to your form :
in your controller :
$username=$this->container->get('security.context')->getToken()->getUser()->getUsername()
$form = $this->createForm(new MyFormType($username), $entity);
in your form :
protected $username;
public function __construct (array $username = null)
{
$this->username = $username ;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('myfield', 'text', array(
'label' => 'Field',
'data' => $this->username))
}
Another way to set default values into a form is to set them on the underlying data object for the form, as in this example from the Symfony documentation on building a form:
public function newAction()
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
In this example, the form's underlying data object is a Task and the values set on the task are the default values to be displayed in the form. The task object is not persistent. This approach works just as well with a form class and assuming the underlying object for your form is a User would look something like this:
$username = $this->container->get('security.context')->getToken()->getUser()->getUsername();
$user = new User();
$user->setUsername($username);
// could set any other default values on user here
$form = $this->createForm(new MyFormClass(), $user);
The disadvantage of this approach is if the form is used in many places requiring the same defaults the code would be repeated. This is not a situation I've come across so far - my User forms are re-used for create/edit but edit doesn't require the defaults.

Expected argument of type "Boolean", "string" given on Symfony2

I'm currently trying to create a form with fields filled with data from my database by default.
I use:
$infos = $this->getDoctrine()
->getRepository('TestMyBundle:My')
->find($id);
$form = $this->createForm(new TestType(), $infos);`
But i always get this error message: Expected argument of type "Boolean", "string" given on Symfony2. the second parameter $infos is probably the reason of this message but it's the way to add some defaults values in a form field with Symfony2.
Can anyone help me please ?
Here is the code of my TestType.php class:
class TestType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
$builder->add('description');
$builder->add('access', 'checkbox', array(
'label' => 'private access: ',
'required' => false,));
$builder->add('visibility', 'checkbox', array(
'label' => 'private group: ',
'required' => false,));
$builder->add('invitation', 'checkbox', array(
'label' => 'ask: ',
'required' => false));
$builder->add('wall', 'checkbox', array(
'label' => 'wall: ',
'required' => false,));
}
I was getting the same error after I reloaded my form with a checked value that was already persisted.
/**
* #var integer
*
* #ORM\Column(name="is_recibido", type="integer", nullable=true)
*/
private $isRecibido=null;
Then I changed it to
/**
* #var integer
*
* #ORM\Column(name="is_recibido", type="boolean", nullable=true)
*/
private $isRecibido=null;
The field in database is integer but doctrine understand and convert it to boolean to the form and an Integer when It goes to the database.
Now everything started to work smooth and nice