Symfony file field empty on edit - php

I am reading for a while about this but found nothing that works for me.
This is entity
/**
* #ORM\Column(type="string")
*
* #Assert\NotBlank(message="Molimo unesite PDF ili Word fajl.")
* #Assert\File(mimeTypes={ "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.ms-powerpoint", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"})
*/
private $body;
This is form
// Problem with empty field on edit persist! Form expect FileType but db sends string!!!
->add('body', FileType::class, [
//'data_class' => null,// <-- If I change it nothing happened.
'label' => 'Word ili pdf dokument',
'invalid_message' =>'Id posta nije validan',
'attr' => [
'class' => 'form-group',
]])
Everything is very simple and made following docs. I have entity that contains few properties one of them for files ($body). Files are set to be saved in web/uploads/post/ . When I go to edit page I get this error:
"The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string. You can avoid this by setting the "data_class" option to null or by adding a view transformer that transforms a string to an instance of Symfony\Component\HttpFoundation\File\File".
If i set data_status => null field is empty.
What I need is some link to example with this working, Data Transformer maybe?

I was having the same problem in my edit action and solved it by submitting the form manually with the clearMissing parameter set to false instead of using $form->handleRequest($request);, as explained on https://symfony.com/doc/current/form/direct_submit.html:
if ($request->isMethod('POST'))
{
$form->submit($request->request->get($form->getName()),false);
if ($form->isSubmitted() && $form->isValid())
{
This way, the original value of file will not be set to null in case you aren't submitting a new file.

You can get around this by creating a new File() in your editAction. Like this:
/**
* #Route("/edit/{yourEntity}", name="entity_edit")
*
* #return Response
*/
public function editAction(Request $request, YourEntity yourEntity)
{
$body = new File($this->getParameter('your_files_directory') . '/' . $yourEntity->getBody());
$yourEntity->setBody($body);
// rest of your editAction
}

Ok, solved. The answer from #ASOlivieri is good with one small modification. Code in his answer will remember path with file name. So if you want to edit it again it will throw not found exeption. You have to ->setBody again, to be the same as original file name. Set it before flush()
public function editAction(Request $request, Post $post) {
$fileName = $post->getBody();
$file = new File($this->getParameter('post_directory') . '/' . $post->getBody());
$post->setBody($file);
$deleteForm = $this->createDeleteForm($post);
$editForm = $this->createForm('AppBundle\Form\PostEditType', $post);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$post->setBody($fileName);
$this->getDoctrine()->getManager()->flush();
Hope this helps!

In my case what helped was:
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName()));
if ($form->isValid()) {...
to
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) { ...

Related

Why the URL header takes the wrong parameter?

On a CRUD comment system that I put on posts, I have no problem modifying/deleting said comment by retrieving the post of the id and that of the comment. Here is the method used (which is also used to create a comment):
/**
* #Route("{id}/create", name="createComment")
* #Route("{id}/{comment}/modif", name="modifComment", defaults={"comment"=1}, methods="GET|POST")
*/
public function modification(FilmRepository $film, Comment $comment = null, Request $req, EntityManagerInterface $em, $id)
{
if(!$comment) {
$comment = new Comment();
}
$film = $em->getRepository(Film::class)->findOneBy(array('id' => $id));
$user = $this->getUser();
$form = $this->createForm(CommentType::class, $comment);
$form->handleRequest($req);
dump($film);
dump($comment);
if($form->isSubmitted() && $form->isValid()) {
$comment->setAuthor($user);
$comment->setFilm($film);
$comment->setCreatedAt(new \DateTime());
$em->persist($comment);
$em->flush();
$this->addFlash('success', 'L\'action a bien été effectuée');
return $this->redirectToRoute('home');
}
return $this->render('comment/modif.html.twig', [
"comment" => $comment,
"form" => $form->createView()
]);
}
The problem comes when I try to create a new comment. When I am directed to the form, it considers that the post id corresponds to the comment id (for example, if I am on post 1 and want to add a comment it takes me to the comment form 1). However I specified in my twig request (contrary to modify) that I only took the film.id parameter:
{# Modif comment, with two parameters, functionnal#}
Modif
{# Add comment, with one parameter, unfunctionnal#}
Add
I used the same code as for the CRUD of my posts, and yet he when I want to create a new post returns me an empty form :
/**
* #Route("/admin/create", name="createFilm")
* #Route("/admin/{id}", name="modifFilm", methods="GET|POST")
*/
public function modification(Film $film = null, Request $req, EntityManagerInterface $em)
{
if(!$film) {
$film = new Film();
}
$form = $this->createForm(FilmType::class, $film);
$form->handleRequest($req);
if($form->isSubmitted() && $form->isValid()) {
$em->persist($film);
$em->flush();
$this->addFlash('success', 'L\'action a bien été effectuée');
return $this->redirectToRoute('admin');
}
return $this->render('admin/modif.html.twig', [
"film" => $film,
"form" => $form->createView(),
"admin" => true
]);
}
So the problem comes from the url which takes the id of the film and interprets it as the id of the comment, but I don't understand what is causing this?
In your public function, you have Comment $comment.
You are giving two argument to your Route: id and comment
The Paramconverter will try to find the correct Comment with what you gave him (an id and a comment). It will not check where the value comes from in your twig file.
Indeed, if your argument id comes from a film.id, the Paramconverter will give you the wrong comment.
What you should do is send the comment.id to the argument id.
You can change your Route this way :
#Route("{film}/{id}/modif
For your Twig :
Modif

Edit file name in entity with Symfony

Hello (excuse my English, not too confident with it)
I'm actually working on a Symfony website that display some spectacles' information.
For now, I need to add an image when I create one of them. I managed to do so with the help of this tutorial.
It basically works like this: I upload an image into site's directory and then send file's name to the entity (Store in a MySQL database). I can then display the image in spectacle's details.
The issue appeared when I want to edit a spectacle. I can't update the image's name. I have two only possibilities are to 1/not edit the entity, or to 2/change the image name and then get a random one that I can't display anymore (these names are usually like /tmp/phpWb8kwV)
My image is instantiate like this in the entity (in Spectacle.php):
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=255)
* #Assert\NotBlank(message="Veuillez ajouter une image à votre spectacle.")
* #Assert\File(mimeTypes={ "image/png" })
*/
private $image;
And the FormType for the spectacle's form is made like this (in SpectacleType.php):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom')
->add('lieu')
->add('dateSpectacle', null, array(
'label' => 'Date du spectacle',
))
->add('annee')
->add('image',FileType::class, array(
'label' => 'Image du spectacle',
'required' => false, //(Still need to provide a file to finalize the creation/edit)
));
}
And the controller to acces this page is made like this (in SpectacleController.php):
/**
* Creates a new spectacle entity.
*
* #Route("/new", name="admin_spectacle_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$spectacle = new Spectacle();
$form = $this->createForm('FabopBundle\Form\SpectacleType', $spectacle);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
//--------------------------------------------------------------------
$file = $spectacle->getImage();
$fileName = (md5(uniqid())).'.'.$file->guessExtension();
// moves the file to the directory where image are stored
$file->move(
$this->getParameter('img_directory'), //(Define in the service.yml)
$fileName
);
$spectacle->setImage($fileName); //(Don't know how to handle file names without this line)
//---------------------------------------------------------------------
$em->persist($spectacle);
$em->flush();
return $this->redirectToRoute('admin_spectacle_show', array('id' => $spectacle->getId()));
}
return $this->render('spectacle/new.html.twig', array(
'spectacle' => $spectacle,
'form' => $form->createView(),
));
}
The function that is routed to the edit view is approximatively the same, but i can't use
$spectacle->setImage($fileName);
There is two possibilities to solve this: I would like being able to update the new filename in the entity (with the other information) or being able to update the entity without changing filename.
I hope I was clear enough to explain my problem...
Thanks in advance for your responses.
I had this problem when trying to upload a PDF/TEXT.. file.
But for managing images, I advice you to use ComurImageBundle, it helps you a lot and your problem will be resolved.
It's very simple, you download the bundle like it's explained in this link.
Then you modify your code like this :
1/ Instantiation of your image in Spectacle.php (your image is stored in the DB like string)
/**
* #ORM\Column(type="string", nullable=true)
*/
private $image;
2/ Update your base ( php bin/console doctrine:schema:update --force)
3/ Add these functions to your Spectacle.php after updating your DB schema, these functions let you upload and stock your images under specific directory (web/uploads/spectacles) and don't forget to add this two libraries
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #Assert\File()
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* #ORM\PrePersist
*/
public function preUpload()
{
if (null !== $this->file) {
$this->image = uniqid() . '.' . $this->file->guessExtension();
}
}
/**
* #ORM\PostPersist
*/
public function upload()
{
if (null === $this->file) {
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->file->move($this->getUploadRootDir(), $this->image);
}
public function getUploadDir()
{
return 'uploads/spectacles';
}
public function getBaseUrl()
{
$currentPath = $_SERVER['PHP_SELF'];
$pathInfo = pathinfo($currentPath);
return substr($pathInfo['dirname']."/", 1);
}
public function getUploadRootDir()
{
return $this->getBaseUrl() . $this->getUploadDir();
}
public function getWebPath()
{
return null === $this->image ? null : $this->getUploadDir() . '/' . $this->image;
}
public function getAbsolutePath()
{
return null === $this->image ? null : $this->getUploadRootDir() . '/' . $this->image;
}
4/ Modify the FormType (SpectacleType.php)like this
use Comur\ImageBundle\Form\Type\CroppableImageType;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom')
->add('lieu')
->add('dateSpectacle', null, array(
'label' => 'Date du spectacle',
))
->add('annee')
->add('image', CroppableImageType::class, array('label' => 'Image', 'required' => true,
'uploadConfig' => array(
'uploadUrl' => $myEntity->getUploadDir(), // required - see explanation below (you can also put just a dir path)
'webDir' => $myEntity->getUploadRootDir(), // required - see explanation below (you can also put just a dir path)
'fileExt' => '*.png', // required - see explanation below (you can also put just a dir path)
'showLibrary' => false,
),
'cropConfig' => array(
'minWidth' => 128,
'minHeight' => 128,
'aspectRatio' => true,
)
));
}
5/ Remove all these lines from your controller you won't need them
//--------------------------------------------------------------------
$file = $spectacle->getImage();
$fileName = (md5(uniqid())).'.'.$file->guessExtension();
// moves the file to the directory where image are stored
$file->move(
$this->getParameter('img_directory'), //(Define in the service.yml)
$fileName
);
$spectacle->setImage($fileName); //(Don't know how to handle file names without this line)
//---------------------------------------------------------------------
6/ That's it you can call the form of your image in new.html.twig and edit.html.twig, all will get well, try it please and notify me if there is any problem.
The solution was stupid ...
In fact, the controller to access the edit route didn't have those lines:
$em = $this->getDoctrine()->getManager();
...
$em->persist($spectacle);
$em->flush();
I need to finish this quickly. If i have more time later, i'll try to make it work with the ComurImageBundle.
Thank you for your help, i'll be more carefull next time ...

Symfony3 form checkbox save error

I tried to look up on Google but didn't find anyone with such a problem. I think I did everything like the documentation guides but I guess I'm missing something
So I have a form with checkbox like this:
$builder->add(
'productTypes',
EntityType::class,
array(
'label' => 'Available for products',
'class' => 'ShopBundle:ProductType',
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
)
);
When I'm editing everything goes smooth, I can edit existing entry and check or uncheck this checkbox, it saves properly, but when I try to add new Object I get error:
PHP Fatal error: Call to a member function add() on null in
C:\xampp\htdocs\uniacar-sf\src\ShopBundle\Entity\ProductAttribute.php
on line 188
This is my controller action:
public function editAction(Request $request, $id = null)
{
$this->setMenuTab('cars', 'admin');
$productTypes = new ArrayCollection();
if (!empty($id)) {
$attribute = $this->getRepo(ProductAttribute::class)->find($id);
$this->setTitle('admin.cars.attributes.edit');
foreach ($attribute->getProductTypes() as $value) {
$productTypes->add($value);
}
} else {
$attribute = new ProductAttribute();
$this->setTitle('admin.cars.attributes.new');
}
$form = $this->createForm(ProductAttributeForm::class, $attribute);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$attribute = $form->getData();
foreach ($productTypes as $productType) {
if (false === $attribute->getProductTypes()->contains($productType)) {
$productType->getAttributes()->removeElement($attribute);
$this->db()->persist($productType);
}
}
$this->db()->persist($attribute);
$this->db()->flush();
return $this->redirectToRoute('carAdmin', array('tab' => 'attributes'));
}
$this->setVariables(
array(
'form' => $form->createView(),
'attribute' => $attribute,
)
);
return $this->response();
}
$this->db() is my shortcut for $this->getDoctrine()->getManager()
And this is definition part of ProductAttribute that relates to ProductType:
/**
* Constructor
*/
public function __construct() {
$this->productTypes = new ArrayCollection();
}
/**
* Many Attributes have Many ProductTypes
* #ORM\ManyToMany(targetEntity="ProductType", mappedBy="attributes", cascade={"persist"})
*/
private $productTypes;
/**
* #param ProductType $productType
*/
public function addProductType(ProductType $productType)
{
$this->productTypes->add($productType);
$productType->addProductAttribute($this);
}
/**
* #param ProductType $productType
*/
public function removeProductType(ProductType $productType)
{
$this->productTypes->removeElement($productType);
}
Also there is part of ProductType Entity that relates to ProductAttribute:
/**
* Constructor
*/
public function __construct() {
$this->attributes = new ArrayCollection();
}
/**
* Many ProductTypes have Many Attributes
* #ORM\ManyToMany(targetEntity="ProductAttribute", inversedBy="productTypes")
* #ORM\JoinTable(name="product_type_to_attribute")
*/
private $attributes;
/**
* #param ProductAttribute $attribute
*/
public function addProductAttribute(ProductAttribute $attribute)
{
if (!$this->attributes->contains($attribute)) {
$this->attributes->add($attribute);
}
}
public function removeProductAttribute(ProductAttribute $attribute)
{
$this->attributes->removeElement($attribute);
}
I tried to follow Symfony Embed Form Tutorial (How to Embed a Collection of Forms)
I know that in this case there is no embeded collection (I have another field in this Entity, that is embeded collection of forms and it works just fine) but from what I understand relations are the same in this case, it's many to many so I have to tell the Symfony how to treat relations, add and remove objects.
I dumped data that comes in POST but it's the same as for edition - productType is there. Any ideas why do I get this error?
It fires in ProductAttribute Entity in the line $this->productTypes->add($productType);
EDIT:
I updated the controller code, I messed up the logic about unlinking ProductType from ProductAttribute. But it doesn't have any impact on the problem, still the same 500 error when I try to save new object.
EDIT2:
I can't get stack trace from Symfony because I get ordinary browser 500 error (probably because it's Fatal Error, I found it in apache logs). The line in controller which creates error is $form->handleRequest($request);.
This is not a Collection of Forms, but you are using collection specific method, this is not a good practice, however, you don't need this below code when you create a new object.
foreach ($productTypes as $value) {
if (false === $attribute->getProductTypes()->contains($value)) {
$attribute->getProductTypes()->removeElement($value);
}
}
So, I haven't found solution to the problem but I solved it somehow by fixing file structure of my project (moved bundle's Resources from general Resources folder to Bundle's Resources folder). I have no idea why this fixed the issue and what is even the connection between working but not proper folder structure and submitting forms but now it works, so I will mark the question as answered.

