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
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));
}
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
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.
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());
}
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