Upload image using VichUploaderBundle - symfony

I have a problem with VichUploaderBundle, I followed all the instructions but I dont know how my Controller should handle this after finished form becouse image failed to uploaded and database did not write.
my Entity class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ..... other fields
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="integer")
*
* #var integer
*/
private $imageSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Product
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
var_dump($image);
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return Product
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
/**
* #param integer $imageSize
*
* #return Product
*/
public function setImageSize($imageSize)
{
$this->imagesize = $imageSize;
return $this;
}
/**
* #return integer|null
*/
public function getImageSize()
{
return $this->imageSize;
}
}
my FormType class:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class AvatarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', FileType::class, array('label' => 'Brochure (PDF file)'))
;
}
public function getName()
{
return 'avatar_form';
}
}
config:
vich_uploader:
db_driver: orm # or mongodb or propel or phpcr
mappings:
product_image:
uri_prefix: /image/avatar
upload_destination: '%kernel.root_dir%/../web/image/avatar'
Twig template:
<div>
{{ form_start(form) }}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form.imageFile) }}
<button type="submit" class="btn btn-default">Submit</button>
{{ form_end(form) }}
</div>
and finally Controller:
namespace AppBundle\Controller\User;
use AppBundle\Entity\Product;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use AppBundle\Controller\InitController;
use AppBundle\Form\AvatarType;
class UserController extends Controller implements InitController
{
/**
* #Route("/user/avatar", name="AvatarAction")
*/
public function AvatarAction(Request $request)
{
$form = $this->createForm('AppBundle\Form\AvatarType',null,array(
// To set the action use $this->generateUrl('route_identifier')
'action' => $this->generateUrl('AvatarAction'),
'method' => 'POST'
));
$form->handleRequest($request);
if ($request->isMethod('POST')) {
if($form->isValid()){
//$track = new Product();
//$em = $this->getDoctrine()->getManager();
//$em->persist($track);
//$em->flush();
}
return $this->render('default/avatar.html.twig',array(
'form' => $form->createView()
));
}
}
Thanks for all your advice!

Maybe this is not the solution, but your line :
->add('imageFile', FileType::class, array('label' => 'Brochure (PDF file)')):
Should be :
->add('imageFile', VichFileType::class, array('label' => 'Brochure (PDF file)'))
More info : https://github.com/dustin10/VichUploaderBundle/blob/master/Resources/doc/form/vich_file_type.md
What Symfony tells you as error ?
Have a nice day

Related

Symfony 4.2 VichUploaderBundle: File not saved

I'm using VichUploaderBundle in my application to upload the files.
Problem is that even if I followed the docs, the file isn't uploaded (even if seems to be all right, as profiles sais), and no rows was make in the entity table.
Here is my vich_uploader.yaml:
vich_uploader:
db_driver: orm
mappings:
media:
uri_prefix: /media
upload_destination: '%kernel.project_dir%/public/media'
inject_on_load: false
delete_on_update: true
delete_on_remove: true
Here is my Media Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class Media
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="media", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="integer")
*
* #var integer
*/
private $imageSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $imageFile
*/
public function setImageFile(?File $imageFile = null): void
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getImageFile(): ?File
{
return $this->imageFile;
}
public function setImageName(?string $imageName): void
{
$this->imageName = $imageName;
}
public function getImageName(): ?string
{
return $this->imageName;
}
public function setImageSize(?int $imageSize): void
{
$this->imageSize = $imageSize;
}
public function getImageSize(): ?int
{
return $this->imageSize;
}
}
I just need to store the file in the entity, there is no relations between files and other entities.
Here is my MediaType
<?php
namespace App\Form;
use App\Entity\Media;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class MediaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', VichImageType::class, [
'required' => false,
'allow_delete' => true,
'download_uri' => true
])
->add('submit', SubmitType::class, [
'attr' => [
'class' => 'btn btn-success'
],
'label' => 'Upload File'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Media::class,
]);
}
}
The Form is handled by this Twig template:
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel">
<div class="panel-body">
{{ form_start(form) }}
{{ form_widget(form.imageFile) }}
</div>
<div class="panel-footer">
{{ form_widget(form.submit) }}
{{ form_end(form) }}
</div>
</div>
</div>
</div>
</div>

