I'm using A2lix Translation Form Bundle and Doctrine Behaviors Translatable in a project where I have two entities: company and files. Company has some translatable fields so I have a CompanyTranslations Entity for that. One company can have one file so Company and file are mapped with an OneToOne unidirectional reference. The company file is translatable so the property is in the CompanyTranslation file.
CompanyTranslation:
class CompanyTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #ORM\OneToOne(targetEntity="File", cascade={"persist"})
* #ORM\JoinColumn(name="translatable_file_id", referencedColumnName="id")
* #Assert\Valid()
* #Assert\Type(type="MyApp\CoreBundle\Entity\File")
**/
private $translatableFile;
/**
* Set translatableFile
*
* #param $translatableFile
*/
public function setTranslatableFile(File $translatableFile = null)
{
$this->translatableFile = $translatableFile;
}
/**
* Get translatableFile
*
* #return $translatableFile
*/
public function getTranslatableFile()
{
return $this->translatableFile;
}
}
File:
class File
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $filePath;
/**
* #Assert\File()
*/
private $file;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set filePath
*
* #param string $filePath
*/
public function setFilePath($filePath)
{
$this->filePath = $filePath;
}
/**
* Get filePath
*
* #return string
*/
public function getFilePath()
{
return $this->filePath;
}
/**
* Set file
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
}
File Form Type:
class FileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file', array(
'label' => false
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyApp\CoreBundle\Entity\File'
));
}
public function getName()
{
return 'file_form';
}
}
Company Form Type:
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('translations', 'a2lix_translationsForms', array(
'locales' => $this->languages,
'form_type' => new FileType(),
'form_options' => array(
'data_class' => 'MyApp\CoreBundle\Entity\File',
'required' => false,
'validation_groups' => array('file_upload')
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
'data_class' => 'MyApp\CoreBundle\Entity\Company'
));
}
}
The error is this one:
The form's view data is expected to be an instance of class MyApp\CoreBundle\Entity\File, but is an instance of class MyApp\CoreBundle\Entity\CompanyTranslation. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class MyApp\CoreBundle\Entity\CompanyTranslation to an instance of MyApp\CoreBundle\Entity\File.
I already set the data_class of the File Type Form and the data_class of the field to null but also to MyApp\CoreBundle\Entity\File. Both send me errors. I don't know what's happening.
Could anyone help?
Thanks!
Related
I have 2 "simple" entities, and i want to do the classical form embedding
but i have this error : "Neither the property "itemcode" nor one of the methods "getItemcode()", "itemcode()", "isItemcode()", "hasItemcode()", "__get()" exist and have public access in class "NWA\ItemSelectorBundle\Entity\ItemSelector"."
I've seen many posts with this error, but none provided the solution
In the entities i have getItemCode() but why would it be public ?
What is wrong with my construction?
Thank you in advance
Here are my entities (parts relevant to the properties at fault)
class ItemSelector
{
/**
* #var Items[]
*
* #ORM\OneToMany(targetEntity="NWA\ItemSelectorBundle\Entity\Item", mappedBy="itemselector", cascade={"all"})
*/
protected $items;
/**
* Class constructor
*/
public function __construct()
{
$this->items = new ArrayCollection();
}
/**
* Add item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*
* #return ItemSelector
*/
public function addItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items[] = $item;
//$item->setItemselector($this);
return $this;
}
/**
* Remove item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*/
public function removeItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items->removeElement($item);
}
/**
* Get items
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getItems()
{
return $this->items;
}
}
and
class Item
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="itemcode", type="string", length=255)
*/
protected $itemcode;
/**
* #var ItemSelector
*
* #ORM\ManyToOne(targetEntity="NWA\ItemSelectorBundle\Entity\ItemSelector", inversedBy="items")
* #ORM\JoinColumn(name="itemselector_id", referencedColumnName="id")
*/
protected $itemselector;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set itemcode
*
* #param string $itemcode
*
* #return Item
*/
public function setItemcode($itemcode)
{
$this->itemcode = $itemcode;
return $this;
}
/**
* Get itemcode
*
* #return string
*/
public function getItemcode()
{
return $this->itemcode;
}
/**
* Set itemselector
*
* #param \NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector
*
* #return Item
*/
public function setItemselector(\NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector = null)
{
$this->itemselector = $itemselector;
return $this;
}
/**
* Get itemselector
*
* #return \NWA\ItemSelectorBundle\Entity\ItemSelector
*/
public function getItemselector()
{
return $this->itemselector;
}
}
Then the Form constructors
class ItemSelectorType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\ItemSelector',
'translation_domain' => 'resource'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselector';
}
}
and
class ItemType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'text', array(
'label' => 'Code'
)
);
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\Item'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselectorbundle_item';
}
}
And finally the call in the Controller
public function chooseAction(Request $request, ItemSelector $itemSelector)
{
$form = $this->get('form.factory')
->create(new ItemSelectorType(), $itemSelector);
$form->handleRequest($request);
if ($form->isValid()) {
}
return array(
'_resource' => $itemSelector,
'form' => $form->createView(),
);
}
Maybe you need to rename your field name itemcode to items in ItemSelectorType.
->add(
'items', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);
I'm trying to customize a selection list's text while using the entity's ID. This is because I want the list options to be specific to the authenticated user. The database text values are Full Name, By City and State, and Anonymous, but I want it to actually display the user's full name (John Smith), User in Denver, CO, and Anonymous. I'm attempting to use a view data transformer to achieve this, but with no luck. I'd rather not use Javascript to achieve this if possible.
Here's my main form type:
<?php
namespace Members\MessagesBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContext;
class MessageType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('viewability', 'viewability_entity', array(
'class' => 'MessagesBundle:Viewability',
'property' => 'name',
'required' => true,
))
->add('body', new MessageBodyType())
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Members\MessagesBundle\Entity\Message',
));
}
/**
* #return string
*/
public function getName()
{
return 'members_messages_message';
}
}
Here's my custom form type for Viewability (the entity which I would like to transform):
<?php
namespace Members\MessagesBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContext;
use Members\MessagesBundle\Form\DataTransformer\MessageNameTransformer;
class ViewabilityType extends AbstractType
{
private $context;
/**
* #param SecurityContext $context
*/
public function __construct(SecurityContext $context)
{
$this->context = $context;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new MessageNameTransformer($this->context);
$builder->addViewTransformer($transformer);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected issue does not exist',
));
}
/**
* #return string
*/
public function getParent()
{
return 'entity';
}
/**
* #return string
*/
public function getName()
{
return 'viewability_entity';
}
}
Here's my service which defines the Viewability Type:
members.messages.form.type.viewability_entity:
class: Members\MessagesBundle\Form\ViewabilityType
tags:
- { name: form.type, alias: viewability_entity }
arguments: [#security.context]
Here's my Viewability Entity:
<?php
namespace Members\MessagesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Viewability
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function __construct()
{
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
}
Finally, here's my data transformer:
<?php
namespace Members\MessagesBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Members\MessagesBundle\Entity\Viewability;
use Symfony\Component\Security\Core\SecurityContext;
class MessageNameTransformer implements DataTransformerInterface
{
private $user;
/**
* #param SecurityContext $context
*/
public function __construct(SecurityContext $context)
{
$this->user = $context->getToken()->getUser();
}
/**
* #param Viewability|null $viewability
* #return string
*/
public function transform($viewability)
{
if (null === $viewability) {
return '';
}
if($viewability === 'Full Name')
return sprintf('%s %s', $this->user->getInfo()->getFirstName(), $this->user->getInfo()->getLastName());
if($viewability === 2)
return sprintf('Lawyer in %s, %s', $this->user->getInfo()->getAddress()->getCity(), $this->user->getInfo()->getAddress()->getState());
if($viewability === 3)
return 'Anonymous';
}
/**
* #param Viewability $viewability
* #return Viewability
*/
public function reverseTransform($viewability)
{
return $viewability;
}
}
The data passed into transform() always seems to be null or "" (empty string).
Thanks for any help.
So I ended up taking a different approach to solving this. Originally I was trying to transform data coming from an entity. Fortunately this entity didn't really need to be a database entity after all and a simple choice type sufficed. This doesn't solve the specific issue of transforming an entity list, but it allows me to customize the drop down list values.
The viewability entity was removed and the relationship in the Message entity was changed to an integer field.
My main type is now as follows:
class MessageType extends AbstractType
{
private $user;
public function __construct($user)
{
$this->user = $user;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('body', new MessageBodyType())
->add('viewability', 'choice', array(
'choices' => array(
1 => $user->getFirstName(),
2 => $user->getAddress()->getCity(),
3 => 'Anonymous',
),
'multiple' => false,
'label' => 'Send Message As',
'data' => 0,
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Members\MessagesBundle\Entity\Message',
));
}
/**
* #return string
*/
public function getName()
{
return 'members_messages_message';
}
}
I am making an invoicing system with an embedded form. A form where you can add a date, choose a customer company from a dropdown list (-> customer is an entity) and add details to the invoice item (-> details is also an entity, with properties like price, amount,...) - with javascript. This works just fine, but at saving the form I get an error.
I have 3 entities: InvoicingCustomer, InvoiceItem, InvoiceItemDetail.
(Sorry; this is going to be a long post)
InvoicingCustomer.php (with properties like street, address,...) =
/**
* #ORM\Table(name="invoicing_customer")
* #ORM\Entity
*/
class InvoicingCustomer
{
/**
* #ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", mappedBy="customer")
*/
private $invoice;
public function __construct()
{ $this->invoice = new ArrayCollection();}
public function getInvoice()
{ return $this->invoice; }
public function getAllInvoices()
{
$invoices = $this->getInvoice()->toArray();
return $invoices;
}
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(name="company_name", type="string", length=50, nullable=false)
* #Assert\NotBlank()
*/
private $companyName;
//idem for next properties:
private $firstName;
private $lastName;
private $street;
private $number;
private $postalCode;
private $city;
}
And off course the getters and setters.
InvoiceItem.php =
/**
* #ORM\Table(name="invoicing_invoice_item")
* #ORM\Entity
*/
class InvoiceItem
{
/**
* #ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItemDetail", mappedBy="item_nr", cascade={"ALL"}, fetch="EAGER", orphanRemoval=true)
*/
private $item_detail;
public function __construct()
{ $this->item_detail = new ArrayCollection(); }
/**
* #return mixed
*/
public function getItemDetail()
{ return $this->item_detail; }
/**
* #param mixed $item_detail
*/
public function setItemDetail(Collection $item_detail)
{
foreach ($item_detail as $v)
{
if (is_null($v->getId()))
{
$v->getId($this);
}
}
$this->item_detail = $item_detail;
}
public function addDetail(InvoiceItemDetail $detail){
$detail->$this->setItemDetail($this);
$this->detail[] = $detail;
return $this;
}
public function removeDetail(InvoiceItemDetail $detail){
//
}
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \DateTime
* #ORM\Column(name="date", type="date", nullable=false)
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="Invoicing\CustomerBundle\Entity\InvoicingCustomer", inversedBy="invoice")
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false)
* #Assert\Type(type="Invoicing\CustomerBundle\Entity\InvoicingCustomer")
* #Assert\Valid()
*
*/
private $customer;
// here also getters and setters
}
InvoiceItemDetail.php =
/**
* #ORM\Table(name="invoicing_invoice_itemdetail")
* #ORM\Entity
*/
class InvoiceItemDetail
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="description", type="text", length=200, nullable=false)
*/
private $description;
/**
* #var string
* #ORM\Column(name="price", type="decimal", precision=10, scale=0, nullable=false)
*/
private $price;
/**
* #var integer
* #ORM\Column(name="amount", type="decimal", precision=10, scale=0, nullable=false)
*/
private $amount;
/**
* #ORM\ManyToOne(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", inversedBy="item_detail" )
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false, name="item_nr_id", referencedColumnName="id")
* #Assert\Type(type="Invoicing\InvoicingBundle\Entity\InvoiceItem")
* #Assert\Valid()
*/
private $item_nr;
// + getters and setters
}
Then, I got the types.
InvoiceItemType.php =
class InvoiceItemType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', 'date', array(
'format' => 'dd-MM-yyyy',
'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day'),
'years' => range(date('Y') -1, date('Y')),
))
->add('customer', null, array(
'empty_value' => 'Choose a company',
'label' => 'Company',
'required' => true,
))
->add('item_detail', 'collection', array(
'type' => new InvoiceItemDetailType(),
'allow_add' => true,
'constraints' => new NotBlank(),
'by_reference' => false,
));
}
public function getName()
{ return 'invoiceitem'; }
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItem',
));
}
}
InvoicingCustomerType.php =
class InvoicingCustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('companyName', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\CustomerBundle\Entity\InvoicingCustomer',
));
}
public function getName()
{ return 'customer'; }
}
InvoiceItemDetailType.php =
class InvoiceItemDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'text')
->add('price', 'number', array(
'label' => 'Price - €',
))
->add('amount', 'number');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
));
}
public function getName()
{ return 'detail'; }
}
In my controller I have this (InvoiceItemController.php):
/** InvoiceItem controller */
class InvoiceItemController extends Controller
{
/**
* Creates a new invoiceitem entity.
*/
public function createAction(Request $request)
{
$entity = new InvoiceItem();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
// hack to work around handleRequest not using class methods to populate data
foreach($entity->getItemDetail() as $detail){
foreach($detail as $i){
// if i didn't made a second loop, I get an error: "object could not be converted to string..."
$i->this->setItemNr($entity);
$em->persist($i);
}
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('invoiceitem_show', array('id' => $entity->getId())));
}
return $this->render('InvoicingBundle:InvoiceItem:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
}
In my twig it's just like:
{% block body -%}
<h1>Invoice item creation</h1>
{{ form(form) }}
{% endblock %}
Everything in the form is displayed good (and with javascript I can add several details to one invoice item). But when I submit the form, symfony throws an error:
An exception occurred while executing 'INSERT INTO invoicing_invoice_itemdetail (description, price, amount, item_nr_id) VALUES (?, ?, ?, ?)' with params ["test", 300, 1, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'item_nr_id' cannot be null
I searched around on the docs of symfony (http://symfony.com/doc/current/cookbook/form/form_collections.html ) and on stackoverflow (for example: Saving embedded collections ), but none of these give me the right solution.
I know this is a long post: I am sorry. But I don't know how to sort this problem out (+ I am new in learning symfony2 & new in asking questions here).
I believe your problem is in InvoiceItem entity. Try to create method addItemDetail (or maybe addInvoiceItemDetail) instead addDetail. You can also delete method setItemDetail and maybe you will see good explanation what method is Symfony looking for.
public function addItemDetail(InvoiceItemDetail $detail){
$detail->setItemNr($this);
$this->item_detail[] = $detail;
return $this;
}
And delete the hack from controller.
// hack to work around handleRequest not using class methods to populate data
foreach($entity->getItemDetail() as $detail){
foreach($detail as $i){
// if i didn't made a second loop, I get an error: "object could not be converted to string..."
$i->this->setItemNr($entity);
$em->persist($i);
}
}
I hope it helps but it is a little hard to answer this question without live code.
The concept of Symfony forms is that for relations you can also specify the related entity in the form type.
In your case, you didn't add InvoiceItemType to the Type ItemInvoiceDetail:
I would expect the following code in your InvoiceDetailType:
class InvoiceItemDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'text')
->add('invoice_item', 'invoice_item', array('error_bubbling' => true, 'mapped' => true))
->add('price', 'number', array(
'label' => 'Price - €',
))
->add('amount', 'number');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
));
}
public function getName()
{ return 'detail'; }
Notice i set the type of the form element as invoice_item.
you can achieve that by defining it as a service:
service_name:
class: invoiceitemfullclasspath
arguments: []
tags:
- { name: form.type, alias: invoice_item }
I am creating a MotorsAds entity to which I am linking with MotorsAdsFile entity. For each MotorsAds, we could attach many MotorsAdsFile.
My objective is creating a form for MotorsAds which allows adding MotorsAdsFile just through a click on a button. Here, I am trying to implement the embeded forms.
My problem is that I got that error:
An exception occurred while executing 'INSERT INTO MotorsAdsFile (filename, motors_id) VALUES (?, ?)' with params ["5493b613839d7_2012-07-02 22.06.00.jpg", null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'motors_id' cannot be null
Probably, the MotorsAds object is not persisted yet: that's why the Column 'motors_id' is null. Could you help in this issue?
Have I missed something?
1. Definition of the entities
MotorsAdsFile
<?php
namespace Minn\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class MotorsAdsFile {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #Assert\File(
* maxSize="5M",
* mimeTypes={"image/png", "image/jpeg"}
* )
* #Vich\UploadableField(mapping="motors_files", fileNameProperty="filename")
*/
protected $file;
/**
* #ORM\Column(type="string", length=255, name="filename")
* #var string $filename
*/
protected $filename;
/**
* #ORM\ManyToOne(targetEntity="Minn\AdsBundle\Entity\MotorsAds", inversedBy="files")
* #ORM\JoinColumn(nullable=false,onDelete="CASCADE")
*/
private $motors;
/**
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $file
*/
public function setFile(File $file) {
$this->file = $file;
}
/**
* #return File
*/
public function getFile() {
return $this->file;
}
/**
* #param string $filename
*/
public function setFilename($filename) {
$this->filename = $filename;
}
/**
* #return string
*/
public function getFilename() {
return $this->filename;
}
/**
* Set motors
*
* #param \Minn\AdsBundle\Entity\MotorsAds $motors
* #return MotorsAds
*/
public function setMotors(\Minn\AdsBundle\Entity\MotorsAds $motors) {
$this->motors = $motors;
return $this;
}
/**
* Get motors
*
* #return \Minn\AdsBundle\Entity\MotorsAds
*/
public function getMotors() {
return $this->motors;
}
}
MotorsAds
<?php
namespace Minn\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
/**
* MotorsAds
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Minn\AdsBundle\Entity\MotorsAdsRepository")
* #ORM\HasLifecycleCallbacks()
*/
class MotorsAds {
// ...
/**
* #ORM\OneToMany(targetEntity="Minn\AdsBundle\Entity\MotorsAdsFile",
* mappedBy="motors",
* cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $files;
public function __construct() {
$this->files = new \Doctrine\Common\Collections\ArrayCollection();
$this->favorites = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getFiles() {
return $this->files;
}
public function addFile(MotorsAdsFile $file) {
$this->files[] = $file;
return $this;
}
public function removeFile(MotorsAdsFile $file) {
$this->files->removeElement($file);
}
// ...
}
2. The forms
MotorsAdsFileType
class MotorsAdsFileType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('file', 'file',array('required' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Minn\AdsBundle\Entity\MotorsAdsFile'
));
}
public function getName() {
return 'MotorsAdsFiletype';
}
}
MotorsAdsType
class MotorsAdsType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
// ...some code here
$builder->add('files','collection',array('type'=> new MotorsAdsFileType(),
'allow_add'=> true,
'allow_delete' => true,
'by_reference' => true));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Minn\AdsBundle\Entity\MotorsAds'
));
}
public function getName() {
return 'MotorsAdstype';
}
}
I
I found the solution...
Two things have to be done:
(1)
public function buildForm(FormBuilderInterface $builder, array $options) {
// ...some code here
$builder->add('files','collection',array('type'=> new MotorsAdsFileType(),
'allow_add'=> true,
'allow_delete' => true,
'by_reference' => false)); // it has to be set to false and not true
}
(2)
public function addFile(MotorsAdsFile $file) {
$file->setMotors($this); // you have to add this line too
$this->files[] = $file;
return $this;
}
I have document called aboutMe
and it has another embedded document called projects to add many projects (prototype)
The projects has project name and image for the project.
i created a formType for aboutMe document and i embedded the project form inside the aboutMe form to be able to add many projects prototype.
The problem is updating the project->image when the user didn't change the old project image.
doctrine updating the old embedded project document with a null image.
I Need to keep the old image name if the user didn't upload a new one
/**
* #MongoDB\Document
* #MongoDB\HasLifecycleCallbacks
*/
class AboutMeIndex {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var array
*
* #MongoDB\EmbedMany(targetDocument="AboutMeProjects", strategy="set")
*/
protected $projects = array();
public function __construct()
{
$this->projects = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Remove project
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project
*/
public function removeProject(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project)
{
$this->projects->removeElement($project);
}
/**
* Get projects
*
* #return Doctrine\Common\Collections\Collection $projects
*/
public function getProjects()
{
return $this->projects;
}
/**
* Add certification
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification
*/
public function addCertification(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification)
{
$this->certifications[] = $certification;
}
//=================================================================================//
public function fileGetter($file){
if(method_exists($this, 'get' . ucfirst($file))) {
return call_user_func(array($this, 'get' . ucfirst($file)));
}
else {
throw new \Exception("Couldn't Find Method name get" . ucfirst($file));
}
}
protected function getUploadRootDir($uploadDir)
{
return __DIR__.'/../../../../../../web/uploads/'.$this->getUploadDir($uploadDir);
}
protected function getUploadDir($uploadDir)
{
return $uploadDir;
}
public function uploadEmbeddedPhotos($file, $uploadDir)
{
if (null === $this->fileGetter($file)) {
return;
}
foreach ($this->fileGetter($file) as $galleryPhoto){
$pictureName = uniqid().'.'.$galleryPhoto->getImage()->guessExtension();
$galleryPhoto->getImage()->move($this->getUploadRootDir($uploadDir),$pictureName);
$this->path = $galleryPhoto->getImage()->getClientOriginalName();
$galleryPhoto->setImage($pictureName);
}
}
public function deleteImage($image, $uploadDir){
#unlink($this->getUploadRootDir($uploadDir).$image);
}
//=================================================================================//
/**
* #MongoDB\EmbeddedDocument
*
*/
class AboutMeProjects {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var string
*
* #MongoDB\String
*/
protected $projectName;
/**
* #var string
*
* #Assert\Image(
* maxSize = "20000k",
* mimeTypes = {"image/gif", "image/jpeg", "image/png"},
* mimeTypesMessage = "Please upload a valid picture"
* )
* #Assert\Regex(
* pattern="/[a-zA-Z0-9]+/",
* match=true,
* message="Special characters are not allowed"
* )
*
* #MongoDB\String
*/
protected $image;
/**
* #var string
*
* #MongoDB\String
*/
protected $desc;
/**
* Get id
*
* #return int_id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set projectName
*
* #param string $projectName
* #return self
*/
public function setProjectName($projectName)
{
$this->projectName = $projectName;
return $this;
}
/**
* Get projectName
*
* #return string $projectName
*/
public function getProjectName()
{
return $this->projectName;
}
/**
* Set image
*
* #param string $image
* #return self
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string $image
*/
public function getImage()
{
return $this->image;
}
/**
* Set desc
*
* #param string $desc
* #return self
*/
public function setDesc($desc)
{
$this->desc = $desc;
return $this;
}
/**
* Get desc
*
* #return string $desc
*/
public function getDesc()
{
return $this->desc;
}
}
class AboutMeIndexType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName')
->add('projects', 'collection', array(
'type' => new ProjectsType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeIndex'
));
}
/**
* #return string
*/
public function getName()
{
return 'AbourMe';
}
}
class ProjectsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('projectName','text',array('attr'=> array('class'=>'form-control', 'placeholder' => 'Project name') ))
->add('image','file',array('data_class' => null,'attr'=> array('class'=>'form-control col-lg-2 file-inputs') ))
->add('desc','textarea',array('attr'=> array('class'=>'form-control', 'data-provide' => 'markdown', 'placeholder' => 'Description') ))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeProjects'
));
}
/**
* #return string
*/
public function getName()
{
return 'ProjectsType';
}
}
And here's the controller that i want to be able to keep the old image
/**
* Edits an existing aboutMeIndex document.
*
* #Route("/profile/about-me/update", name="profile_about_me_update")
* #Method("PUT")
* #Template()
*/
public function updateAction(Request $request)
{
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$user = $this->getUser();
$entity = $dm->getRepository('AboutMeIndex')->findOneBy(array('user.$id' => (int)$user->getId()));
if (!$entity) {
throw $this->createNotFoundException('Unable to find entity Document.');
}
$editForm = $this->createForm(new AboutMeIndexType(), $entity);
$editForm->submit($request);
if ($editForm->isValid()) {
if($entity->getProjects()->getImage() is newImage){
$entity->uploadEmbeddedPhotos('projects', 'profile/aboutMe/');
}else{
// Keep the old Image
}
$dm->persist($entity);
$dm->flush();
}
}