Symfony2 / Multiple Upload - symfony

I have complete this: SF2 Handle File Uploads with Doctrin
Now I try to do multiple upload, but can't find newer tutorial for this. All what I found is like 2+ years old, and doesn't work.
Could someone help me with this problem?
I tried change $path, $file to array, and add foreach($this->file as $files) to upload() function, but .... error ....
Here is my code:
Entity: http://pastie.org/private/1qvkhcdhojm1n1orfzuja
Controller: http://pastie.org/private/ppcoezaoicdufg5dsa9dw
Twig: http://pastie.org/private/eclba9e84kzfbh96ecgya
PS: I need Something like this in path(in database): "image1.jpeg,image2.jpeg,image3.jpeg,..."
Sorry, but I'm so dumb with this, just learning..
Thank you

here is some pseudo example code showing how i access to the multiple uploaded files and persist an entity for each uploaded file
$form = $this->createForm('doc',null);
$request = $this->getRequest();
if ('POST' === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
$data=$form->getData();
try {
$i=1;
// upload each file
foreach ($data['file'] as $item) {
$document = new Document();
$document->setFile($item);
// upload file
$document->upload();
$document->setUploadedBy($user->getUserName());
// set thumbnail path
$em->persist($document);
// generate images and set paths
$imageService->optiPathSingle($document);
$i++;
}
return $this->redirect($this->generateUrl('gleisdreieck_core_index'));
} catch (\Exception $e) {
$form->addError(new FormError($e->getMessage()));
}
}
return array(
"form"=>$form->createView(),
"tags"=>$tags
);
} else{
return array(
"form"=>$form->createView(),
"tags"=>$tags
);
}
used formbuilder:
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('file', 'file', array(
'label' => 'Attachments',
'required' => true,
'attr' => array (
'accept' => 'image/*',
'multiple' => 'multiple'
)
))
->add('save', 'submit');
}

Related

Symfony 6 - How can I change file upload to multiple file uploads