Syfmony: upload files with dropzone

I'm developing a king of simple CMS, with Symfony 4.1.
Regarding my question, we have 2 entities:
Post Entity:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post extends BaseEntity
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="boolean")
*/
private $status;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Category", inversedBy="posts")
*/
private $categories;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Picture", mappedBy="post", orphanRemoval=true, cascade={"persist"})
*/
private $pictures;
/**
* #Assert\All({#Assert\Image(mimeTypes="image/jpeg")})
*
*/
private $pictureFiles;
/**
* Post constructor.
*/
public function __construct()
{
$this->categories = new ArrayCollection();
$this->pictures = new ArrayCollection();
}
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return null|string
*/
public function getContent(): ?string
{
return $this->content;
}
/**
* #param string $content
* #return Post
*/
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
/**
* #return bool|null
*/
public function getStatus(): ?bool
{
return $this->status;
}
/**
* #param bool $status
* #return Post
*/
public function setStatus(bool $status): self
{
$this->status = $status;
return $this;
}
/**
* #return Collection|Category[]
*/
public function getCategories(): Collection
{
return $this->categories;
}
/**
* #param Category $category
* #return Post
*/
public function addCategory(Category $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
return $this;
}
/**
* #param Category $category
* #return Post
*/
public function removeCategory(Category $category): self
{
if ($this->categories->contains($category)) {
$this->categories->removeElement($category);
}
return $this;
}
/**
* #return Collection|Picture[]
*/
public function getPictures(): Collection
{
return $this->pictures;
}
/**
* #param Picture $picture
* #return Post
*/
public function addPicture(Picture $picture): self
{
if (!$this->pictures->contains($picture)) {
$this->pictures[] = $picture;
$picture->setPost($this);
}
return $this;
}
/**
* #param Picture $picture
* #return Post
*/
public function removePicture(Picture $picture): self
{
if ($this->pictures->contains($picture)) {
$this->pictures->removeElement($picture);
if ($picture->getPost() === $this) {
$picture->setPost(null);
}
}
return $this;
}
/**
* #return mixed
*/
public function getPictureFiles()
{
return $this->pictureFiles;
}
/**
* #param $pictureFiles
* #return Post
*/
public function setPictureFiles($pictureFiles): self
{
foreach ($pictureFiles as $pictureFile) {
/** #var Picture $picture */
$picture = new Picture();
$picture->setImageFile($pictureFile);
$this->addPicture($picture);
}
$this->pictureFiles = $pictureFiles;
return $this;
}
}
Picture Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* #ORM\Entity(repositoryClass="App\Repository\PictureRepository")
*/
class Picture
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var File|null
* #Assert\Image(mimeTypes="image/jpeg")
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*/
private $filename;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="pictures")
* #ORM\JoinColumn(nullable=false)
*/
private $post;
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return File|null
*/
public function getImageFile(): ? File
{
return $this->imageFile;
}
/**
* #param File|null $imageFile
* #return Picture
*/
public function setImageFile(? File $imageFile): self
{
$this->imageFile = $imageFile;
return $this;
}
/**
* #return string|null
*/
public function getFilename(): ?string
{
return $this->filename;
}
/**
* #param string $filename
* #return Picture
*/
public function setFilename(string $filename): self
{
$this->filename = $filename;
return $this;
}
/**
* #return Post|null
*/
public function getPost(): ?Post
{
return $this->post;
}
/**
* #param Post|null $post
* #return Picture
*/
public function setPost(?Post $post): self
{
$this->post = $post;
return $this;
}
}
So for adding a Post, I have a PostType:
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Post;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class PostType
* #package App\Form
*/
class PostType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('content', CKEditorType::class)
->add('categories', EntityType::class,
[
'class' => Category::class,
'required' => true,
'choice_label' => 'name',
'multiple' => true,
]
)
->add('pictureFiles', FileType::class,
[
'required' => false,
'multiple' => true,
'label' => 'Add files...',
'attr' =>
[
'action' => '%kernel.project_dir%/public/media/posts'
]
]
)
->add('status')
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}
The view corresponding to that form:
{% form_theme form '/admin/form/switch_btn_layout.html.twig' %}
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="form-row">
<div class="col-md-6">
{{ form_row(form.name) }}
{{ form_row(form.categories) }}
{{ form_row(form.status) }}
</div>
<div class="col-md-6 dropzone" id="postDropzone">
{{ form_row(form.pictureFiles, {'attr': {'class': 'dropzone'}} ) }}
<div class="dropzone-previews" style="border: 1px solid red"></div>
</div>
</div>
<div class="form-group">
{{ form_row(form.content) }}
</div>
<div class="form-group">
{{ form_row(form.status) }}
</div>
{{ form_rest(form) }}
<button class="btn btn-success btn-lg btn-block" id="postSubmit">
{{ button_label|default('Save') }}
</button>
{{ form_end(form) }}
As you can see, the "input" for files as the dropzone css class.
Indeed, my project include the oneup_uploader bundle, for dropzone.
Here the configuration for oneup_uploader:
oneup_uploader:
mappings:
# This is a mapping example, remove it and create your own mappings.
post_image:
frontend: dropzone
namer: oneup_uploader.namer.uniqid
storage:
directory: '%kernel.project_dir%/public/media/posts'
And my script for Dropzone:
Dropzone.autoDiscover = false;
var postDropzone = new Dropzone('.dropzone', {
url: '%kernel.project_dir%/public/media/posts',
// url: 'file/post',
maxFiles: 10,
addRemoveLinks: true,
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
});
postDropzone.on("addedfile", function (file) {
file.previewElement.addEventListener("click", function () {
postDropzone.removeFile(file);
})
});
The issue for me is:
no file is save in the folder
the Post entity is save in my DB, but nothing is save for Pictures.
I also tried to not use OneUploaderBundle, and use VichUploader: the saving part in DB is perfect, but I can't link it to dropzone.
Some help guys ?
Thanks a lot !
Might be useful for new visitors.
You can use a library that extends Symfony Form and adds a new type DropzneType.
1.Install the library
composer require emrdev/symfony-dropzone
This way you will have a new form type DropzoneType
2.Use the type in your form like this
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
{
// userFiles is OneToMany
$builder->add('userFiles', DropzoneType::class, [
'class' => File::class,
'maxFiles' => 6,
'uploadHandler'=>'uploadHandler', // route name
'removeHandler'=> 'removeHandler'// route name
]);
}
Change the uploadHandler and removeHandler options to your endpoints
3.Route uploadHandler/removeHandler might look something like this
/**
* #Route("/uploadhandler", name="uploadHandler")
*/
public function uploadhandler(Request $request, ImageUploader $uploader) {
$doc = $uploader->upload($request->files->get('file'));
$file = new File();
$file->setSrc($doc['src']);
...
$this->getDoctrine()->getManager()->persist($file);
$this->getDoctrine()->getManager()->flush();
return new JsonResponse($file);
}
/**
* #Route("/removeHandler/{id}", name="removeHandler")
*/
public function removeHandler(Request $request,File $file = null) {
$this->getDoctrine()->getManager()->remove($file);
$this->getDoctrine()->getManager()->flush();
return new JsonResponse(true);
}
note that uploadhandler should return a File object
you should pass upload url instead of upload directory
generate url in twig - {{ oneup_uploader_endpoint('post_image') }}
var postDropzone = new Dropzone('.dropzone', {
url: '{{ oneup_uploader_endpoint('post_image') }}',
// url: '%kernel.project_dir%/public/media/posts',
// url: 'file/post',
maxFiles: 10,
addRemoveLinks: true,
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
});

