Duplicate when creating a blog entity using an existing gallery in sonata admin - gallery

I'm currently using Sonata Admin. I have a Blog entity that links to a sonata-media-bundle Gallery entity.
I created a Blog object B1 with gallery G1. This is OK
I then tried to create a Blog object B2 using the same gallery G1. Error for duplicates reasons.
The Blog Entity
/**
* Blog
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="ACME\BlogBundle\Entity\Repository\BlogRepository")
* #ORM\HasLifecycleCallbacks
*/
class Blog
{
...
/**
*
* #ORM\OneToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Gallery", orphanRemoval=true)
*/
private $images;
...
}
In my admin
/**
* #param \Sonata\AdminBundle\Form\FormMapper $formMapper
*
* #return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title', null, array('required' => true))
->add('author', null, array('required' => true))
->add('blog', null, array('required' => true))
->add('taxonomy', null, array('required' => true))
->end()
->with('Images')
->add('images', 'sonata_type_model', array('multiple' => false, 'required' => false))
->end()
->with('System Information', array('collapsed' => true))
->add('created')
->add('updated')
->add('published')
->end()
;
}
Anyone here can explain why I get the following error when trying to reuse an existing Gallery object?
Integrity constraint violation: 1062 Duplicate entry '1' for key 'UNIQ_6027FE7DD44F05E5'

Ok I found why.
The OneToOne relationship is creating a unicity constraint.
Replacing OneToOne by ManyToOne fixed it:
* #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Gallery")

Related

Symfony Form + EntityType Field + oTm > mTo < oTm relation = wrong type when saving

I have a tagging system set as OneToMany ManyToOne OneToMany like this:
ITEM OneToMany to TAGS
TAGS ManyToOne to TAG and ITEM
TAG OneToMany to TAGS
The ITEM form field to this relation is EntityType from TAG so I have a list of my available tags from the database to choose from
When I save my form I am getting Expected value of type "App\Entity\tags" for association field "App\Entity\Item#$tags", got "App\Entity\Tag" instead.
I don't understand because I am using EntityType so Doctrine should know this is a relation and automatically create the TAGS object ? How do I tell Symfony to automatically convert my TAG object to TAGS relation object ?
Am I supposed to do a CollectionType instead ? That seems inappropriate in this case...
here is my form TYPE:
->add('tags', EntityType::class, array(
'class' => \App\Entity\Tag::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('tag')
->orderBy('tag.name', 'ASC');
},
'expanded' => true ,
'multiple' => true,
))
ITEM ENTITY
/**
* #ORM\OneToMany(targetEntity="tags", mappedBy="item")
*/
private $tags;
TAGS ENTITY
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="Item", inversedBy="tags")
* #ORM\JoinColumn(name="item_id")
*/
private $item;
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="items")
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
*/
private $tag;
TAG ENTITY
/**
* #ORM\OneToMany(targetEntity="tags", mappedBy="tag")
*
*/
private $items;
you have to embed the second form as Collection Type then add allow_add if you want to add new tags to your entity ITEM:
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
));
check the docs here

SonataAdmin form EntityType not required

My entity has an optional relationship (nullable=true) to other entity.
But When I use required = false The form created by Sonata has a <select> with only all my entities, not a blank value.
With a classic symfony form, required = false allow to select no entity
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('otherEntity', EntityType::class, [
'class' => OtherEntity::class,
'required' => false,
])
;
}
Do you know why?
First, check if your entity allows for null value on your relationship. In entity something like (note JoinColumn):
/**
* #var OtherEntity
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\OtherEntity")
* #ORM\JoinColumn(nullable=true)
*/
private $otherEntity;
Second add placeholder option to your form mapping:
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('otherEntity', EntityType::class, [
'class' => OtherEntity::class,
'required' => false,
// This is what sonata requires
'placeholder' => 'Please select entity'
])
;
}
I've just found that Sonata is adding a little cross to delete the current selected relationship
It's so small that I didn't saw it last night...
Thanks anyways for the answer M. Kebza!

ManyToMany association. How to add owner entities to inverse entities?

