Override Method or EventListener: stop creation process and show warning just the first time in EasyAdmin? - symfony

I am using EasyAdmin in my SF 3.3 project but I need to achieve something different from how EasyAdmin has been built for. Take a look at the following picture:
As you might notice a user can be in more than one GroupingRole. Having that information the challenge is:
Check if the user has been assigned to any other GroupingRole
If the criteria meets the condition then show a warning message saying "The user A is already assigned to GroupingRole A" and prevent the record to be created. (this message could be in a popup, a javascript alert or an alert from Bootstrap - since EA already uses it)
When the admin click once again on "Save changes" the record should be created.
What I want to achieve with this approach is to alert the admin that the user is already to any other group but not stop him for create the record.
I have achieve some part of it already by override the prePersist method for just that entity (see below):
class AdminController extends BaseAdminController
{
/**
* Check if the users has been assigned to any group
*/
protected function prePersistGroupingRoleEntity($entity)
{
$usersToGroupRoleEntities = $this->em->getRepository('CommonBundle:UsersToGroupRole')->findAll();
$usersToGroupRole = [];
/** #var UsersToGroupRole $groupRole */
foreach ($usersToGroupRoleEntities as $groupRole) {
$usersToGroupRole[$groupRole->getGroupingRoleId()][] = $groupRole->getUsersId();
}
$usersInGroup = [];
/** #var Users $userEntity */
foreach ($entity->getUsersInGroup() as $userEntity) {
foreach ($usersToGroupRole as $group => $users) {
if (\in_array($userEntity->getId(), $users, true)) {
$usersInGroup[$group][] = $userEntity->getId();
}
}
}
$groupingRoleEnt = $this->em->getRepository('CommonBundle:GroupingRole');
$usersEnt = $this->em->getRepository('CommonBundle:Users');
$message = [];
foreach ($usersInGroup as $group => $user) {
foreach($user as $usr) {
$message[] = sprintf(
'The user %s already exists in %s group!',
$usersEnt->find($usr)->getEmail(),
$groupingRoleEnt->find($group)->getName()
);
}
}
}
}
What I don't know is how to stop the record to be created and instead show the warning just the first time the button is clicked because the second time and having the warning in place I should allow to create the record.
Can any give me some ideas and/or suggestions?
UPDATE: adding entities information
In addition to the code displayed above here is the entities involved in such process:
/**
* #ORM\Entity
* #ORM\Table(name="grouping_role")
*/
class GroupingRole
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer",unique=true,nullable=false)
* #ORM\GeneratedValue
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="role_name", type="string", nullable=false)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="role_description", type="string", nullable=false)
*/
private $description;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Schneider\QuoteBundle\Entity\Distributor", inversedBy="groupingRole")
* #ORM\JoinTable(name="grouping_to_role",
* joinColumns={
* #ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="DistributorID", referencedColumnName="DistributorID", nullable=false)
* }
* )
*
* #Assert\Count(
* min = 1,
* minMessage = "You must select at least one Distributor"
* )
*/
private $distributorGroup;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="CommonBundle\Entity\Users", inversedBy="usersGroup")
* #ORM\JoinTable(name="users_to_group_role",
* joinColumns={
* #ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="users_id", referencedColumnName="users_id", nullable=false)
* }
* )
*
* #Assert\Count(
* min = 1,
* minMessage = "You must select at least one user"
* )
*/
private $usersInGroup;
/**
* Constructor
*/
public function __construct()
{
$this->distributorGroup = new ArrayCollection();
$this->usersInGroup = new ArrayCollection();
}
}
/**
* #ORM\Entity()
* #ORM\Table(name="users_to_group_role")
*/
class UsersToGroupRole
{
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(type="integer",nullable=false)
* #Assert\Type(type="integer")
* #Assert\NotNull()
*/
protected $usersId;
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(type="integer", nullable=false)
* #Assert\Type(type="integer")
* #Assert\NotNull()
*/
protected $groupingRoleId;
}