Symfony2 multiple choice is not validating

I'm creating a simple list of shop carts with users and products assigned to it.
My form for new cart looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cartName', 'text', array('label' =>'Nazwa koszyka:'))
->add('user', new UserForm(), array('data_class' => 'Zadanie\Bundle\Entity\User', 'label' => false))
->add('products','entity', array('label' => 'Wybierz produkty:', 'class' =>'Zadanie\Bundle\Entity\Product' , 'multiple' => true, 'required' => true))
->add('Zapisz', 'submit');
}
and everything is great except that i can submit the form even without selecting any product.
By far i just added "required" by jquery, but i don't like that. Can somebody explain to me why it is not working properly? :P
EDIT:
Here is the code from controller:
/**
* #Route("/cart/edit/{id}",name="_edit_cart")
* #Template()
*/
public function editAction($id, Request $request)
{
$cart = $this->getDoctrine()->getRepository('ZadanieBundle:Cart')->find($id);
if($cart == null)
{
throw $this->createNotFoundException('Nie znaleziono rekordu');
}
$form = $this->createForm(new CartForm(), $cart);
$form->handleRequest($request);
if($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$data = $form->getData();
$em->persist($data);
$em->flush();
$this->get('session')->getFlashBag()->set('message', 'Koszyk zaktualizowano.');
return $this->redirect($this->generateUrl('_main_carts'));
}
return array('form' => $form->createView());
}
SECOND EDIT:
i found a SOLUTION, ( don't know if the best, but works :) ) so if anybody encounters that:
You have to create your validation file ( validation.yml for example) under YourBundle/Resources/config, in which you have to put information about properties. In my case it was:
Zadanie\Bundle\Entity\Cart:
properties:
cartname:
- NotBlank: ~
user:
- NotBlank: ~
constraints:
- Callback:
methods:
- [Zadanie\Bundle\Form\MyValidator, isUserValid]
and then i created MyValidator:
namespace Zadanie\Bundle\Form;
use Symfony\Component\Validator\ExecutionContextInterface;
use Zadanie\Bundle\Entity\Cart;
class MyValidator {
public static function isUserValid(Cart $cart, ExecutionContextInterface $context)
{
if(!$cart->getUser()->getName())
$context->addViolationAt('name', 'Proszę podać imię.', array(), null);
if(!$cart->getUser()->getSurname())
$context->addViolationAt('surname', 'Proszę podać nazwisko.', array(), null);
if(count($cart->getProducts()) == 0)
$context->addViolationAt('products', 'Proszę wybrać produkt.', array(), null);
}
}
#Mati, regarding your first question about how the required option works, this option only sets the required attribute in HTML5 so does not do anything server side. From the documentation
As of HTML5, many browsers can natively enforce certain validation
constraints on the client side. The most common validation is
activated by rendering a required attribute on fields that are
required. For browsers that support HTML5, this will result in a
native browser message being displayed if the user tries to submit the
form with that field blank.
Regarding your solution, that will certainly work though you may want to consider relying on the built-in validators. I'm fairly sure the product count constraint can use the built-in Count Collection constraint.

Symfony2 form - how overwrite field with default value

I have a form with one default value:
class GearType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options')
->add('model', 'choice', array('choices' => $this->getModelChoices(), 'data' => 2));
}
one of the requirements is form can be pre-populated by re-sellers by passing parameters in URL. It is also nice feature for potential customers to copy and paste link to email, communicators, etc.
I did it this way:
/**
* #Route("/car/gear")
* #Template()
*/
public function gearAction(Request $request)
{
$form = $this->createForm(new GearType());
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
return 'is valid';
}
} else {
$get = $this->getRequest()->query->all();
if (!empty($get)) {
$normalizer = new GetSetMethodNormalizer();
$form->setData($normalizer->denormalize($get, new Gear())); # look here
}
}
return array('form' => $form->createView());
}
unfortunately field 'options' has always default value, instead value passed as a parameter.
I have tried to change line # look here into
$gear = $normalizer->denormalize($get, new Gear());
$form = $this->createForm(new GearType(), $gear);
but no result.
It seems that solution is passing additional parameter to GearType object. I do not like this solution. Does anyone know better way?
Add this snippet, and modifiy between the [ ] as appropriate
$form->bind($request);
if ( [ passed parameters from querystring ] ){ //// New Code
$form->getData()->setOptions( [ processed parameter ]); //// New Code
} //// New Code
if ($form->isValid()) {
return 'is valid';
}
The reason for the field options always having default value may be the actual query. Instead of denormalizing and setting the data directly, modify else fragment to:
} else {
$form = $this->createForm(new GearType(), new Gear(), array(
'validation_groups' => array('not-validating')
));
$form->bind($request);
}
The form will validate only against validations associated with the not-validating group, which will avoid showing the common required alerts if the form is built form GET.
Docs about 'validations-groups': http://symfony.com/doc/current/book/forms.html#validation-groups
The question is similar to: Entity form field and validation in Symfony2?

Categories