I have 2 entities and manytomany association:
/**
* #ORM\Entity
* #ORM\Table(name="m2m_table1")
*/
class Table1
{
/**
* #ORM\Id
* #ORM\Column(type="integer", nullable=false, options={"unsigned":true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Table2", inversedBy="table1", fetch="LAZY", cascade="all")
* #ORM\JoinTable(name="m2m_links",
* joinColumns={#ORM\JoinColumn(name="table1_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="table2_id", referencedColumnName="id")}
* )
*/
private $table2;
...
}
/**
* #ORM\Entity
* #ORM\Table(name="m2m_table2")
*/
class Table2
{
/**
* #ORM\Id
* #ORM\Column(type="integer", nullable=false, options={"unsigned":true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Table1", mappedBy="table2", fetch="LAZY", cascade="all")
*/
private $table1;
...
}
And I want to have opportunity to add inverse entities to owner entities and vice versa. I can add inverse entities to owner entities, but I can't add owner entities to inverse entities.
$table1 = $em->find('XxxM2mBundle:Table1', 1);
$table2 = $em->find('XxxM2mBundle:Table2', 1);
$table2->addTable1($table1);
$em->flush($table2);
Link is not added. Example is simplified, in fact there are 2 forms, 1-st to adjust links for Table1 and 2-nd to adjust links for Table2. I use Symfony\Bridge\Doctrine\Form\Type\EntityType for it. 2-nd form doesn't work with this configuration.
Form class:
namespace Xxx\M2mBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;
use Symfony\Bridge\Doctrine\Form\Type as DoctrineFormType;
class Table2 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('post')
->add('name', FormType\TextType::class, [
'required' => true,
'label' => 'Name',
])
->add('table1', DoctrineFormType\EntityType::class, [
'required' => false,
'expanded' => true,
'multiple' => true,
'class' => 'Xxx\\M2mBundle\\Entity\\Table1',
'choice_label' => 'name',
'label' => 'Table1',
])
->add('save', FormType\SubmitType::class, [
'label' => 'Save',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Xxx\\M2mBundle\\Entity\\Table2',
]);
}
}
I have changed association for Table2 to:
/**
* #ORM\ManyToMany(targetEntity="Table1", inversedBy="table2", fetch="LAZY", cascade="all")
* #ORM\JoinTable(name="m2m_links",
* joinColumns={#ORM\JoinColumn(name="table2_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="table1_id", referencedColumnName="id")}
* )
*/
private $table1;
It helped, but I think that it is not good decision, and now I get error The table with name 'm2m_links' already exists. when I try to update schema ./bin/console doctrine:schema:update --dump-sql --force.
You need to understand about Owning and Inverse side of the association.
This is how you are able to work smoothly with ManyToMany associations:
generate the two enities you need with the CLI e.g.
bin/console doctrine:generate:entity
go to doctrine association mapping for ManyToMany bidirectional
add the annotations and the constructors to your Entities. Do not forget to write ORM\ before your annotations. Change Entity and table names to your own situation.
extend the ManyToMany annotation at the Owner side or both sides of your relationship with the cascade={"persist"} option. e.g.
#ORM\ManyToMany(targetEntity="Tag", inversedBy="images", cascade={"persist"})
automatically generate needed methods and update the schema with the CLI.
bin/console doctrine:generate:entities AppBundle
bin/console doctrine:schema:update --force
add a __toString() method to both entities.
If you want to be able to add owner-entities on inversed entity you could make a small change in the add and remove method of the inversed entity as the example below shows.
example:
# Tag entity
public function addImage(\AppBundle\Entity\Image $image)
{
$image->addTag($this); // new rule!
$this->images[] = $image;
return $this;
}
public function removeImage(\AppBundle\Entity\Image $image)
{
$image->removeTag($this); // new rule!
$this->images->removeElement($image);
}
add 'by_reference' => false to the entity form field options
example:
class TagType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('images', EntityType::class, array(
'multiple' => true,
'expanded' => true,
'class' => 'AppBundle:Image',
'by_reference' => false # HERE!
))
;
}
// ..

Construct or validation does not work when I embed an entity with sonata_type_admin