A little example by using form validation approach in EasyAdminBundle:
class AdminController extends EasyAdminController
{
// ...
protected function create<EntityName>EntityFormBuilder($entity, $view)
{
$builder = parent::createEntityFormBuilder($entity, $view);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$flag = false;
if (isset($data['flag'])) {
$flag = $data['flag'];
unset($data['flag']);
}
$key = md5(json_encode($data));
if ($flag !== $key) {
$event->getForm()->add('flag', HiddenType::class, ['mapped' => false]);
$data['flag'] = $key;
$event->setData($data);
}
});
return $builder;
}
protected function get<EntityName>EntityFormOptions($entity, $view)
{
$options = parent::getEntityFormOptions($entity, $view);
$options['validation_groups'] = function (FormInterface $form) {
if ($form->has('flag')) {
return ['Default', 'CheckUserGroup'];
}
return ['Default'];
};
$options['constraints'] = new Callback([
'callback' => function($entity, ExecutionContextInterface $context) {
// validate here and adds the violation if applicable.
$context->buildViolation('Warning!')
->atPath('<field>')
->addViolation();
},
'groups' => 'CheckUserGroup',
]);
return $options;
}
}
Note that PRE_SUBMIT event is triggered before the validation process happen.
The flag field is added (dynamically) the first time upon submitted the form, so the validation group CheckUserGroup is added and the callback constraint do its job. Later, the second time the submitted data contains the flag hash (if the data does not changes) the flag field is not added, so the validation group is not added either and the entity is saved (same if the callback constraint does not add the violation the first time).
Also (if you prefer) you can do all this inside a custom form type for the target entity.

Related

Symfony 3 UniqueEntity validation on update

I am facing an issue with UniqueEntity validation.
I have a field "internal_asset_number" which should be unique and it's working fine on create. On update when i edit the existing current data with the same values, it shows "There is already an asset with that internal number!" but it shouldn't because it's the same entry.
The entity:
/**
* Asset
*
* #ORM\Table(schema="assets", name="asset", uniqueConstraints= {#ORM\UniqueConstraint(name="uk_asset_internal_asset_number_client_id", columns={"internal_asset_number", "client_id"})})
* #ORM\Entity(repositoryClass="Api\AssetBundle\Entity\AssetRepository")
* #UniqueEntity(fields={"internalAssetNumber"}, groups={"post", "put"}, message="There is already an asset with that internal number!")
*/
class Asset
{
/**
* #var guid
*
* #ORM\Column(name="id", type="string")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="client_id", type="string", length=255, nullable=false)
*/
private $clientId;
/**
* #var string
*
* #ORM\Column(name="internal_asset_number", type="string", length=255, nullable=true, unique=true)
*/
private $internalAssetNumber;
Update method:
public function putAssetAction(Request $request, $id)
{
$data = $this->deserializer('Api\AssetBundle\Entity\Asset', $request, 'put');
if ($data instanceof \Exception) {
return View::create(['error' => $data->getMessage()], 400);
}
$validator = $this->get('validator');
$errors = $validator->validate($data, null, 'put');
if (count($errors) > 0) {
$errorsResponse = [];
foreach ($errors as $error) {
$errorsResponse = $error->getMessage();
}
return View::create(array('error' => $errorsResponse), 400);
}
...
As #xabbuh commented, the problem is that the entity you persist after update is not retrieved through the entityManager so when you persist it the entity manager thinks it is a new entity.
Here is how to solve it:
$entityManager->merge($entity);
This will tell the entitymanager to merge your serialized entity with the managed one
Some more explanation on merge():
https://stackoverflow.com/a/15838232/5758328

Symfony 2 - Entity is not updated

I am working with form aimed at uploading the file and updating the database in Symfony2. I want to manually set value of book_id field and not to allow user to change it in the form. Thus in my controller before using doctrine to persist document I am calling:
$documents->setBookId('1');
Unluckilly I get error which indicates that the doctrine does not recognise the above hard coded value input.
An exception occurred while executing 'INSERT INTO Documents (book_id, marker, document_date, link, notes) VALUES (?, ?, ?, ?, ?)' with params [null, "fdd", "2015-04-04", null, "test"]:
To my mind this may be connected with the fact that book_id field is related to Books. Therefore probably I should use setBook function instead. Could you please advice how to do this properly?
My controler file looks like this:
/**
* This code is aimed at checking if the book is chosen and therefore whether any further works may be carried out
*/
$session = new Session();
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
// Authorization goes here
$documents = new Documents();
$form = $this->createForm(new DocumentsType(), $documents);
$form->add('save', 'submit', array('label' => 'Dodaj dokument'));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$documents->upload();
$documents->setBookId('1');
$em->persist($documents);
$em->flush();
}
return $this->render('AppBundle:Documents:adddocuments.html.twig', array('form' => $form->createView()));
Document class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="Documents")
* #ORM\HasLifecycleCallbacks
*/
class Documents
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Books", inversedBy="documents")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
/**
* #ORM\Column(type="integer")
*/
protected $book_id;
/**
* #ORM\Column(type="string", length=220)
*/
protected $marker;
/**
* #ORM\Column(type="date", length=220)
*/
protected $document_date;
/**
* #ORM\Column(type="string", length=220)
* #Assert\File(maxSize="6000000")
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $notes;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set book_id
*
* #param integer $bookId
* #return Documents
*/
public function setBookId($bookId)
{
$this->book_id = $bookId;
return $this;
}
/**
* Get book_id
*
* #return integer
*/
public function getBookId()
{
return $this->book_id;
}
/**
* Set marker
*
* #param string $marker
* #return Documents
*/
public function setMarker($marker)
{
$this->marker = $marker;
return $this;
}
/**
* Get marker
*
* #return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* Set document_date
*
* #param \DateTime $documentDate
* #return Documents
*/
public function setDocumentDate($documentDate)
{
$this->document_date = $documentDate;
return $this;
}
/**
* Get document_date
*
* #return \DateTime
*/
public function getDocumentDate()
{
return $this->document_date;
}
/**
* Set link
*
* #param string $link
* #return Documents
*/
public function setLink($link)
{
$this->link = $link;
return $this;
}
/**
* Get link
*
* #return string
*/
public function getLink()
{
return $this->link;
}
/**
* Set notes
*
* #param string $notes
* #return Documents
*/
public function setNotes($notes)
{
$this->notes = $notes;
return $this;
}
/**
* Get notes
*
* #return string
*/
public function getNotes()
{
return $this->notes;
}
/**
* Set book
*
* #param \AppBundle\Entity\Books $book
* #return Documents
*/
public function setBook(\AppBundle\Entity\Books $book = null)
{
$this->book = $book;
return $this;
}
/**
* Get book
*
* #return \AppBundle\Entity\Books
*/
public function getBook()
{
return $this->book;
}
/*
* ### FILE UPLOAD PROCESS ###
*/
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->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 up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->getFile()->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
}
Okay, first since you're using ManyToOne relation, you don't actually need another property refering to the book - book_id. You can remove that and leave book only.
Then in your controller you have to query the database for that Book and set the that object your Document.
You can do it like this:
$bookId = 1; // Following your example, let's say tou already know the book ID.
$book = $em->getReference('AppBundle:Books', $bookId);
// Check if we actually found a record and then set it to Documents
// Looking at your entity mapping, your reference to Book can not be null,
// but doing an extra check never hurts, since this is just an example.
if( $book ) {
$documents->setBook($book);
}
-Update-
If you want to directly insert the bookID, then what is the purpose of having ManyToOne reference in your entity? Eventually you're going to have to start using doctrine's relations and objects properly. Also, the cool thing about getReference method is that you are getting a reference to an entity, without having to load the entity from the database - you get the so called Proxy objects.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier
You can read further about this here.

