I'm trying to populate $errors['field_name'] = 'Error message'; in my controller so that I can pass the variable to twig for further processing. How can I loop thru the errors and create my own array variable?
I've checked and applied these but didn't get the exact answer, or maybe I missed.
Accessing and Debugging Symfony Form Errors
Symfony2 : How to get form validation errors after binding the
request to the form (Works but is it reliable?)
Symfony2 – Getting All Errors From a Form in a Controller (Overkill)
FORM TYPE
->add('name', 'text', array('label' => 'Name', 'error_bubbling' => true))
->add('origin', 'text', array('label' => 'Origin', 'error_bubbling' => true))
TWIG
{% if errors is defined %}
<ul>
{% for field, message in errors %}
<li>{{ field ~ ':' ~ message }}</li>
{% endfor %}
</ul>
{% endif %}
CONTROLLER
public function submitAction(Request $request)
{
$form = $this->createForm(new BrandsType(), new Brands());
$form->handleRequest($request);
if ($form->isValid() !== true)
{
$errors['field_name'] = 'Error message';
return $this->render('CarBrandBundle:brands.html.twig',
array('errors' => $errors, 'form' => $form->createView()));
}
}
Try a method like this:
public function getErrorMessages(FormInterface $form)
{
$errors = array();
//this part get global form errors (like csrf token error)
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
//this part get errors for form fields
/** #var Form $child */
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$options = $child->getConfig()->getOptions();
//there can be more than one field error, that's why implode is here
$errors[$options['label'] ? $options['label'] : ucwords($child->getName())] = implode('; ', $this->getErrorMessages($child));
}
}
return $errors;
}
This method will return what you want, which is associative array with form errors.
The usage of it would be in your case (controller):
if ($form->isValid() !== true)
{
$errors = $this->getErrorMessages($form);
return $this->render('CarBrandBundle:brands.html.twig',
array('errors' => $errors, 'form' => $form->createView()));
}
This usage assumes you have getErrorMessages method in your controller, however a better idea would be creating some class with this method and registering it as a service (you might want to reuse it in other controllers)
Related
In my Symfony project I have two views. Details view of specific entity and a button to lead me to a new view with with some other data by date param.
The problem with the code I have is of generating 'Return to previous' page button from second method back to first..
Code:
/**
* #Route("/details/{id}", name="first_method")
*/
public function firstMethod(Detail $id)
{
$workspace = $this->entityManager
->getRepository(Detail::class)
->find($id);
$build['detail'] = $detail;
$form = $this->createFormBuilder()
->add('date', DateTimeType::class, [
'data' => new \DateTime(),
'widget' => 'single_text'
])
->add(
'save',
SubmitType::class,
[
'attr' => ['class' => 'btn-submit']
]
)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$date = $data['date']
return $this->redirectToRoute('second_method', [
'date' => $date
]);
}
return $this->render('first-method.html.twig', [
'detail' => $detail,
]);
}
And there is that 'second' method:
/**
* #Route("/second-method/{date}", name="second_method")
*/
public function secondMethod($date)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
]);
}
I have a button on the second_method twig view which needs to return me back to method_one page.
How can accomplish that as the parameter $id is probably needed in second method but can not find a way to provide it. Maybe there is some other way? Can someone help?
I think in this first way it shoud me like:
{{ path('first_method', {'detailId':detail.id}) }}
As hinted by #msg, you should provide a second parameter to your second page. You will then need the #Entity annotation for Detail conversion and the #ParamConverter annotation for the date conversion :
/**
* #Route("/second_method/{id_detail}/{date}", name="second_method")
* #Entity("detail", expr="repository.find(id_detail)")
* #ParamConverter("date", options={"format": "!Y-m-d"})
*/
public function (Detail $detail, \DateTime $date)
// ...
The parameter conversion will perform a first sanity check and will return a 404 if the entity doesn't exist.
You will also be able to check in your controller that $detail is coherent with the date in the URL. Remember : never trust user input (this includes URLs).
And you can provide this new parameter when your redirect to your second controller :
return $this->redirectToRoute("second_method", [
"date" => $date,
"id_detail" => $workspace->getId() // You can also pass the whole object and fetch the ID in the template
]);
Then in your template :
{{ path('first_method', {'id': id_detail}) }}
Details
For ParamConverters you can find more details here : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
I personnaly prefer the #Entity annotation for conversion of an Entity since you are sure of how the conversion is done : I find it better for when your project grows and your entities have more and more parameters. Eventhough the #ParamConverter would work in your case.
Alternative without changing the second_method route parameters, relying only on the referer.
Add the helper method to the controller:
/**
* Returns the referer url to redirect back if the request came from first_method
* Otherwise returns FALSE.
*/
private function getBackUrl(Request $request, UrlMatcherInterface $matcher)
{
$referer = $request->headers->get('referer');
if (null === $referer) {
return false;
}
$url = parse_url($referer);
if ($url['host'] != $request->getHost()) {
return false;
}
// Remove the baseurl if we're in a subdir or there's no Rewrite
$path = str_replace($request->getBaseUrl(), '', $url['path']);
try {
$route = $matcher->match($path);
} catch (ResourceNotFoundException $e) {
// Doesn't match any known local route
return false;
}
// A route has been determined, check if it matches the requirement
if ('first_method' == $route['_route']) {
return $referer;
}
return false;
}
Change the method to (The Route annotation doesn't change, just the method signature):
public function secondMethod($date, Request $request, UrlMatcherInterface $matcher)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
'back_url' => $this->getBackUrl($request, $matcher),
]);
}
In your template:
{# Show the button only if the request came from first_method (otherwise will be false) #}
{% if back_url %}
<a class="btn" href="{{ back_url }}">Return</a>
{% endif %}
I have a symfony 4.2 form via AJAX with embedded sub-form that is used as a custom group.
The problem is that my imgLinks which is a part of the embedded form group_pictures does not return its error as a part of the group_picture bu as a lone error without any group.
The field is being validated and the error is returned as expected EXCEPT not assigned to the correct group.
In the symfony profiler Form tab, I can see the error related to the field but it does not display the error as part of the group like shown in the screenshot below
This is a very odd behaviour as all my other fields return errors in their respective groups.
In the profiler post parameter we can see that it is submitted with the correct group:
So here is the entity field:
/**
* #var string|null
*
* #ORM\Column(name="img_links", type="simple_array")
*
* #AcmeAssert\ImageLinks
*/
private $imgLinks;
Changing my custom Assert for another bundled with symfony results in the same behaviour so my custom constraint is not the culprit
The Form Type
$builder->add(
$builder->create('group_pictures', FormType::class, array('inherit_data' => true))
->add('imgLinks', CollectionType::class, array(
'label' => false,
'entry_type' => TextType::class,
'allow_add' => true,
'allow_delete' => true,
))
);
My controller function to collect errors and return them to my AJAX
private function getErrorMessages($form)
{
$errors = array();
foreach ($form->getErrors() as $key => $error) {
$errors[] = $error->getMessage();
}
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = $this->getErrorMessages($child);
}
}
return $errors;
}
in the error collector above if I insert code to dump the group picture child like this:
if( $child->getName() == 'group_pictures'){
dump($child);
}
I see that there is no error assigned to it -errors: []
Then the errors returned to the browser (console screenshot) as you can see the error is assigned the key 0 instead of group_picture
getErrorMessages
Method first assign the error as ordered /index => value/ and then for every child form the error is assigned as unordered /key => value/
This part for me is a bit odd
this is what i'm using
public function
function getErrors(FormInterface $form)
{
$this->childErrors($form);
foreach ($form->all() as $key => $child) {
if ($child instanceof FormInterface) {
$this->childErrors($child, true);
}
}
return $this->errors;
}
And this is the childErrors method
private function childErrors(FormInterface $form, $recursive = false)
{
foreach ($form->getErrors() as $error) {
$message = $this->translator->trans($error->getMessage(), [], 'validators');
$this->errors[] = $message;
}
if ($form->count() && $recursive) {
foreach ($form->all() as $child) {
$this->childErrors($child, $recursive);
}
}
}
UPDATE:
yes, it is the error_bubbling options.
The collection type entries should be set error_bubbling to true /it is by default false and the collection type itself is set to true, so it will need to be disabled otherwise the error will bubble up to the main form.
How can edit more products entities in same page (no 1 to many).
In My editaction :
$entities=$em->getRepository('MyBundle:Product')->findAll();
$editForm=array();
$deleteForm=array();
foreach ($entities as $product )
{
$editForm [$port->getId()]= $this->createEditForm($product);
$deleteForm[$port->getId()] = $this->createDeleteForm($product->getId());
}
return $this->render('MyBundle:Product:edit.html.twig', array(
'entities' => $entities,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
I have this error :
Error: Call to a member function createView() on a non-object
And how update the edit.thml.twig to show all form update as table with only one submit ?
This is not about Symfony2, but about PHP, you are calling a method onto an array...
Consider something like this :
$entities = $em->getRepository('MyBundle:Product')->findAll();
$editForms = array();
$deleteForms = array();
foreach ($entities as $product)
{
$editForms[$port->getId()] = $this->createEditForm($product)
->createView();
$deleteForms[$port->getId()] = $this->createDeleteForm($product->getId())
->createView();
}
return $this->render('MyBundle:Product:edit.html.twig', array(
'entities' => $entities,
'edit_forms' => $editForms,
'delete_forms' => $deleteForms,
));
With a template like this one :
{% for form in edit_foms %}
{{ form(form) }}
{% endfor %}
{% for form in delete_foms %}
{{ form(form) }}
{% endfor %}
I have fix this :
change the name of form:
public $name;
/**
* #return string
*/
public function getName()
{
return (string)'port_'.$this->name;
}
public function __construct($name=0) {
$this->name=$name;
}
and in my controller
editAction :
$entities = $em->getRepository('InfraProductBundle:InfraPortDdf')->findAll();
foreach ($entities as $port )
{
$editForm [$port->getId()]= $this->createEditForm($port);
$edit_view[$port->getId()]=$editForm[$port->getId()]->createView();
}
return $this->render('......:edit.html.twig', array(
'entities' => $entities,
'edit_form' => $edit_view,
));
in update action
foreach ($array_id as $key=>$id){
if(!is_numeric(str_replace('port_','',$id)))
continue;
$entity = $em->getRepository('InfraProductBundle:InfraPortDdf')->find(str_replace('port_','',$id));
if (!$entity) {
throw $this->createNotFoundException('Unable to find InfraPortDdf entity.');
}
$editForm = $this->createEditForm($entity);
if($editForm->submit($request->request->get($id)))
$em->flush();
}
in my edit.html.twig:
{% for key, edit in edit_form %}
...
{{form_widget(edit.description,{name:'['~key~'][description]', 'attr': { 'name' : '['~key~'][description]' } } ) }}
....
{%endfor%}
I have Two Action, GetAllPost and newComment
I have a page with many Post and each Post have commentForm
PostController
public function getPostAction () {
return array(
);
}
Twig
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
CommentController
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
}
return array(
'post' => $post,
'form' => $form->createView(),
);
}
TwifFormController
{{ form(form, {'action': path('new_comment',{'id': post.id})})}}
When I insert a new comment I have redirect to new_comment even if my value isn't valid.
How can I redirect to GeTAllPost and show the correct Error or the new Comment?
I tried to use
return $this->redirect($this->generateUrl('get_post',array('error',$form->getErrors())));
and 'error_bubbling' => true,, but each time request a get_post ( GetAllPost ) I do a new render of my Form and I don't see the errors
For Example i'd like to use newCommentAction in several scenario.
For example i GetAllPost for each post, but even in GetSpecificPost, where I Have A specific post, where I Can insert a new comment, but the save ( and the Action ) is the same.
Do I have create a Service ?
UPDATE
After Bonswouar's answer. This is my Code
PostController
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$comment = new Comment();
return array(
'commentForms' => $this->createCreateForm($comment),
);
}
private function createCreateForm (Comment $entity) {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), $entity);
$commentForms[$post->getId()] = $form->createView();
}
return $commentForms;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
//Sometimes I Have only One Form
$commentForms = $this->createCreateForm($comment);
$form = $this->createForm(new CommentType($post->getId()), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
} else {
$commentForms[$post->getId()] = $form->createView();
}
return array(
'commentForms' => $commentForms,
);
}
And I Don't have any Render.
But, I want to re-use newCommentAction also in Single Post, and i Want to create Only one Form. I don't want use $commentForms = $this->createCreateForm($comment);, because i Want just one form,and I have to change template even. How can I do ?
If I'm not mistaking, your problem is that you're posting on new_comment, which is a "sub action".
You actually don't need this Twig render.
You could just generate all the forms you need in the main Action, with something like this :
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), new Comment());
$form->handleRequest($request);
if ($form->isValid()) {
//...
// Edited : to "empty" the form if submitted & valid. Another option would be to redirect()
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
$commentForms[$post->getId()] = $form->createView();
}
return array(
'posts' => $posts,
'commentForms' => $commentForms,
);
Not forgetting to set a dynamic Name in your Form class :
class CommentType extends AbstractType
{
public function __construct($id) {
$this->postId = $id;
}
public function getName() {
return 'your_form_name'.$this->postId;
}
//...
}
And then just normally render your forms in your Twig loop. You should get the errors.
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ form(commentForms[post.id]) }}
<hr>
{%endfor%}
If I didn't miss anything that should do the job.
UPDATE :
After seeing your update, this might be the controller you want (sorry if I didn't understand properly or if I did some mistakes):
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$commentForms[$post->getId()] = $this->createCommentForm($post);
}
return array(
'commentForms' => $commentForms
);
}
private function createCommentForm (Post $post, $request = null) {
$em = $this->getEm();
$form = $this->createForm(new CommentType($post->getId()), new Comment());
if ($request) {
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
}
return $form;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
return array(
'commentForm' => $this->createCommentForm($post, $request);
);
}
What about using a flash message to set your error messages? http://symfony.com/doc/current/components/http_foundation/sessions.html#flash-messages
EDIT: Modifying based on your comments. In your controller you could do this:
foreach ($form->getErrors() as $error) {
$this->addFlash('error', $post->getId().'|'.$error->getMessage());
}
or
$this->addFlash('error', $post->getId().'|'.(string) $form->getErrors(true, false));
What this will do is allow you to tie the error to the particular post you want, as you are passing it a string like 355|This value is already used. If you need to know the field you could add another delimiter for $error->getPropertyPath() in your flash message, or you could overwrite the error name in the entity itself.
Then in your controller you could parse out the flash messages, and add them to an array that your twig template would check:
$errors = array();
foreach ($this->get('session')->getFlashBag()->get('error', array()) as $error)
{
list($postId, $message) = explode('|', $error);
$errors[$postId][] = $message;
}
return array('errors' => $errors, ...anything else you send to the template)
Now your twig template can check for the existence of errors on that particular form:
{% for post in app.user.posts %}
{% if errors[post.id] is defined %}
<ul class="errors">
{% for error_message in errors[post.id] %}
<li>{{ error_message }}</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
I am trying to modify a form used for editing and updating data. However when I try submitting the 'edit' form, I keep getting a 'MethodNotAllowedHttpException'. I'm not sure if this is because I am using the 'PUT' method incorrectly or my 'EditAlbumsController.php' file is not defined correctly.
edit-album.blade.php:
{{ Form::model($album, array('method' => 'PUT', 'route' => array('edit_album', $album->album_id))) }}
/* Form code here */
{{ Form::close() }}
routes.php:
Route::get('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));
EditAlbumsController.php:
class EditAlbumsController extends AlbumsController {
public function __construct()
{
parent::__construct();
}
public function update($id)
{
$input = \Input::except('_method');
$validation = new Validators\Album($input);
if ($validation->passes())
{
$album = Album::find($id);
$album->album_name = $input['album_name'];
/* Additional database fields go here */
$album->touch();
return $album->save();
return \Redirect::route('gallery.album.show', array('id' => $id));
}
else
{
return \Redirect::route('gallery.album.edit', array('id' => $id))
->withInput()
->withErrors($validation->errors)
->with('message', \Lang::get('gallery::gallery.errors'));
}
}
Any help is greatly appreciated!
You need to define the PUT route (you are incorrectly using GET)
Route::put('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));