How to preselect generated checkboxes in Symfony 4?

Let's imagine that we want to assign categories to a post, we use an EntityType to generate them based on the amount of categories we have, so we just add the next block of code to our form:
Controller:
->add('categories', EntityType::class, array(
'class' => Category::class,
'choice_label' => 'category_description',
'multiple' => true,
'expanded' => true,
'required' => false,
))
And then save them to the database when the form is submitted, ManyToMany, using ArrayCollection:
foreach($post_data['categories'] as $form_category)
{
$database_category = $database_manager->getRepository(Category::class)->find($form_category->getId());
$post->addCategory($database_category);
}
Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository")
*/
class Post
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
...
/**
* #ORM\ManyToMany(targetEntity="Category", cascade={"persist"})
* #ORM\JoinTable(name="junction_table",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
*/
private $categories;
public function __construct() {
$this->categories = new ArrayCollection();
}
...
public function getCategories()
{
return $this->categories;
}
public function addCategory(Category $category): self
{
$this->categories->add($category);
return $this;
}
}
Image:
Generated checkboxes with description and index
But what if i want to have those checkboxes preselected when i go to edit mode so the user knows which ones were selected last time, how would you approach that?
Also how would you remove one if it is deselected?
If your form fields are mapped to an entity, the data should be set automatically.
You don't even have to add the categories manually to the post.
Here is a full sample of working code that matches what you want to do:
//src/Entity/Group.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\GroupRepository")
* #ORM\Table(name="`group`")
*/
class Group
{
/**
* Group constructor.
*/
public function __construct()
{
$this->players = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Player", inversedBy="groups")
*/
private $players;
/**
* #return mixed
*/
public function getPlayers()
{
return $this->players;
}
/**
* #param mixed $players
*
* #return Group
*/
public function setPlayers($players)
{
$this->players = $players;
return $this;
}
/**
* #param Player $player
*
* #return Group
*/
public function addPlayer(Player $player)
{
$this->players->add($player);
return $this;
}
/**
* #param Player $player
*
* #return Group
*/
public function removePlayer(Player $player)
{
$this->players->removeElement($player);
return $this;
}
}
//src/Entity/Player.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\PlayerRepository")
*/
class Player
{
/**
* #return string
*/
public function __toString()
{
return $this->name;
}
/**
* Player constructor.
*/
public function __construct()
{
$this->groups = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Group", mappedBy="players")
*/
private $groups;
/**
* #return mixed
*/
public function getGroups()
{
return $this->groups;
}
/**
* #param mixed $groups
*
* #return Player
*/
public function setGroups($groups)
{
$this->groups = $groups;
return $this;
}
/**
* #param Group $group
*
* #return Player
*/
public function addGroup(Group $group)
{
$this->groups->add($group);
return $this;
}
/**
* #param Group $group
*
* #return Player
*/
public function removeGroup(Group $group)
{
$this->groups->removeElement($group);
return $this;
}
}
//src/Form/GroupType.php
namespace App\Form;
use App\Entity\Group;
use App\Entity\Player;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GroupType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'players',
EntityType::class,
[
'class' => Player::class,
'multiple' => true,
'expanded' => true,
'required' => false,
]
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Group::class,
]
);
}
}
//src/Controller/GroupController.php
namespace App\Controller;
use App\Entity\Group;
use App\Form\GroupType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class GroupController extends Controller
{
/**
* #Route("/edit-group/{id}", name="group_edit")
* #param Request $request
* #param Group $group
*
* #return Response
*/
public function editGroup(Request $request, Group $group)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(GroupType::class, $group);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($group);
$em->flush();
}
return new Response(
$this->renderView(
'group/edit-group.html.twig',
array(
'form' => $form->createView(),
)
)
);
}
}
//templates/group/edit-group.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<h1>Edit group</h1>
</div>
{{ form_start(form) }}
{{ form_row(form.players) }}
<button type="submit" class="btn btn-success">Edit</button>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}