Symfony 2 - findall() too many DB queries

why Symfony2 performs 40 DB queries if I use following code:
$em = $this->getDoctrine()->getManager();
$records = $em->getRepository('MyWebBundle:Highlight')->findAll();
I thought that findAll() method returns only all items from Highlight entity and associations to other entities replaces Proxy objects. But now findAll() method gettings all associations entities.
Do you know where is the problem ?
indexAction
public function indexAction() {
$em = $this->getDoctrine()->getManager();
$records = $em->getRepository('MyWebBundle:Highlight')->findAll();
$csrf = $this->get('security.csrf.token_manager');
$token = $csrf->refreshToken(self::FORM_TOKEN_ID);
$params = array(
"data" => array(
"all" => $records,
),
"token" => $token->getValue(),
"static" => array(
"add" => $this->generateUrl("admin_highlight_add"),
"edit" => $this->generateUrl("admin_highlight_edit"),
"del" => $this->generateUrl("admin_highlight_del"),
),
);
$ser = $this->get('jms_serializer');
$jsonContent = $ser->serialize($params, 'json');
return array('jsonContent' => $jsonContent);
}
Highlight entity
namespace My\WebBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Highlight
*
* #JMS\ExclusionPolicy("none")
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\WebBundle\Entity\HighlightRepository")
*/
class Highlight {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="abbreviation", type="string", length=8, unique=true)
*/
private $abbreviation;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=80, nullable=true)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="color", type="string", length=7)
*/
private $color;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Goods", mappedBy="highlight")
*/
private $goods;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Calibration", mappedBy="highlight")
*/
private $calibrations;
/**
* Constructor
*/
public function __construct() {
$this->goods = new \Doctrine\Common\Collections\ArrayCollection();
$this->calibrations = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set abbreviation
*
* #param string $abbreviation
* #return Highlight
*/
public function setAbbreviation($abbreviation) {
$this->abbreviation = $abbreviation;
return $this;
}
/**
* Get abbreviation
*
* #return string
*/
public function getAbbreviation() {
return $this->abbreviation;
}
/**
* Set description
*
* #param string $description
* #return Highlight
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription() {
return $this->description;
}
/**
* Set color
*
* #param string $color
* #return Highlight
*/
public function setColor($color) {
$this->color = $color;
return $this;
}
/**
* Get color
*
* #return string
*/
public function getColor() {
return $this->color;
}
/**
* Add goods
*
* #param \My\WebBundle\Entity\Goods $goods
* #return Highlight
*/
public function addGood(\My\WebBundle\Entity\Goods $goods) {
$this->goods[] = $goods;
return $this;
}
/**
* Remove goods
*
* #param \My\WebBundle\Entity\Goods $goods
*/
public function removeGood(\My\WebBundle\Entity\Goods $goods) {
$this->goods->removeElement($goods);
}
/**
* Get goods
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getGoods() {
return $this->goods;
}
/**
* Add calibrations
*
* #param \My\WebBundle\Entity\Calibration $calibrations
* #return Highlight
*/
public function addCalibration(\My\WebBundle\Entity\Calibration $calibrations) {
$this->calibrations[] = $calibrations;
return $this;
}
/**
* Remove calibrations
*
* #param \My\WebBundle\Entity\Calibration $calibrations
*/
public function removeCalibration(\My\WebBundle\Entity\Calibration $calibrations) {
$this->calibrations->removeElement($calibrations);
}
/**
* Get calibrations
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCalibrations() {
return $this->calibrations;
}
}
Highlight repository is empty
I think the problem comes from the serializer. Since you serializer highliths, each of them has their properties serialized as well which means that lazy query will be performed to retieved Goods which will be also serialized.
You should then prevent this behaviour by adding annotations to highlight's goods property as this
use ...
use JMS\SerializerBundle\Annotation\ExclusionPolicy;
use JMS\SerializerBundle\Annotation\Exclude;
/**
* ...
* #ExclusionPolicy("none")
*/
class Highlight
{
/**
* ...
* #Exclude
*/
private $goods;
}
You can have further details about exclusion stratigies from JMSSerializer doc
findAll itself does not perform many queries. Queries are executed when you access related entity via getters. As relation are not fetched eagerly, they first time are fetched when you are acessing them.
I think serializer access all children to send your object.
See Doctrine documentation
Whenever you have a managed entity instance at hand, you can traverse
and use any associations of that entity that are configured LAZY as if
they were in-memory already. Doctrine will automatically load the
associated objects on demand through the concept of lazy-loading.
To prevent this either disable children serialization or use fetch EAGER or build a DQL query, which prefetch all the children alongside with parents, like (just sample, not valid DQL)
SELECT Highlight, Good, Calibration
FROM Highlights Highlight
LEFT JOIN Highlight.googs Good
LEFT JOIN Goog.calibrations Calibration
WHERE ...

Create relationnal entitiy on Symfony 2

I would like to save my users search on my website. Thaht's why i have a Class User and i would like to create Search Class.
I have done that :
class Search
{
public function __construct()
{
$this->searched_date = new \Datetime();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="test\UserBundle\Entity\User")
*/
private $user;
/**
* #ORM\Column(name="termsearch", type="string", length=255, nullable="true")
*/
private $termsearch;
/**
* #ORM\Column(name="goodtitle", type="string", length=255, nullable="true")
*/
private $goodtitle;
/**
* #ORM\Column(name="searched_date", type="datetime")
*/
private $searched_date;
/**
* Set termsearch
*
* #param text $termsearch
*/
public function setTermsearch($termsearch)
{
$this->termsearch = $termsearch;
}
/**
* Get termsearch
*
* #return text
*/
public function getTermsearch()
{
return $this->termsearch;
}
/**
* Set searched_date
*
* #param datetime $searchedDate
*/
public function setSearchedDate($searchedDate)
{
$this->searched_date = $searchedDate;
}
/**
* Get searched_date
*
* #return datetime
*/
public function getSearchedDate()
{
return $this->searched_date;
}
/**
* Set user
*
* #param test\UserBundle\Entity\User $user
*/
public function setUser(\test\UserBundle\Entity\User $user)
{
$this->user = $user;
}
/**
* Get user
*
* #return test\UserBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Set goodtitle
*
* #param text $goodtitle
*/
public function setGoodtitle($goodtitle)
{
$this->goodtitle = $goodtitle;
}
/**
* Get goodtitle
*
* #return text
*/
public function getGoodtitle()
{
return $this->goodtitle;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
And i would like to insert like that :
$em = $this->getDoctrine()->getEntityManager();
$user = $em->getRepository('TestUserBundle:User')->find($currentuser->getID());
$search = new Search();
$search->setUser($user);
$search->setTermsearch($termsearch);
$search->setGoodtitle($goodtitle);
$em->persist($search);
$em->flush();
Unfortunately i have this error :
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens (500 Internal Server Error)
And in the stack we can found that :
INSERT INTO s_search (user_id, termsearch, goodtitle, searched_date) VALUES (?, ?, ?, ?) ({"1":"c2c","2":"C2C Down The Road","3":{"date":"2012-10-31 00:18:47","timezone_type":3,"timezone":"Europe\/Paris"}})
I don't know how i can create this class Search...
Thank for for you help !
Remove #ORM\Id from the $user as #ManyToOne mapping reference does not require a type. See Doctrine's Annotation Reference for details. Doctrine takes care of the correct column type to hold the reference to another entity.
Make also sure that your User query really returns a valid $user. If it's possible that Search does not have $user, use #JoinColumn annotation to claim the column nullable. See another SO question Doctrine 2 can't use nullable=false in manyToOne relation?

Doctrine - ManyToMany Self Referencing Association + nested toArray() on child elements

I'm trying to perform a ManyToMany self referencing association in my Symfony 2.1 project by following the Doctrine docs: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-self-referencing
My use-case is that I'm working on a CMS and I'm adding the ability to have related items of content. For example: I could have a sidebar on a website which would say that this piece of content X is related to Y and Z. Similarly on pages where content Y appears it says that it is related to content item X.
In my tests using this to add a new relation between content items fails because it reaches PHP's maximum nesting level of 100 because it is running toArray() on the current content item and then again on the related content item and so on and so on.
I've seen many similar questions on SO about Many-to-Many Self referential Doctrine associations but none with enough complete code to be able to see how others have managed this. Can anybody help?
My Content entity:
/**
* #ORM\MappedSuperclass
* #ORM\Table(name="content")
* #ORM\Entity(repositoryClass="CMS\Bundle\Common\ContentBundle\Entity\ContentRepository")
* #ORM\InheritanceType("JOINED")
*/
abstract class content implements ContentInterface
{
/**
* #var int $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
// Other class properties
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Content", cascade={"persist"})
* #ORM\JoinTable(name="content_relation",
* joinColumns={#ORM\JoinColumn(name="relation_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="related_content_id", referencedColumnName="id")
* })
**/
private $related;
public function __construct()
{
$this->related = new ArrayCollection();
}
// Other getters & setters for class properties
/**
* #return array
*/
public function getRelated()
{
return $this->related;
}
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related->add($relation);
$this->related->add($this);
}
/**
* #return array
*/
public function toArray()
{
$related = array();
foreach($this->getRelated() as $relatedItem) {
$related[] = $relatedItem->toArray();
}
return array(
'type' => static::getType(),
'id' => $this->id,
'title' => $this->title,
....
'related' => $related
);
}
In my RelationsController for managing the related content data I use it like this:
/**
* Creates a new relation to a content item
*
* #Route("{_locale}/content/{id}/related", name="relation_add")
* #Method("POST")
*/
public function addAction(Request $request, $id)
{
// Validation and error checking
// $entity is loaded by the repository manager doing a find on the passed $id
$entity->addRelation($relation);
$em = $this->getEntityManager();
$em->persist($entity);
$em->persist($relation);
$em->flush();
$response = $relation->toArray();
return new JsonResponse($response, 201);
}
The fix for this was to use the JMSSerializerBundle to encode the entity to JSON instead of using a toArray method and change the addRelation function to:
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related[] = $relation;
if (! $relation->getRelated()->contains($this)) {
$relation->addRelation($this);
}
}

Resources