I've been trying to setup file upload via default symfony form and
Symfony\Component\HttpFoundation\File\UploadedFile.
I have really trivial form, with one input, button for file upload and submit button. Here is my conroller:
class DefaultController extends Controller
{
public function uploadAction(Request $request)
{
$document = new Elements();
$form = $this->createFormBuilder($document)
->add('name')
->add('file')
->add('save', SubmitType::class, array('label' => 'Create Task'))
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$document->upload();
$em->persist($document);
$em->flush();
return $this->redirectToRoute('felice_admin_upload');
}
return $this->render('FeliceAdminBundle:Default:upload.html.twig', array(
'form' => $form->createView(),
));
}
}
And I have also created an entity, to persist data to database. I'm using doctrine. Everything that I did, was by manual:
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
But the only exception was that I used yml, not annotations. After all, I have an error, when trying to upload file:
FileNotFoundException in File.php line 37:
The file "/tmp/phpFMtBcf" does not exist
What I am doing wrong?
Ok, I'm still haven't found an answer for my question. I've tried to search on different forums, in on French :) So my solutions in next. I gather file data manually, before actually handling a request, then I handle a request and next thing what I do, is I copy my file instead of moving. That is not getting my described error. So it should be quite refactored for beauty and convenience, but it works well. Thank you for attention.
class DefaultController extends Controller
{
/**
* #Route("/product/new", name="app_product_new")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createFormBuilder(null, array('csrf_protection' => false))
->add('pic', FileType::class, array('label' => 'Picture'))
->add('Send', 'submit')
->getForm();
$pic = $request->files->get("form")["pic"];
$form->handleRequest($request);
if ($form->isValid()) {
// $file stores the uploaded PDF file
/** #var \Symfony\Component\HttpFoundation\File\UploadedFile $file */
$file = $pic;
// Generate a unique name for the file before saving it
$fileName = md5(uniqid()) . '.' . $pic->guessExtension();
// Move the file to the directory where brochures are stored
$brochuresDir = $this->container->getParameter('kernel.root_dir') . '/../web/uploads';
copy($pic->getPathname(), $brochuresDir . "/" . $fileName);
// Update the 'brochure' property to store the PDF file name
// instead of its contents
$product->setPic($fileName);
// ... persist the $product variable or any other work
return $this->redirect($this->generateUrl('app_product_new'));
}
return $this->render('FeliceAdminBundle:Default:index.html.twig', array(
'form' => $form->createView(),
));
}
}
Related
I am trying to upload a file, very much following the instructions on Symfony's cookbook, but it doesn't seem to work.
The specific error is as follows, but the background reason is that the file as such does not seem to be ( or remain ) uploaded.
Call to a member function guessExtension() on string
As it happens, the file is momentarily created at upload_tmp_dir, but gets deleted almost immediately ( I know that 'cause I kept that directory visible on my Finder).
The file metadata is available on the var_dump($_FILES) command on the script below.
So, for some reason the file is being discarded which, I believe, causes the specific error seen above.
I believe $file ( from UploadedFile ), should receive the file as such, not the path to it, but not sure how to get there. Particularly is the file does not remain on upload_tmp_dir.
For information, I tried the upload in a plain PHP project I have and it works fine. The file remains in upload_tmp_dir till is moved elsewhere.
Thanks
Here is the controller:
class ApiUserXtraController extends Controller
{
public function UserXtraAction(Request $request, ValidatorInterface $validator) {
$is_logged = $this->isGranted('IS_AUTHENTICATED_FULLY');
if ($is_logged) {
$user = $this->getUser();
}
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository(UserXtra::class);
$userxtra = new UserXtra();
$form = $this->createFormBuilder($userxtra)
->add('imgFile', FileType::class, array('label' => 'file'))
->add('save', SubmitType::class, array('label' => 'Create Task'))
->getForm();
var_dump($_FILES); // outputs file metadata, ie, name, type, tmp_name, size
$form->handleRequest($request);
$userxtra->setUser($user);
if ($form->isSubmitted() && $form->isValid()) {
/**
* #var UploadedFile $file
* */
$file = $userxtra->getImgFile();
var_dump('file', $file);// outputs full path to upload_tmp_dir
$fileName = $this->generateUniqueFileName().'.'.$file->guessExtension(); // **THIS THROWS THE ERROR**
$file->move(
$this->getParameter('user_image_directory'),
$fileName
);
$userxtra->setImgFile($fileName);
//$data = json_decode($data);
return new JsonResponse(array(
'status' => 'ok',
'is_logged' => $is_logged,
));
}
return $this->render('upload.html.twig', array(
'form' => $form->createView(),
));
}
Maybe you are looking for something like
$form->getData('imgFile')->guessExtension();
instead?
Edit: Ah sorry, missed that you are assuming that $file = $userxtra->getImgFile(); actually gives back an UploadedFile object. Apparently that assumption is not correct, as the error you are seeing indicates that it gives back a string instead.
I've found the solution on this SO question.
The docs, or actually Symfony's cookbook, is wrong.
The line on my code above that states:
$file = $userxtra->getImgFile();
should be:
$file = $form->get('imgFile')->getData();
I have problem with my project.
My problem exist when I try to delete some data from entity. My controller was generated with Sesio generator. Here is my code:
/**
* Deletes
* #Route("/{id}/delete", name="delete")
* #Method({"DELETE"})
*/
public function deleteAction(Request $request, Task $task) {
$form = $this->createDeleteForm($task);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->remove($task);
$em->flush();
$this->get('session')->getFlashBag()->add('notice_success', 'Success');
} else {
$this->get('session')->getFlashBag()->add('notice_error', 'NO DELETE');
}
return $this->redirectToRoute('task');
}
/**
* Creates a form to delete.
*/
private function createDeleteForm(Task $task) {
return $this->createFormBuilder()
->setAction($this->generateUrl('delete', array('id' => $task->getId())))
->add('submit', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class, array('label' => 'Delete'))
->getForm()
;
}
I have to tell you that this code work nice on DEV (app_dev.php) but It isn't working in PROD version.
I try to solve that problem and I have tried to change form method to POST and it work property od PROD and DEV. It look like DELETE method doesnt work in PROD version.
Someone have similar problem?
If you're using the AppCache, the kernel will ignore the _method parameter added for DELETE method.
To solve the problem in your web/app.php call Request::enableHttpMethodParameterOverride(); before creating the Request object:
...
$kernel = new AppCache($kernel);
Request::enableHttpMethodParameterOverride();// <-- add this line
$request = Request::createFromGlobals();
...
See http://symfony.com/doc/current/form/action_method.html and http://symfony.com/doc/current/reference/configuration/framework.html#configuration-framework-http-method-override
I am trying to upload file with Symfony3 but with no luck. I have a Profile entity which is linked to User entity with 1-1 relationship. The profile contains a picture column.
I have created a ProfileType and Profile Model. Upon submitting the form, the model contains only the File name and nothing else. The $_FILES array is also empty. This is the code.
$builder
->add("name", TextType::class, array(
"required" => true,
))
->add("email", EmailType::class, array(
"required" => true,
))
->add("city", TextType::class, array(
"required" => false,
))
->add("country", ChoiceType::class, array(
"required" => false,
))
->add("picture", FileType::class, array(
"required" => false,
));
class ProfileModel
{
private $name;
private $email;
private $city;
private $country;
private $picture;
In Controller I am creating the form like this.
$profileForm = $this->createForm(ProfileType::class, $profileModel);
When I get the picture, It contains just the name.
$file = $profileForm->get("picture")->getData();
Hewwo rashidkhan~
Symfony doc is quite complete on the upload process, did you read it?
http://symfony.com/doc/current/controller/upload_file.html
After a few modifications, I choose to use it as service.
Here is the process:
1) Add a few parameters to app/config/config.yml:
under parameters:
parameters:
locale: en
profile_directory: '%kernel.root_dir%/../web/upload/profile'
another_directory: '%kernel.root_dir%/../web/upload/another'
under twig
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
globals:
profile_directory: '/upload/profile/'
another_directory: '/upload/another/'
The two profile_directoryadded just now will be used as variables in both your upload service and twig to point the targer directory.
I added another_directory to explain something more a bit after.
2) Create the service:
Create a new file under src/YourBundle/Services/FileUploader.php
From here, my code is a bit different than what you can find on the Symfony site.
FileUploader.php content:
<?php
namespace YourBundle\Services;
use YourBundle\Entity\ProfileModel;
use YourBundle\Entity\Another;
class FileUploader {
private $profileDir;
private $anotherDir;
public function __construct($profileDir) {
$this->profileDir=$profileDir;
$this->anotherDir=$anotherDir;
}
public function upload($class) {
if($class instanceof ProfileModel) {
$file=$class->getPicture();
$fileName='picture-'.uniqid().'.'.$file->guessExtension();
$file->move($this->profileDir, $fileName);
$class->setPicture($fileName);
}
if($class instanceof Another) {
$file=$class->getPicture();
$fileName='picture-'.uniqid().'.'.$file->guessExtension();
$file->move($this->anotherDir, $fileName);
$class->setPicture($fileName);
}
return $class;
}
}
3) Register the service to app/config/services.yml:
under services:
services:
app.file_uploader:
class: YourBundle\Services\FileUploader
arguments:
- '%profile_directory%'
- '%another_directory%'
Each argument must be in the same order as your privatein the FileUploader.php file.
Those arguments are the ones we setted in app/config/config.yml under parameters.
4) Edit your controller:
The controller part is quite simple.
Add use Symfony\Component\HttpFoundation\File\File; in the import section
Under newAction
public function newAction(Request $request)
{
$profileModel = new ProfileModel();
$form = $this->createForm('YourBundle\Form\ProfileModelType', $profileModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// We upload the file with this line
$profileModel=$this->get('app.file_uploader')->upload($profileModel);
$em = $this->getDoctrine()->getManager();
$em->persist($profileModel);
$em->flush();
return $this->redirectToRoute('profile_model_show', array('id' => $profileModel->getId()));
}
return $this->render('YourBundle:Default:new.html.twig', array(
'profileModel' => $profileModel,
'form' => $form->createView(),
));
}
Under editAction
public function editAction(Request $request, ProfileModel $profileModel)
{
// Add this live above everything else in the code.
$profileModel->setPicture(new File($this->getParameter('profile_directory').'/'.$profileModel->getPicture()));
[...]
}
I haven't gone more far, so I can only explain what to modify after...
In your editAction, you will also have to check that $_FILES isn't empty.
If it's not, then you do the upload process.
If it's, then make sure to not edit the picture column in the SQL query (you will have to do a custom query)
5) Your twig views:
Under show.html.twig
Change
<tr>
<th>Picture</th>
<td>{{ profileModel.picture) }}</td>
</tr>
to
<tr>
<th>Picture</th>
<td><img src="{{ asset(profile_directory~profileModel.picture) }}"></td>
</tr>
Same goes for the index.html.twig.
And you can add (not replace) it to the edit.html.twig to get a preview of the actual picture.
6) Explanations:
In app/config/config.yml we added a few directory to use as parameters in your files.
It will later make it easier to change those directories if needed. (Won't have to edit tons of files... YAY!)
Twig directories always start from the /web folder.
Those directory are used when we register our service as arguments.
They will set our variable in the service file FileUploader.php.
Unlike the Symfony site exemple, we pass the whole object to the upload service.
We then, check from which class this object was created and do our upload process based in it.
Your upload process in the controller is then shortened to a single line.
In twig, we will also use the directory variable set in app/config/config.yml undet the twigproperty.
Like said above, if our upload directory change, we will then just have to edit the app/config/config.yml file.
I hope this will help you solve your upload issues.
Cordially,
Preciel.
You should try
$form = $this->createForm(ProfileType::class, $profileModel);
$form->handleRequest($request);
$file = $profileModel->getBrochure();
More: http://symfony.com/doc/current/controller/upload_file.html
Guys if you want to upload any kind of file in Symfony then I have very simple solution, which I have mentioned in the below. Why I am giving simple solutions because whenever new version come, you have to do some settings in services.yaml or you have to create extra files apart from your main controller.
So solutions is: Just use move($storing_place, $actual_filename) function in your main controller.
Put below codes in your controller file.
$folder_path = 'public/uploads/brochures/';
$file = $request->files->get('myfile');
$fileName = $request->files->get('myfile')->getClientOriginalName();
$file->move($folder_path, $fileName);
return new Response($file);
Hope given solution will help in your project.
So I have a Symfony 3 app that is embedded in a website (.jsp). I have to redirect back to the webpage and not let it fall through to the view as it will display the app out of the website.
So in order to save the form states to give the user feedback on the redirect I have to cache the form errors and values into Redis and retrieve them again applying them back to the form to display.
I'm doing this at the top and bottom of every controller action that utilizes a form.
The problem is this is repeated code and bloats out my controllers. Is there anyway to extend Symfony form or maybe attach to a form event of some kind so that the following happens:
Submit form.
Check if form is valid.
If form is not valid then the parent form class isValid method or form event caches to Redis.
Perform redirect.
When the form is created again check if it has an item in cache corresponding to it's name.
Retrieve that cache and apply back to the form object.
Here is my current controller with the caching I want to abstract.
/**
* #param Request $request
* #return Response
*
* #Route("/signin", name="signin.user")
*/
public function signInAction(Request $request)
{
// Instantiate the form with the Authentication entity.
$authentication = new Authentication();
$headers = $this->getAppHeaders();
$form = $this->createForm(SignInFormType::class, $authentication, [
'config' => $this->getCustomization(true)[$this->getActionName(true)]['form'] ?? [],
'action' => '/apps/' . $headers['schemaId'] . '/' . $headers['schemaVersion'] . $this->generateUrl('signin.user'),
'method' => 'POST',
]);
$redisService = $this->get('cache.redis_service');
$serializer = $this->getSerializer();
// Retrieve from cache and apply back to the form before purging cache.
if ($cachedErrors = $redisService->hget('forms:' . $form->getName(), 'errors')) {
$cachedErrors = $serializer->decode($cachedErrors, 'json');
FormUtil::applyErrorsToForm($form, $cachedErrors);
// Clear cache
$redisService->hDel('forms:' . $form->getName(), 'errors');
}
// Handle the form request which also handle errors.
$form->handleRequest($request);
// If the form is submitted and contains no errors then process.
if ($form->isSubmitted()) {
if ($form->isValid()) {
// Try and perform authentication
$authService = $this->get('core.authentication_service');
if ($response = $authService->performLogin($authentication)) {
return $response;
}
// User could not be authenticated return response.
$formError = new FormError($authService->getResponseBody());
$form->addError($formError);
}
if (count($form->getErrors())) {
$this->flashMessage('danger', [
'title' => 'There has been a problem',
'template' => 'AppBundle:error:sign-in-error.html.twig'
]);
}
// Cache form errors before redirect.
$redisService->hset('forms:' . $form->getName(), 'errors', FormUtil::getFormErrorsToSerialized($form));
return $this->redirect($this->getFullPresencePath() . '/sign_in.page');
}
return $this->render(
'AppBundle::signin.html.twig',
[
'form' => $form->createView(),
'config' => $this->get('core.application_service')->getAppCustomization()
]
);
}
Any help would be greatly appreciated :)
This code works just fine :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
abstract class TableManagerController extends Controller
{
public function listAndAddAction(Request $request)
{
// We get the Entity Manager
$entityManager = $this->getDoctrine()->getManager();
// We get the entity repository
$repository = $entityManager->getRepository($this->entityRepository);
// We build the new form through Form Factory service
$form = $this->get('form.factory')->create($this->entityFormObject, $this->entityObject);
// If user sent the form and sent data is valid
if ($form->handleRequest($request)->isValid())
{
// We set the position of the new entity to the higher existing one + 1
$newPosition = $repository->higherPosition() + 1;
$this->entityObject->setPosition($newPosition);
// We insert the data in DB
$entityManager->persist($this->entityObject);
$entityManager->flush();
// We redirect user to the defined homepage
return $this->redirect($this->generateUrl($this->routeHomePage));
}
return $this->render($this->renderIndexTemplate, array(
'dataList' => $repository->listAll(),
'form' => $form->createView()
));
}
}
But when I just split it in 3 methods, like this :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
abstract class TableManagerController extends Controller
{
public function listAndAddAction(Request $request)
{
$dataList = $this->listMethod();
$form = $this->addMethod($request);
return $this->render($this->renderIndexTemplate, array(
'dataList' => $dataList,
'form' => $form
));
}
protected function listMethod()
{
// We get the Entity Manager
$entityManager = $this->getDoctrine()->getManager();
// We get the entity repository
$repository = $entityManager->getRepository($this->entityRepository);
// We generate the entity management homepage view (list + add form)
return $repository->listAll();
}
protected function addMethod(Request $request)
{
// We get the Entity Manager
$entityManager = $this->getDoctrine()->getManager();
// We get the entity repository
$repository = $entityManager->getRepository($this->entityRepository);
// We build the new form through Form Factory service
$form = $this->get('form.factory')->create($this->entityFormObject, $this->entityObject);
// If user sent the form and sent data is valid
if ($form->handleRequest($request)->isValid())
{
// We set the position of the new entity to the higher existing one + 1
$newPosition = $repository->higherPosition() + 1;
$this->entityObject->setPosition($newPosition);
// We insert the data in DB
$entityManager->persist($this->entityObject);
$entityManager->flush();
// We redirect user to the defined homepage
return $this->redirect($this->generateUrl($this->routeHomePage));
}
// We return the generated form
return $form->createView();
}
}
I get this error which appears once I've sent the form :
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Argument 1 passed to Symfony\Component\Form\FormRenderer::renderBlock() must be an instance of Symfony\Component\Form\FormView, instance of Symfony\Component\HttpFoundation\RedirectResponse given, called in D:\Websites\CPG-2015\app\cache\dev\twig\d6\80\0e5eee6c7aa1859cedb4cd0cc7317a0ebbdd61af7e80f217ce1d2cf86771.php on line 61 and defined in D:\Websites\CPG-2015\vendor\symfony\symfony\src\Symfony\Component\Form\FormRenderer.php line 106") in IBCPGAdministrationBundle:CourseLevel:index.html.twig at line 19.
for which I understand there is something wrong with the form. But I really don't get why since this same form, from the same view, appears perfectly well before I send it.
The problem is here in your addMethod:
// We redirect user to the defined homepage
return $this->redirect($this->generateUrl($this->routeHomePage));
which in turn gets used here without any handling of that return possibility:
$form = $this->addMethod($request);
return $this->render($this->renderIndexTemplate, array(
'dataList' => $dataList,
'form' => $form
));
By returning $this->redirect inside of an if-statement, you're giving two potential return values of addMethod, a FormView or a RedirectResponse. As a result, you then try to pass that RedirectResponse through form which Twig attempts to render (which it can't, of course.)
The solution is to re-work your return logic!