Symfony 4.x ROLE_USER error when rendering a form with $form->createView() - symfony-4.2

I'm creating a blog with Symfony 4 and generate forms with :
php bin/console make:form
when I try to render it like this :
/**
* Require ROLE_USER for only this controller method.
* #Route("/create", name="post.create")
* #return Response
*/
public function create(): Response
{
$post = new Post();
$form = $this->createForm(CreatePostType::class, $post);
return new Response($this->twig->render('pages/create.html.twig'), [
'form' => $form->createView()
]);
}
I get this error
Could not convert database value "'ROLE_USER'" to Doctrine Type json
for this line
'form' => $form->createView()
Here is my getRoles :
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
and my security.yaml
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
in_memory: { memory: ~ }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
I'm searching for 3 days and restarted my project in a 2nd directory without the security management, I don't understand were it's from and how to solve it
thanks

I guess the roles property is not stored as json-type in your database. Can you check the type of the roles field in the database?
As a sidenote, you can rewrite the response to:
return $this->render('pages/create.html.twig', [
'form' => $form->createView(),
]);

Related

how can i edit services.yaml after overriding FOUserBundle

I integrated FOSUserBundle to my project in Symfony 4, everything is ok.
My goal : add 2 attributes in user.php (name and firstname).
My steps:
i added these 2 attributes in User.php (src Entity User.php) -- ok
i move to my terminal to genrate the migration -- ok
i created new folder Form (src Form) and new file inside RegistrationFormTYpe.php (see background)
i edited services.yaml (see background)
i edited fos_user.yaml in (config fos_user.yaml)..maybe it's the wrong file..cause config.yaml doesn't exist in symfony 3 (see background)
see console error in background...
Does anyone have a solution ? must i give up to integrate fosuser in symfony 4 ?
//src\Form;
namespace src\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseRegistrationFormType;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name',TextType::class,array(
'label' => 'Nom',
'required' => TRUE,
'attr' =>array(
'class='=>'form-group form-control'
)
))
->add('firstname',TextType::class,array(
'label' => 'Prénom',
'required' => TRUE,
'attr' =>array(
'class='=>'form-group form-control'
)
))
;
}
public function getParent(){
return BaseRegistrationFormType::class;
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
}
// config\services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'fr'
services:
app.form.registration:
class: AppBundle\Form\RegistrationFormType
tags:
- { name: form.type }
- { firstname: form.type }
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,EventListener,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
// config\fos_user.yaml
fos_user:
db_driver: orm
user_class: App\Entity\User
firewall_name: main
service:
mailer: fos_user.mailer.noop
from_email:
address: "sebvrg#gmail.com"
sender_name: "sebvrg#gmail.com"
registration:
form:
type: AppBundle\Form\RegistrationFormType
// output in console :
In FileLoader.php line 166:
A "tags" entry is missing a "name" key for service "app.form.registration" in C:\Users\sebvr\Desktop\Projets\SELT\selt\config/services.yaml in C:\Users\sebvr\Deskt
op\Projets\SELT\selt\config/services.yaml (which is loaded in resource "C:\Users\sebvr\Desktop\Projets\SELT\selt\config/services.yaml").
In YamlFileLoader.php line 489:
A "tags" entry is missing a "name" key for service "app.form.registration" in C:\Users\sebvr\Desktop\Projets\SELT\selt\config/services.yaml.
Did you extend your User entity aswell?
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* #ORM\Entity
* #ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $firstname;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getFirstname()
{
return $this->firstname;
}
public function setFirstname($firstname)
{
$this->firstname = $firstname;
}
}
And did you register the form in app/config/config.yml?
fos_user:
registration:
form:
type: AppBundle\Form\RegistrationFormType
Also your tag says Symfony 4.2 but you are using the structure of symfony 3 (AppBundle) instead of (App)

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

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 ...

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.

FOSRestBundle update only one field at a time using PUT

While ago I have asked same question Symfony PUT does not contain all entity properties mapped and I've got some #hack solution to solve the problem but when the form is more complex, contains choices (arrays) or mapped entities (objects) the solution is not applicable anymore. So I came up with very dirty ideas to make it work such as
if(is_object($event->getForm()->getData())) $event->setData($event->getForm()->getData()->getId());
if(is_array($event->getData()) && empty($event->getData()))
{
$event->setData([$event->getForm()->getData()]);
}
if($event->getForm()->getData() instanceof \DateTime) $event->setData($event->getForm()->getData()->format('Y-m-d H:i:s'));
if(!is_array($event->getData()) && (is_string($event->getForm()->getData()) || is_integer($event->getForm()->getData())))
{
$event->setData($event->getForm()->getData());
}
but it's not even working perfect. So I must ask one more time if there's a better solution to updatejust one value at the time of sending json response, because if I send {"user":{"firstName":"John"}} all other fields belonging to the User form are empty and I cannot send entire resource. I cannot find any solution to this problem.
And here's the Controller
/**
* This endpoint updates an existing Client entity.
*
* #ApiDoc(
* resource=true,
* description="Updates an existing Client entity",
* )
* #ParamConverter("user", class="Software:User", options={"mapping": {"user": "guid"}})
*/
public function putAction(Request $request, $user)
{
$form = $this->createForm(new UserType(['userType' => 'client', 'manager' => $this->getDoctrine()->getManager()]), $user, ['method' => 'PUT']);
$form->handleRequest($request);
if($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
$manager->flush();
return $this->view([
'user' => $user
]);
}
return $this->view($form);
}
I am going to answer my own question.
The answer is to use PATCH method instead of PUT.
Here's the modified code:
/**
* This endpoint updates an existing Client entity.
*
* #ApiDoc(
* resource=true,
* description="Updates an existing Client entity",
* )
* #ParamConverter("user", class="Software:User", options={"mapping": {"user": "guid"}})
*/
public function patchAction(Request $request, $user)
{
$form = $this->createForm(new UserType(['userType' => 'client', 'manager' => $this->getDoctrine()->getManager()]), $user, ['method' => 'PATCH']);
$form->handleRequest($request);
if($form->isValid())
{
$manager = $this->getDoctrine()->getManager();
$manager->flush();
return $this->view([
'user' => $user
]);
}
return $this->view($form);
}
Please find references:
Symfony2 REST API - Partial update
http://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-way/
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Read carefully PATCH vs. PUT chapters.

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 ✎✚❗☀💡