How to save/add image at Symfony 4.2 Admin using VichUploader Bundle - sonata-admin

I have 2 tables:
product: id, name
image: id, product_id, path
When I click Edit 'image' and click Update, all fields of 'image' are updated successfully except 'path'
When I click Add new 'image' and click Create, it occurred an error: Failed to create object: App\Entity\Image
I know it is error of file upload but I dont know how to fix it. Here's my code:
services.yaml
parameters:
app.path.product_images: /uploads/product'
vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
product_images:
uri_prefix: '%app.path.product_images%'
upload_destination: '%kernel.project_dir%/public%app.path.product_images%'
Image 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(repositoryClass="App\Repository\ImageRepository")
*/
class Image
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #var string $path
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Product", inversedBy="images")
*/
private $product;
/**
* #Vich\UploadableField(mapping="image", fileNameProperty="path")
* #var File $imageFile
*/
private $imageFile;
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param File
*/
public function setImageFile(File $path = null)
{
$this->imageFile = $path;
}
public function __toString()
{
return (string) $this->getName();
}
public function getId(): ?int
{
return $this->id;
}
public function getPath(): ?string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
}
ImageAdmin.php
protected function configureFormFields(FormMapper $formMapper): void
{
$formMapper
->add('id')
->add('imageFile',FileType::class,[
'label' => 'Image file'
])
->add('product', ModelType::class,[
'label'=>'Product name'
])
;
}

Related

symfony2 file lost upon form error

I am using a standard implementation of file upload in connection with doctrine, as per the example on the symfony2 website tutorials.
When my upload form encounters an error in validation, and sends the user back to the form with error messages, it looses the file chosen for upload, although if I var_dump my $entity->file I can see that it has the file...
//if form is valid, do some stuff... if not:
else {
//var_dump($entity->file); //This works, I get my file
//die;
//Get and check the folder chosen as parent
$entity->setFolder( $this->checkFolderId($request->request->get('folder')) ); //will cause die() if folder doesn't belong to this company
$folders = $this->getFolders();
return $this->render('BizTVMediaManagementBundle:Image:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
'folders' => $folders,
'fileExists' => $fileExists,
));
}
After this is put to the twig view, there is nothing in the file field.
Here is my entity...
<?php
namespace BizTV\MediaManagementBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* BizTV\MediaManagementBundle\Entity\Image
*
* #ORM\Table(name="image")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
* #Assert\NotBlank
*/
private $name;
/**
* #var integer $width
*
* #ORM\Column(name="width", type="integer")
*/
private $width;
/**
* #var integer $height
*
* #ORM\Column(name="height", type="integer")
*/
private $height;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $path;
/**
* #var object BizTV\BackendBundle\Entity\company
*
* #ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
*/
protected $company;
/**
* #var object BizTV\MediaManagementBundle\Entity\Folder
*
* #ORM\ManyToOne(targetEntity="BizTV\MediaManagementBundle\Entity\Folder")
* #ORM\JoinColumn(name="folder", referencedColumnName="id", nullable=true)
*/
protected $folder;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// do whatever you want to generate a unique name
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
return 'uploads/images';
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set width
*
* #param integer $width
*/
public function setWidth($width)
{
$this->width = $width;
}
/**
* Get width
*
* #return integer
*/
public function getWidth()
{
return $this->width;
}
/**
* Set height
*
* #param integer $height
*/
public function setHeight($height)
{
$this->height = $height;
}
/**
* Get height
*
* #return integer
*/
public function getHeight()
{
return $this->height;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set company
*
* #param BizTV\BackendBundle\Entity\company $company
*/
public function setCompany(\BizTV\BackendBundle\Entity\company $company)
{
$this->company = $company;
}
/**
* Get company
*
* #return BizTV\BackendBundle\Entity\company
*/
public function getCompany()
{
return $this->company;
}
/**
* Set folder
*
* #param BizTV\MediaManagementBundle\Entity\Folder $folder
*/
public function setFolder(\BizTV\MediaManagementBundle\Entity\Folder $folder = NULL)
{
$this->folder = $folder;
}
/**
* Get folder
*
* #return BizTV\MediaManagementBundle\Entity\Folder
*/
public function getFolder()
{
return $this->folder;
}
}
And the form:
<?php
namespace BizTV\MediaManagementBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ImageType extends AbstractType
{
function __construct($createAction=0) {
$this->createAction = $createAction;
}
public function buildForm(FormBuilder $builder, array $options)
{
$createAction = $this->createAction;
if ($createAction) {
$builder
->add('file')
;
}
$builder
->add('name', 'text', array('label' => 'Namn'))
;
}
public function getName()
{
return 'biztv_mediamanagementbundle_imagetype';
}
}
You can't, for security purposes, set a file for the upload field. See here for more info. How to set the value of a HTML file field?
I suggest you are trying to access the file property of your entity in twig. Please take a quick look at your upload function.
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
As you can see the file property is being unset after the upload and persist operation has completed.
Now to have twig show your actual image you have to use the webPath property as this is the generated url to your newly uploaded image.
File uploading can be handled a bit easier with the Dustin10/VichUploaderBundle which also supports file system abstraction with KnpLabs/Gaufrette.
Hope this helps :)