I'm working on a project where a user is able to upload a file. My code works when a single file is uploaded, but I need to change it so a user is able to upload multiple files.
I want to store the files in my database as String. Currently it is stored as example: "file1.png". When uploading multiple files I would like it to be stored as "file1.png;file2.png;file3.png".
However when I add the "multiple => true" in the form, I get an error when pressing submit by the validator that the input needs to be a String.
My best guess is that I need to use Data transformers, but after reading the docs I still don't know how to approach this. ?
Data Transform
This is the controller (currently it expects a single file, as for multiple I would use foreach):
\#\[Route('/new', name: 'app_blog_new', methods: \['GET', 'POST'\])\]
\#\[IsGranted('IS_AUTHENTICATED')\]
public function new(Request $request, BlogRepository $blogRepository, SluggerInterface $slugger, MailerInterface $mailer): Response
{
$blog = new Blog();
$form = $this-\>createForm(BlogType::class, $blog);
$form-\>handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$additionalImages = $form->get('additional_images')->getData();
if ($additionalImages) {
$originalFilename = pathinfo($additionalImages->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $additionalImages->guessExtension();
try {
$additionalImages->move(
$this->getParameter('blogimage_directory'),
$newFilename
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$blog->setAdditionalImages($newFilename);
}
}
If I add "multiple => true' to this form I get an "expected String" error on the front.
This is the form used to upload images to a blog:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('additional_images', FileType::class, [
'label' => 'Additional images',
'mapped' => false,
'multiple' => true,
'required' => false,
'constraints' => [`your text`
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
],
]);
$builder->get('additional_images')
->addModelTransformer(new CallbackTransformer(
function ($additionalAsArray) {
// transform the array to a string
return implode('; ', $additionalAsArray);
},
function ($additionalAsString) {
// transform the string back to an array
return explode('; ', $additionalAsString);
}
))
;
}
This is the blog entity class which contains the image(s)
#[ORM\Entity(repositoryClass: BlogRepository::class)]
class Blog
{
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $additional_images = null;
}
I tried adding 'multiple => true' to the form and it works, as the user is able to select multiple files. But after submitting I get "implode(): Argument #1 ($pieces) must be of type array, string given"
I found out that all I had to do was add "new All" to the form:
->add('additional_images', FileType::class, [
'label' => 'Additional images',
'mapped' => false,
'required' => false,
'multiple' => true,
'constraints' => [
new All([
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/*',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
])
],
]);
And made my controller work with an array:
$additionalImages = $form->get('additional_images')->getData();
if ($additionalImages) {
$result = array();
foreach ($additionalImages as $image)
{
$originalFilename = pathinfo($image->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $slugger->slug($originalFilename);
$newFilename = $safeFilename . '-' . uniqid() . '.' . $image->guessExtension();
try {
$image->move(
$this->getParameter('blogimage_directory'),
$newFilename
);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
$result[] = $newFilename;
}
$blog->setAdditionalImages(implode(";", $result));
}

Easyadmin add an option for every filter to be inclusive or exclusive (use OR or AND)

I'm trying to modify easyadmin filters to include an option to change de behaviour of the query from "andWhere" to "orWhere" and give the possibility to make the filters non-exclusive.
I'm trying with the class ComparysonFilterType adding a checkbox:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('comparison', $options['comparison_type'], $options['comparison_type_options']);
$builder->add('value', FormTypeHelper::getTypeClass($options['value_type']), $options['value_type_options'] + [
'label' => false,
]);
#I've added this field
$builder->add('useOr', CheckboxType::class, [
'label' => 'no excluyente',
'required' => false
]);
}
And then I'm changing the filter function with a simple conditional:
public function filter(QueryBuilder $queryBuilder, FormInterface $form, array $metadata)
{
$alias = current($queryBuilder->getRootAliases());
$property = $metadata['property'];
$paramName = static::createAlias($property);
$data = $form->getData();
#I've added this lines
if(true === $data["useOr"]){
$queryBuilder->orWhere(sprintf('%s.%s %s :%s', $alias, $property, $data['comparison'], $paramName))
->setParameter($paramName, $data['value']);
}else{
$queryBuilder->andWhere(sprintf('%s.%s %s :%s', $alias, $property, $data['comparison'], $paramName))
->setParameter($paramName, $data['value']);
}
}
This works but maybe there is another approach with custom filters more clean to achieve this goal. I'm trying not to modify the original classes.
I would like to give this option in any filterType and not only in "ComparisonFilterType"
Any suggests

twig how to render a twig template as a variable

I'm trying to render twig template as variable, using symfony. I have a 'sendAction' Controller, which uses the mailgun API to send emails to one or more mailing lists. Here is my code for the Controller:
public function sendAction(Request $request, Newsletter $newsletter, MailgunManager $mailgunManager) {
$form = $this->createForm(SendForm::class);
$form->handleRequest($request);
$formData = array();
if ($form->isSubmitted() && $form->isValid()) {
$formData = $form->getData();
$mailingLists = $formData['mailingLists'];
foreach ($mailingLists as $list) {
$mailgunManager->sendMail($list->getAddress(), $newsletter->getSubject(), 'test', $newsletter->getHtmlContent());
return $this->render('webapp/newsletter/sent.html.twig');
}
}
return $this->render('webapp/newsletter/send.html.twig', array(
'newsletter' => $newsletter,
'form' => $form->createView()
));
}
}
And here's my sendMail (mailgun) function:
public function sendMail($mailingList, $subject, $textBody, $htmlBody) {
$mgClient = new Mailgun($this::APIKEY);
# Make the call to the client.
$mgClient->sendMessage($this::DOMAIN, array(
'from' => $this::SENDER,
'to' => $mailingList,
'subject' => $subject,
'text' => $textBody,
'html' => $htmlBody
));
}
I want my ' $newsletter->getHtmlContent()' to render template called 'newsletter.twig.html'. can anyone help me or point me in the right direction as to what I can do or Where I can find Tutorials or notes on what I am trying to do. the symfony documentation is quite vague.
You can use getContent() chained to your render function.
return $this->render('webapp/newsletter/send.html.twig', array(
'newsletter' => $newsletter,
'form' => $form->createView()
))->getContent();
Simply inject an instance of Symfony\Bundle\FrameworkBundle\Templating\EngineInterface into your action, and you’ll be able to use Twig directly:
public function sendAction(Request $request, EngineInterface $tplEngine, Newsletter $newsletter, MailgunManager $mailgunManager)
{
// ... other code
$html = $tplEngine->render('webapp/newsletter/send.html.twig', [
'newsletter' => $newsletter,
'form' => $form->createView()
]);
}
Note that $this->render() (in the controller action) will return an instance of Symfony\Component\HttpFoundation\Response, while $tplEngine->render() returns a HTML string.

Symfony - Form redirect

I am building simple car renting app in Symfony for a programming class at my university.
On URL /search I show a form to user. Right now when the form is submitted user his /search again but different template is rendered
This is form code:
class SearchQueryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pickupCity', TextType::class)
->add('returnCity', TextType::class)
->add('pickupDateTime', DateTimeType::class, array(
'years' => range(2016,2017),
'error_bubbling' => true,
))
->add('returnDateTime', DateTimeType::class, array(
'years' => range(2016,2017),
'error_bubbling' => true,
))
->add('save', SubmitType::class, array(
'label' => 'Find Car',
'attr' => array(
'class' => 'btn-secondary btn-lg'
)))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchQuery',
));
}
}
And controller for this route:
class SearchController extends Controller
{
public function searchAction(Request $request)
{
$query = new SearchQuery();
$form = $this->createForm(SearchQueryType::class, $query);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/* Some logic here */
return $this->render('AppBundle:default:results.html.twig', array(
'form' => $form->createView()
));
}
return $this->render('AppBundle:default:search.html.twig', array(
'form' => $form->createView()
));
}
}
However when the form is submitted I want to redirect to URL /results?SUBMITTED-PARMS-HERE, so user can send this link to someone and receive the same results. On /results again I wand to render form, this time filled with search params submitted and below render available car.
I don't know if its the best way to handle this problem, current solution works but link can't be send to someone else.
EDIT:
I change the code to fallowing:
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$session = $this->get('session');
$session->set('pickupCity', $data->getPickupCity());
$session->set('returnCity', $data->getReturnCity());
$session->set('pickupDateTime', $data->getPickupDateTime());
$session->set('returnDateTime', $data->getReturnDateTime());
return $this->redirectToRoute('results', array(
'pickupCity' => $data->getPickupCity(),
'returnCity' => $data->getReturnCity(),
'pickupDate' => $data->getPickupDateTime()->format('d-m-Y-H-i'),
'returnDate' => $data->getReturnDateTime()->format('d-m-Y-H-i'),
), 301);
}
So as a result I receive URL ./results/Paris/Berlin/10-02-2016-10-00/17-02-2016-10-00
And in routing.yml:
results:
path: /results/{pickupCity}/{returnCity}/{pickupDate}/{returnDate}
defaults: { _controller: AppBundle:Search:result}
methods: [POST]
Because I only want to receive POST request on this route and now when I submit form I get error page saying:
No route found for "GET /results/Paris/Berling/01-07-2016-00-00/01-11-2016-00-00": Method Not Allowed (Allow: POST)
Any idea how to fix it?
EDIT 2:
Never mind, my reasoning was bad.
Not sure, but maybe using this example.
$this->redirect($this->generateUrl('default', array('pickupCity' => $pickupCity, 'returnCity' => $returnCity, 'pickupDateTime' => $pickupDateTimepickupDateTime)));
With your own parameter, which you can get where you put your comment
/* Some logic here */
You can use getQueryString() function.
Sample:
if ($form->isSubmitted() && $form->isValid()) {
return $this->redirect('results?'.$request->getQueryString());
}

Calling $builder->getData() from within a nested form always returns NULL

I'm trying to get data stored in a nested form but when calling $builder->getData() I'm always getting NULL.
Does anyone knows what how one should get the data inside a nested form?
Here's the ParentFormType.php:
class ParentFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('files', 'collection', array(
'type' => new FileType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false
);
}
}
FileType.php
class FileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Each one of bellow calls returns NULL
print_r($builder->getData());
print_r($builder->getForm()->getData());
die();
$builder->add('file', 'file', array(
'required' => false,
'file_path' => 'file',
'label' => 'Select a file to be uploaded',
'constraints' => array(
new File(array(
'maxSize' => '1024k',
))
))
);
}
public function setDefaultOptions( \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver )
{
return $resolver->setDefaults( array() );
}
public function getName()
{
return 'FileType';
}
}
Thanks!
You need to use the FormEvents::POST_SET_DATA to get the form object :
$builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) {
$builder = $event->getForm(); // The FormBuilder
$entity = $event->getData(); // The Form Object
// Do whatever you want here!
});
It's a (very annoying..) known issue:
https://github.com/symfony/symfony/issues/5694
Since it works fine for simple form but not for compound form. From documentation (see http://symfony.com/doc/master/form/dynamic_form_modification.html), you must do:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
// check if the Product object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Product"
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
});
The form is built before data is bound (that is, the bound data is not available at the time that AbstractType::buildForm() is called)
If you want to dynamically build your form based on the bound data, you'll need to use events
http://symfony.com/doc/2.3/cookbook/form/dynamic_form_modification.html

Resources