Controller, repo and twig print array (Symfony3)

I need to print domains of a single user
ENTITY (here is the entity code)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Domini
*
* #ORM\Table(name="domini")
* #ORM\Entity(repositoryClass="AppBundle\Repository\DominiRepository")
*/
class Domini
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="id_user", type="integer")
*/
private $idUser;
/**
* #var string
*
* #ORM\Column(name="dominio", type="string", length=100, unique=true)
*/
private $dominio;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set idUser
*
* #param integer $idUser
*
* #return Domini
*/
public function setIdUser($idUser)
{
$this->idUser = $idUser;
return $this;
}
/**
* Get idUser
*
* #return int
*/
public function getIdUser()
{
return $this->idUser;
}
/**
* Set dominio
*
* #param string $dominio
*
* #return Domini
*/
public function setDominio($dominio)
{
$this->dominio = $dominio;
return $this;
}
/**
* Get dominio
*
* #return string
*/
public function getDominio()
{
return $this->dominio;
}
}
REPOSITORY (here is the Repo code)
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Domini;
use Doctrine\ORM\EntityRepository;
class DominiRepository extends \Doctrine\ORM\EntityRepository
{
/**
* #return dominio[]
*/
public function findAllPublishedOrderedBySize()
{
$query = 'SELECT dominio FROM AppBundle:Domini dominio';
$mydomain = $this->getEntityManager()->createQuery($query);
return $mydomain->execute();
}
}
CONTROLLER
<?php
// src/AppBundle/Controller/DominiController.php
namespace AppBundle\Controller;
use AppBundle\Entity\Domini;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Doctrine\ORM\EntityManagerInterface;
class DominiController extends Controller
{
/**
* #Route("/admin/domini")
*/
public function dominiAction()
{
$title = "ADMIN";
$nome_utente = $this->getUser();
$user = $this->getUser()->getId();
$em = $this->getDoctrine()->getManager();
$dominilist = $em->getRepository('AppBundle:Domini')
->findAllPublishedOrderedBySize();
return $this->render('admin/domini.html.twig', [
'title' => $title,
'nome_utente' => $nome_utente,
'user' => $user,
'dominilist' => $dominilist,
]);
}
}
TWIG
<h1>{{ title }}</h1>
<h2>Benvenuto: {{ nome_utente }}</h2>
<h3>USER ID:{{ user }}</h3>
<ul>
{% for item in dominilist %}
<li>{{ item.dominio }}</li>
{% endfor %}
</ul>
MYSQL TABLE
table name: domini
fields: id, id_user, dominio
Now all works fine but 'dominilist' => $dominilist doesn't work. Dominio is the domain name saved under "dominio". Why?
IF i try to cicle this field it doesn't work.
If you want to select all the data stored in that table, you can use a quick and handy method directly in your controller
# Controller
$dominilist = $em->getRepository('AppBundle:Domini')
->findAll();
Or you can find domini by id in the controller too:
# Controller
$user = $this->getUser()->getId();
$dominilist = $em->getRepository('AppBundle:Domini')->findBy(['id'=>$user]);
And only if you want to have a more complex query, then you can create q method in the repository. As that "I need to print domains of a single user"
# Repository
public function findAllPublishedOrderedBySize($id)
{
$qb = $this->createQueryBuilder('d');
$qb
->select('d.dominio')
->where('d.id = :id')
->setParameter('id', $id)
;
return $qb->getQuery()->getResult();
}
# Controller
$user = $this->getUser()->getId();
$domini = $em->getRepository('AppBundle:Domini')
->findAllPublishedOrderedBySize($user);
Anyway, those type of queries are small, and you can create them in the controller, as I showed you above.