How to change image when edit/create using VichUploader bundle at admin in Symfony 4.2

I have 2 tables:
product: id, name
image: id, product_id, path
When I click Edit 'image' and click Update, all fields of 'image' are updated successfully except 'path'
When I click Add new 'image' and click Create, it occurred an error: Failed to create object: App\Entity\Image
I know it is error of file upload but I dont know how to fix it. Here's my code:
services.yaml
parameters:
app.path.product_images: /uploads/product'
vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
product_images:
uri_prefix: '%app.path.product_images%'
upload_destination: '%kernel.project_dir%/public%app.path.product_images%'
Image 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(repositoryClass="App\Repository\ImageRepository")
*/
class Image
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #var string $path
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Product", inversedBy="images")
*/
private $product;
/**
* #Vich\UploadableField(mapping="image", fileNameProperty="path")
* #var File $imageFile
*/
private $imageFile;
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param File
*/
public function setImageFile(File $path = null)
{
$this->imageFile = $path;
}
public function __toString()
{
return (string) $this->getName();
}
public function getId(): ?int
{
return $this->id;
}
public function getPath(): ?string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
}
ImageAdmin.php
protected function configureFormFields(FormMapper $formMapper): void
{
$formMapper
->add('id')
->add('imageFile',FileType::class,[
'label' => 'Image file'
])
->add('product', ModelType::class,[
'label'=>'Product name'
])
;
}

Hide a route in the doc api platform

I realize an API with API Platform under Symfony4 and I try to hide an entity in the doc which is accessible only to the ROLE_ADMIN of the blow no interest to be visible in the doc.
Here is the entity I want to hide:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ApiResource(
* attributes={"access_control"="is_granted('ROLE_ADMIN')"}
* )
* #ORM\Entity(repositoryClass="App\Repository\OrderStatusRepository")
*/
class OrderStatus
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("orderGET")
*/
private $label;
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return null|string
*/
public function getLabel(): ?string
{
return $this->label;
}
/**
* #param string $label
* #return OrderStatus
*/
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
}
Thank you for your help
This isn't supported out of the box (but it would be a nice contribution).
What you can do is to decorate the DocumentationNormalizer to unset() the paths you don't want to appear in the OpenAPI documentation.
More information: https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification

Symfony REST api - Entity association field -> value is not valid