I have an entity 'Employee' and I embed an entity 'User' with 'sonata_type_admin' form type (one to one relation).
When I create a new Employee object I want to set default values to User object, but User __construct() or getNewInstance() method is never called.
I also noticed that user validation constraints don't work when I edit an Employee (for instance, if I edit an employee and I try to set an username that already exists, validation constraint is not displayed on username field). If I edit an user, __construct(), getNewInstance() and validation constraints works fine.
What can I do?
I extended my user entity from SonataUserBundle (FOSUserBundle).
//User.orm.xml
...
<entity name="Application\Sonata\UserBundle\Entity\User" table="fos_user_user">
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<one-to-one field="employee" target-entity="AppBundle\Entity\Employee" mapped-by="user" />
</entity>
</doctrine-mapping>
My Employee entity is in AppBundle.
//Employee.php
namespace AppBundle\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Doctrine\ORM\Mapping as ORM;
/**
* Session
*
* #ORM\Table(name="nup_employee")
* #ORM\Entity(repositoryClass="AppBundle\Entity\EmployeeRepository")
*/
class Employee
{
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
use TimestampableEntity;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(
* targetEntity="Application\Sonata\UserBundle\Entity\User",
* inversedBy="employee",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $user;
...
My configureFormFields.
//UserAdmin.php
...
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('username')
->add('email')
->add('plainPassword', 'text', array(
'required' => (!$this->getSubject() || is_null($this->getSubject()->getId()))
))
->end()
;
$formMapper
->with('Security')
->add('enabled', null, array('required' => false))
->add('locked', null, array('required' => false))
->add('token', null, array('required' => false))
->add('twoStepVerificationCode', null, array('required' => false))
->end()
;
}
...
Composer.json:
...
"symfony/symfony": "2.7.*",
...
"friendsofsymfony/user-bundle": "~1.3",
"sonata-project/admin-bundle": "2.3.*",
"sonata-project/doctrine-orm-admin-bundle": "2.3.*",
"sonata-project/user-bundle": "^2.2"
Finally, I found the solution (#anegrea thanks for your help).
Sonata doesn't call User construct on Employee create because it doesn't know if it's nullable or not (it's different if you create a user because the user is required). if you want to call User construct on Employee create you have to put data on related field.
class EmployeeAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
{
$data = new User();
if ($this->getSubject()->getUser()) {
$data = $this->getSubject()->getUser();
}
$formMapper
->with('General', array('class' => 'col-md-6'))
->add('name')
->add('surnames')
->end()
->with('Access', array('class' => 'col-md-6'))
->add('user', 'sonata_type_admin',
array(
'data' => $data,
'label' => false
),
array(
'admin_code' => 'sonata.user.admin.user',
'edit' => 'inline'
)
)
->end()
;
}
...
}
On the other hand, to show errors on User fields when I create or edit an Employee, I had to add Valid constraint on user mapping in Employee entity and add validation groups related to FOSUserBundle in EmployeeAdmin.
class Employee
{
...
/**
* #ORM\OneToOne(
* targetEntity="Application\Sonata\UserBundle\Entity\User",
* inversedBy="employee",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* #Assert\Valid
*/
private $user;
...
}
class EmployeeAdmin extends Admin
{
public function getFormBuilder()
{
$this->formOptions['data_class'] = $this->getClass();
$options = $this->formOptions;
$options['validation_groups'] = array('Default');
array_push($options['validation_groups'], (!$this->getSubject() || is_null($this->getSubject()->getId())) ? 'Registration' : 'Profile');
$formBuilder = $this->getFormContractor()->getFormBuilder($this->getUniqid(), $options);
$this->defineFormBuilder($formBuilder);
return $formBuilder;
}
...
}
For the validation you have to add the constraint on the Employee entity for the $user property to be Valid.
http://symfony.com/doc/current/reference/constraints/Valid.html
If there is a problem with persisting also, it might be because you are having the bidirectional relation that in you employee admin, on preUpdate/prePersist (these are funcitons the admin knows about) you must also link the employee to the user by doing something like:
public function prePersist($object) {
$object->getUser()->setEmployee($object);
}
Or, you can try to have only the user mapped on the employee (without the inversedBy directve).

Sonata admin: Display entity grandchildren in sonata_type_collection

I have the following entities:
// AppBundle/Entity/Contacts.php
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="Nominations", mappedBy="contact")
**/
private $nominations;
// AppBundle/Entity/Nominations.php
/**
* #var Contacts
*
* #ORM\ManyToOne(targetEntity="Contacts", inversedBy="nominations")
**/
private $contact;
/**
* #var Votes
*
* #ORM\OneToMany(targetEntity="Votes", mappedBy="nomination")
**/
private $votes;
// AppBundle/Entity/Votes.php
/**
* #var Nominations
*
* #ORM\ManyToOne(targetEntity="Nominations", inversedBy="votes")
**/
private $nomination;
With the following method in the Contacts entity, which loops through the Nomination records and stuffs them in an ArrayCollection that I want to display in ContactsAdmin:
// AppBundle/Entity/Contacts.php
/**
* Get Votes
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getVotes()
{
$return = array();
foreach ($this->getNominations() as $nom) {
$return = array_merge($return, $nom->getVotes()->toArray());
}
return new ArrayCollection($return);
}
// AppBundle/Admin/ContactsAdmin.php
$formMapper
->add('votes', 'sonata_type_collection', array(
'required' => false,
'by_reference' => false
), array(
'data_class' => 'AppBundle\Entity\Votes',
'admin_code' => 'app.admin.votes', //this is a VotesAdmin service which works fine on its own
'edit' => 'inline',
'inline' => 'table',
));
But I'm getting this error:
The current field votes is not linked to an admin. Please create one for the target entity : ``
Note the entity name there is blank. I've tried all sorts of different combinations of options but I keep coming back to this same problem: Sonata can't figure out what the entity class and related admin is, even though I'm specifying it.

Resources