Symfony2: Saving a collection entity confusion

The disclaimer: I'm new to Symfony. Really struggling with the collection field type and simple setup of a OnetoOne relationship.
Scenario: I have a Product entity class and a Category entity class. I am using a collection field on the product to create Category items. I am picturing a separate Category Table with a Name column and a related product_id column. I know it's not typically practical. For arguments sake Category may as well be Feature or something else as I just want to establish the relationship as a scenario to extend. Ultimately Category will become an Image field allowing me to pull related images into a view.
The Problem: I've followed the cookbook article (http://symfony.com/doc/current/cookbook/form/form_collections.html) over and over but just hitting brick wall. I'm on board with the principle but feel like I'm missing something significant. I've got a protoype form field generating from the javascript and I'm successfully persisting/saving new Products (in full) and new Categories (only in part). The related product id is not being written into the join column.
I'm sure it's a case of getters/setters not being taken care of correctly. I' relying on doctrine to generate them automatically. Or the issue may be with some unspecified requirement to set the id to the Category in the controller.
Code to follow. Help greatly appreciated as been banging this around for a couple of days and getting nowhere fast. Really frustrating as grasped all other principles really quickly and really chuffed with building a first project in symfony.
Product Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\Category;
/**
* Page
*
* #ORM\Table(name="product")
* #ORM\Entity
* #Vich\Uploadable
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #ORM\OnetoOne(targetEntity="Category", cascade={"persist"})
*/
protected $categorys;
public function __construct()
{
$this->categorys = new ArrayCollection();
}
}
Category Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Product;
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\OneToOne(targetEntity="Product", cascade={"persist"})
*/
protected $product;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set product
*
* #param \AppBundle\Entity\Product $product
* #return Category
*/
public function setProduct(\AppBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
Product Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Product;
use AppBundle\Entity\Category;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('categorys', 'collection', array(
'type' => new CategoryType(),
'allow_add' => true,
'by_reference' => false,
))
->add('save', 'submit', array(
'attr' => array('class' => 'btn btn-default'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}
public function getName()
{
return 'product';
}
}
Category Type
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\Category;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Category',
));
}
public function getName()
{
return 'category';
}
}
Product Controller: New Product
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
$category->getProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
I bet that you don't need OneToOne but OneToMany relation for Product vs Category. Because i think that one category can have multiple products but a product can only have one category.
Another approach is even to make a ManyToMany relation. In that case each category can have multiple products, but each product can be in multiple categories too.
OneToOne is not often used, only in special circumstances.
I think you dont need the collection-type too in your case. You could change this for the entity type. Just make the entities ManyToOne as i explained.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=64)
*/
private $description;
/**
* #var float
*
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
**/
private $category;
}
and category:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=64)
*/
private $name;
public function __toString() {
return $this->name;
}
}
and now use the console to run some commands:
app/console doctrine:generate:entities AppBundle
app/console doctrine:schema:update --force
app/console doctrine:generate:crud AppBundle:Product
app/console doctrine:generate:crud AppBundle:Category
choose YES if asked if you want to generate write Actions
Add a __toString() method to your Category entity:
public function __toString() {
return $this->name;
}
Now see your new controllers and routes and try them.
To see all your routes:
app/console router:debug
You must have this in your app/config/routing.yml:
app:
resource: "#AppBundle/Controller/"
type: annotation
At the end you will have:
- two new entities
- two new controllers
- two new form types
- eight new views
You can learn a lot from the created code and change everything as you wish.
Good luck
I know it's a old question, but :
/**
* #Route("admin/product/new", name="product_add")
* #Security("has_role('ROLE_ADMIN')")
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm(new ProductType(), $product);
$category = new Category();
$form->handleRequest($request);
if ($form->isValid()) {
#################################
##$category->getProduct($this);##
#################################
$category->setProduct($this);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('products_admin');
}
return $this->render('Product/productAdd.html.twig', array(
'form' => $form->createView(),
));
}
You call getProduct($this) instead of setProduct($this)

Resources