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.
Related
First off, I am building using Symfony components. I am using 3.4. I was following the form tutorial https://symfony.com/doc/3.4/components/form.html which lead me to this page
https://symfony.com/doc/current/forms.html#usage
I noticed that Symfony added a Form directory to my application.
This was great! I thought I was on my way. So, in my controller, I added this line.
$form = Forms::createFormFactory();
When I tried loading the page, everything went well with no error messages until I added the next two lines.
->addExtension(new HttpFoundationExtension())
->getForm();
I removed the ->addExtension(new HttpFoundationExtension()) line and left the ->getForm() thinking it would process without the add method call. It did not. So, I backed up to see if the IDE would type hint for me.
In the IDE PHPStorm, these are the methods that I have access to but not getForm per the tutorial
Every tutorial I have tried ends with not being able to find some method that does not exist. What do I need to install in order to have access to the ->getForm() method?
UPDATE:
I have made a couple of steps forward.
$form = Forms::createFormFactory()
->createBuilder(TaskType::class);
The code above loads with no errors. (Why is still fuzzy). But next stop is the createView(). None existant also. I only get hinted with create().
Reading between the lines in this video help with the last two steps. https://symfonycasts.com/screencast/symfony3-forms/render-form-bootstrap#play
UPDATE 2:
This is what I have now.
$session = new Session();
$csrfManager = new CsrfTokenManager();
$help = new \Twig_ExtensionInterface();
$formFactory = Forms::createFormFactoryBuilder()
->getFormFactory();
$form = $formFactory->createBuilder(TaskType::class)
->getForm();
//$form->handleRequest();
$loader = new FilesystemLoader('../../templates/billing');
$twig = new Environment($loader, [
'debug' => true,
]);
$twig->addExtension(new HeaderExtension());
$twig->addExtension(new DebugExtension());
$twig->addExtension($help, FormRendererEngineInterface::class);
return $twig->render('requeueCharge.html.twig', [
'payments' => 'Charge',
'reportForm' => $form->createView()
]);
Does anyone know of an update standalone for example? The one that everyone keeps pointing two is 6 years old. There have been many things deprecated in that time period. So, it is not an example to follow.
Your Form class and method createFormFactory must return object that implement FormBuilderInterface then getForm method will be available. You need create formBuilder object.
But this can't be called from static method because formBuilder object need dependency from DI container. Look at controller.
If you want you need register your own class in DI and create formBuilder object with dependencies and return that instance of object.
EDIT
You don't need to use abstract controller. You can create your own class which is registered in DI for geting dependencies. In that class you create method which create new FormBuilder(..dependencies from DI from your class ...) and return instance of that FormBuilder. Then you can inject your class in controller via DI.
Example (not tested)
// class registered in DI
class CustomFormFactory
{
private $_factory;
private $_dispatcher;
public CustomFormFactory(EventDispatcherInterface $dispatcher, FormFactoryInterface $factory)
{
$_dispatcher = $dispatcher;
$_factory = $factory;
}
public function createForm(?string $name, ?string $dataClass, array $options = []): FormBuilderInterface
{
// create instance in combination with DI dependencies (factory..) and your parameters
return new FormBuilder($name, $dataClass, $_dispatcher, $_factory, $options);
}
}
Usage
$factory = $this->container->get('CustomFormFactory');
$fb = $factory->createForm();
$form = $fb->getForm();
I'm writting tests and task for a new app for an application and I need to access to the "app config parameters" of this new app defined in /apps/mynewapp/config/app.yml. I thought it will be as easy as written in the Symfony doc, but it seems I've forgotten something.
When I get my config: $actions = sfConfig::get("app_actions") it is NULL. I thought the config name is wrong, but when I get all the config parameters available with sfConfig::getAll(), I don't have my app config parameters.
Maybe I've forgotten to include my /apps/mynewapp/config/app.yml?
There is the content of my file:
all:
.array:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
And there is how I try to access to my parameters in /apps/mynewapp/lib/GRM/GRMSender.class.php:
class GRMSender
{
private $actionClassNames;
private $messageClassNames;
public function __construct()
{
$this->actionClassNames = sfConfig::get("app_actions");
$this->messageClassNames = sfConfig::get("app_messages");
}
}
The class has already been autoloaded and I'm able to instantiate the class in my unit test scripts.
Thank you for your help.
EDIT
The problem is about my tests (in /test/unit) and my tasks (in /lib/task). I have to use what I did in my application "mynewapp". I did some things :
For the tasks, I defined the application in my task options :
class mynewappActionTask extends sfBaseTask
{
protected function configure()
{
// Do some configuration...
try {
$this->addOptions(array(
new sfCommandOption(
'application',
"app",
sfCommandOption::PARAMETER_REQUIRED,
'The application name',
"mynewapp" // There
),
));
} catch (sfCommandException $e) {}
}
}
For the tests, I wrote a file which loads my mynewapp config. IMHO it's a hack and there is a better way to do it :
$configMynewapp = ProjectConfiguration::getApplicationConfiguration("mynewapp", sfConfig::get("sf_environment"), true);
There must be better ways to get mynewapp config parameters in tasks and in tests. In mynewapp files (controller, lib, etc.) it's ok.
Try to do this:
/apps/mynewapp/config/app.yml
all:
actions:
bind_destroy: BindDestroyAction
bind_subscribe: BindSubscriptionAction
messages:
bind_destroy: BindDestroyMessage
bind_subscribe: BindSubscriptionMessage
Then you can get:
$actions = sfConfig::get('app_actions');
It will return:
$actions => array(
'bind_destroy' => 'BindDestroyAction',
'bind_subscribe' => 'BindSubscriptionAction'
)
Anyway, you can access one of them directly:
$action = sfConfig::get('app_actions_bind_destroy')
$action => 'BindDestroyAction'
I'm using FMElfinder in association with TinyMCE for managing the assets (images, pdf ...) of the users (managed with FOSUSerBundle)
I've seen that this tool can handle multiple root folder, but in my case, it isn't quite usable : i would like to have a root folder for each user.
In the configuration file app/config/config.yml, there is the root path(s) defined :
fm_elfinder:
instances:
default:
locale: %locale%
...
connector:
roots:
uploads:
driver: LocalFileSystem
path: uploads/data
I was thining about "simply" changing the path to something like :
path: uploads/data/{the_username}
where the username would be the username of the currently logged user
In a controller i can do
$user = $this->get('security.token_storage')->getToken()->getUser();
$username = $user->getUsername();
But i don't know if it's possible (and if so, how) to access specifically the username of the logged user into a config file
Thank you if you have any suggestion
=================[EDIT] ==========================================
I've use the override of configuration. I think i followed the steps, but i haven't managed to make it work :
1 - Create the class
use FM\ElfinderBundle\Model\ElFinderConfigurationProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ElfinderConfigurator implements ElFinderConfigurationProviderInterface
{
protected $container;
protected $options;
/**
* #param ContainerInterface $container
*/
public function __construct($options, ContainerInterface $container)
{
$this->container = $container;
$this->storage = $container->get('security.token_storage');
$this->options = $options;
}
/**
* #param $instance
*
* #return array
*/
public function getConfiguration($instance)
{
//retrieve basepath
$basepath_abs = $this->container->get('kernel')->getRootDir()."/../web/uploads";
$basepath = "uploads/data";
//define path for user
$userid = $this->storage->getToken()->getUser()->getId();
$root = $basepath.'/'.$userid;
$this->options['instances']['default']['connector']['roots']['uploads']['path'] = $root.'/root';
$this->options['instances']['default']['connector']['roots']['uploads']['upload_max_size'] = '2M';
$option = [
'corsSupport' => false,
'roots' => $this->options['instances']['default']['connector']['roots'],
];
$root_abs = $basepath_abs.'/data/'.$userid;
//creates dir if not available
if (!is_dir($root_abs)) {
mkdir($root_abs.'/root', 0775, true);
}
return $option;
}
}
2 - Set my service :
myvendor.mybundle.elfinder_configurator:
class: Myvendor\Mybundle\Services\ElfinderConfigurator
arguments: ["%fm_elfinder%", "#service_container"]
3 - Call the service in app/config/config.yml
fm_elfinder:
configuration_provider: myvendor.mybundle.elfinder_configurator
...
It works partially : When i open the elfinde, the directory are correctly created if they don't exists. But there must be a path problem, and i'm not sure it's well overriden because :
- The thumbs are not displayed in elfinder
- When i add the image to the editor, i don't have the correct path of the image, i have :
//app_dev.php/efconnect?cmd=file&target=l1_Q2FwdHVyZSBkJ8OpY3JhbiBkZSAyMDE2LTAxLTI0IDE0OjM2OjI0LnBuZw
instead of the actual path of the image (if i don't use the override, the tool works and gives me this path)
../../../../uploads/data/1/root/img1.png
and no image is displayed.
Also, if i look in the js console for the
efconnect?cmd=open&target=&init=1&tree=1&_=1469377765664
I see that uplMaxSize is 200M,
in any case, there is no js error in the console
I think you are looking for a custom config provider:
https://github.com/helios-ag/FMElfinderBundle/blob/master/Resources/doc/advanced-configuration.md#custom-configuration-provider
You could then inject the token storage into the service and fetch the user from
like in any controller:
services:
my_elfinder_configurator:
class: Acme\DemoBundle\elFinder\UserAwareConfigurator
arguments: ["#token_storage", "%any_container_params%"]
I made a bundle in my symfony2 project to handle all the webservices I use to save my files in the cloud (like amazon S3). Some of this webservices allow to send files directly on their Server without using mine with forms like that :
<form action="api.thecloud.com/specificId?redirectUrl=MyUrlToGetTheResponse" >
<input type="file" name="file" />
<input type="submit" />
</form>
The given redirectUrl param allow me to get informations from the upload and save its in my database.
My problem is that I want to make it transparent and customizable for the other bundles, like with a standart symfony form type. Is there a good way to do this ?
The solution I found is a bit complex and I think there is a cleaner way to do this :
I create a form type which specify the action url :
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('file', 'file');
$builder->add('otherPostUploadInformation', 'hidden');
$builder->setAction( $this->Cloudservice->generateUploadUrl( 'my_redirect_url' ) ) ;
}
public function getName(){
return '';
}
The action route is given in an other way (php or twig function) and saved in a session.
In my_redirect_url's action, I get and save the informations from the webservice and redirect to the action saved in session :
public function myRedirectUrlAction() {
$request = $this->getRequest() ;
// Recreate a form with received information
$rsrc = new CloudUploadRessource( $request->query->get( 'id' ), $request->query->get( 'url' ) );
$form = $this->createForm(new CloudUploadType(), $rsrc );
// transform it to array and add it to the request like if it has been posted
$formArray = $this->transformFormArray( $form->createView() ) ;
$request->request->add( $formArray);
// redirect to the client action url with the request
// So he can do $form->bind($request); $form->isValid(); ...
$clientActionRoute = $request->getSession()->get( 'clientActionRoute' ) ;
return $this->forward($clientActionRoute, array('request' => $request ) );
}
Thanks
As for me, it is kind a strangely to use such methods (uploading files directly to third party resource). Why don't you just use bundle like : https://github.com/KnpLabs/KnpGaufretteBundle . You may configure as many adapters as you need. But in this case, you'll be able to test your entire application/project by just changing the addapters configuration for test/dev env.
I seem to have come across an issue with Symfony 2 that I have not seen before (or more likley im missing something really obvious).
I had a route which took no parameters and worked perfectly, linking up to the controller and displaying the correct view. I then updated the route to take a single parameter like so:
# Note routing
gibbo_dummy_notes_Add:
pattern: /notes/add/{notebook}
defaults: { _controller: GibboDummyBundle:Note:add, notebook: 0}
requirements:
_method: POST|GET
However if I now try and access this route notes/add/test I get the error The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?. If I remove the parameter it works perfectly notes/add.
This setup is exactly the same as the rest of the routes that use parameters in my app.
The action in the controller definitely returns a response object. If I place a die('test'); at the top of the action and remove the parameter from the URL I reach the die statement and see 'test', but if I add the parameter to the URL it shows the error, so its clearly not reaching my controller or action when including the parameter but as far as I can tell the route is setup correctly, and im not getting the normal route not defined error I would expect to see if there was an issue with the url.
Im running it under the dev environment and I have tried other browsers / cleared cache etc. The action inside the controller NoteController looks like
public function addAction($notebook)
{
$pageTitle = 'Note';
$form = $this->createForm(new NoteType(), null, array(
'validation_groups' => array('add')
));
$request = $this->getRequest();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
/** #var $em \Doctrine\ORM\EntityManager */
$em = $this->getDoctrine()->getManager();
/** #var $note Note */
$note = $form->getData();
$note->setNotebook();
$em->persist($note);
$em->flush();
return $this->redirect($this->generateUrl('gibbo_dummy_note_View'));
}
}
return $this->render('GibboDummyBundle:Note:new.html.twig', array(
'pageTitle' => $pageTitle,
'form_add' => $form->createView(),
'selected' => 'codebooks'
));
}
Can anyone shed some light about what I might be doing wrong?
Please add the controller code for gibbo_dummy_note_View route.
Most likely, your redirection ends up in a bad controller method - the one that does not have return statement