In Symfony I'm writing an API for Angular2. I use the FOSRestBundle with the JMSSerializerBundle. Now, I have an entity "User" that has an entity field "address" with a OneToOne association. And I'm having trouble saving the address of the user.
So I first do a GET of the user object and it returns the whole object with the address as json. Then I do PUT request with that exact same object. In my PUT function I use a Symfony form to validate the data and there it returns an error:
{
"children": {
"address": {
"errors": [
"This value is not valid."
]
}
}
}
I have some other fields on my User entity and those are saved perfectly when I leave out the address field in my form-builder. BTW: I left out those other fields to not overload the amount of code.
I use these versions:
symfony: 3.1
jms/serializer: 1.1
friendsofsymfony/rest-bundle: 2.0
I've been looking for 2 days now and I can't find anything that helps me with this issue. I use the date transformations like FOSRestBundle says: http://symfony.com/doc/current/bundles/FOSRestBundle/2-the-view-layer.html#data-transformation
I hope I formulated my question good enough and gave enough info.
Here is my simplified code:
User class:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Address", cascade={"persist", "remove"}, orphanRemoval=true)
* #JMS\Type("AppBundle\Entity\Address")
*/
private $address;
Address class:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Address
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $street;
/**
* #ORM\Column(type="string")
*/
private $number;
/**
* #ORM\Column(type="string")
*/
private $postalCode;
/**
* #ORM\Column(type="string")
*/
private $city;
/**
* #ORM\Column(type="string")
*/
private $country;
UserType class:
use FOS\RestBundle\Form\Transformer\EntityToIdObjectTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Data transformation needed for relationship entities
$addressTransformer = new EntityToIdObjectTransformer($options['em'], 'AppBundle:Address');
$builder
->add($builder->create('address', TextType::class)->addModelTransformer($addressTransformer))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
'csrf_protection' => false,
'allow_extra_fields' => true,
'em' => null
));
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'user';
}
}
UserController class:
use AppBundle\Entity\User;
use AppBundle\Form\UserType;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\View\View;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* Class UserController
* #package AppBundle\Controller
*/
class UserController extends Controller
{
/**
* #Rest\View
* #Route("/users/{id}")
* #Method("PUT")
*/
public function putAction(Request $request, $id)
{
$user = $this->getEntity($id);
$form = $this->createForm(UserType::class, $user, array(
'method' => 'PUT',
'em' => $this->getDoctrine()->getManager()
));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$response = new Response();
$response->setStatusCode(204);
$response->setContent('User saved!');
return $response;
}
return View::create($form, 400);
}
/**
* #Rest\View
* #Route("/users/{id}", requirements={"id": "\d+"})
* #Method("GET")
*/
public function getAction($id)
{
$user = $this->getEntity($id);
return array('user' => $user);
}
/**
* Get the User entity object by the given ID and return it
*
* #param $id
*
* #return User
*/
private function getEntity($id)
{
$user = $this->getDoctrine()->getRepository('AppBundle:User')->find($id);
if (!$user instanceof User) {
throw new NotFoundHttpException('User not found');
}
return $user;
}
And the json-object that I GET and PUT looks like this:
{
"user":
{
"id":1,
"address": {
"id":1,
"street":"Teststreet",
"number":"1",
"postalCode":"9999",
"city":"Citytest",
"country":"Countrytest"
}
}
}
my config.yml:
fos_rest:
param_fetcher_listener: true
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
format_listener:
rules:
path: ^/
fallback_format: json
prefer_extension: false
priorities: [json, xml]
body_converter:
enabled: false
validate: false
view:
view_response_listener: force
formats:
json: true
xml: true
templating_formats:
html: true
force_redirects:
html: true
failed_validation: HTTP_BAD_REQUEST
default_engine: twig
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
routing_loader:
default_format: json
serializer:
serialize_null: true
nelmio_api_doc: ~
jms_serializer:
metadata:
directories:
FOSUB:
namespace_prefix: "FOS\\UserBundle"
path: "%kernel.root_dir%/serializer/FOSUserBundle"
I have done something like that and I did it without any model transformer in the form type. Here is my code
UserType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('address', AddressType::class)
;
}
Address Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('street', AddressType::class)
->add('number', AddressType::class)
->add('postalCode', AddressType::class)
// ...
;
}
And the json object to put
{
"user":
{
"address": {
"street":"Teststreet",
"number":"1",
"postalCode":"9999",
"city":"Citytest",
"country":"Countrytest"
}
}
}

Generated Admin does not work with Generated “AUTO” Id field

Ok, this has to be a very newbie questions, such that nobody has asked about it :)
So my apologies for the question.
OK.
this is the entity:
<?php
// src/Alvent/LocalBundle/Entity/Note.php
namespace Alvent\LocalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Notes")
*/
class Note
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $Id;
/**
* #ORM\Column(type="text")
*/
protected $Note;
/**
* #ORM\Column(type="datetime", length=50)
*/
protected $Date;
//***************************************************************************************
//Getters and Setters
//***************************************************************************************
/**
* Get Id
*
* #return integer
*/
public function getId()
{
return $this->Id;
}
/**
* Set Note
*
* #param string $notes
* #return Organization
*/
public function setNote($note)
{
$this->Note = $note;
return $this;
}
/**
* Get Notes
*
* #return string
*/
public function getNote()
{
return $this->Note;
}
/**
* Set Date
*
* #param \DateTime $date
* #return Notes
*/
public function setDate($date)
{
$this->Date = $date;
return $this;
}
/**
* Get Date
*
* #return \DateTime
*/
public function getDate()
{
return $this->Date;
}
}
Ths is the generated admin for the entity:
<?php
namespace Alvent\LocalBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class NoteAdmin extends Admin
{
/**
* #param DatagridMapper $datagridMapper
*/
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('Id')
->add('Note')
->add('Date')
;
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('Id')
->add('Note')
->add('Date')
->add('_action', 'actions', array(
'actions' => array(
'show' => array(),
'edit' => array(),
'delete' => array(),
)
))
;
}
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('Id')
->add('Note')
->add('Date')
;
}
/**
* #param ShowMapper $showMapper
*/
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('Id')
->add('Note')
->add('Date')
;
}
}
The issue:
in the create form the ID field shows with no value, it does not let me save without inmput a value,a dn when i do, it does nto like it becuae i have not setId funtions (which i shouldnt).
Neither the property "Id" nor one of the methods "addId()"/"removeId()", "setId()", "id()", "__set()" or "__call()" exist and have public access in class "Alvent\LocalBundle\Entity\Note".
500 Internal Server Error - NoSuchPropertyException
I tries a lot of stuff like hide the Id field, remove it, etc. What am i doing wrong?

Resources