Serializing Entity Relation only to Id with JMS Serializer - php

I'm trying to serialize a entity relation with JMS Serializer.
Here is the Entity:
class Ad
{
/**
* #Type("string")
* #Groups({"manage"})
*
* #var string
*/
private $description;
/**
* #Type("Acme\SearchBundle\Entity\Country")
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*/
private $country;
/**
* #Type("string")
* #Groups({"manage"})
*
* #var string
*/
private $title;
/**
* Set description
*
* #param string $description
* #return Ad
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set country
*
* #param \Acme\SearchBundle\Entity\Country $country
* #return Ad
*/
public function setCountry($country)
{
$this->country= $country;
return $this;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
/**
* Set title
*
* #param string $title
* #return Ad
*/
public function setTituloanuncio($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
And the Entity of the relationship:
class Country
{
/**
* #Type("string")
* #Groups("manage")
*
* #var string
*/
private $id;
/**
* #Type("string")
* #Groups("admin")
*
* #var string
*/
private $description;
/**
* Set description
* #Groups("")
*
* #param string $description
* #return Country
*/
public function setDescripcionpais($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
}
I serialize the entity but I don't know how to convert the country attribute into a simple field.
I get this result in json:
{"description":"foo", "title":"bar", "country":{"id":"en"} }
But I want to get the id field of the country like this:
{"description":"foo", "title":"bar", "country": "en" }
It is possible with JMS Serializer?
Thank you.
[EDIT]
#VirtualProperty doesn't work.

Yes, you could use #VirtualProperty annotation:
/**
* #VirtualProperty
* #SerializedName("foo")
*/
public function bar()
{
return $this->country->getCode();
}
But be aware when it comes to deserialization:
#VirtualProperty This annotation can be defined on a method to
indicate that the data returned by the method should appear like a
property of the object.
> Note: This only works for serialization and is completely ignored
during deserialization.
Hope this helps...

Just to follow answered question:
If you don't like writing one method for each relation you have - just write your own handler. It's easy like
final class RelationsHandler
{
/**
* #var EntityManagerInterface
*/
private $manager;
/**
* RelationsHandler constructor.
*
* #param EntityManagerInterface $manager
*/
public function __construct(EntityManagerInterface $manager) { $this->manager = $manager; }
public function serializeRelation(JsonSerializationVisitor $visitor, $relation, array $type, Context $context)
{
if ($relation instanceof \Traversable) {
$relation = iterator_to_array($relation);
}
if (is_array($relation)) {
return array_map([$this, 'getSingleEntityRelation'], $relation);
}
return $this->getSingleEntityRelation($relation);
}
/**
* #param $relation
*
* #return array|mixed
*/
protected function getSingleEntityRelation($relation)
{
$metadata = $this->manager->getClassMetadata(get_class($relation));
$ids = $metadata->getIdentifierValues($relation);
if (!$metadata->isIdentifierComposite) {
$ids = array_shift($ids);
}
return $ids;
}
}
Register the Handler
jms_serializer.handler.relation:
class: MyBundle\RelationsHandler
arguments:
- "#doctrine.orm.entity_manager"
tags:
- { name: jms_serializer.handler, type: Relation, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation, direction: deserialization, format: json, method: deserializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: deserialization, format: json, method: deserializeRelation}
This allows you to replace virtual getter methods with `Type("Relation").
If you also want't to deserialize relation - you should tell each #Type("Relation") the classname (#Type("Relation<FQCN>")) which it should deserialize to or wrap the metadata driver with one which do it for you.
public function deserializeRelation(JsonDeserializationVisitor $visitor, $relation, array $type, Context $context)
{
$className = isset($type['params'][0]['name']) ? $type['params'][0]['name'] : null;
if (!class_exists($className, false)) {
throw new \InvalidArgumentException('Class name should be explicitly set for deserialization');
}
$metadata = $this->manager->getClassMetadata($className);
if (!is_array($relation)) {
return $this->manager->getReference($className, $relation);
}
$single = false;
if ($metadata->isIdentifierComposite) {
$single = true;
foreach ($metadata->getIdentifierFieldNames() as $idName) {
$single = $single && array_key_exists($idName, $relation);
}
}
if ($single) {
return $this->manager->getReference($className, $relation);
}
$objects = [];
foreach ($relation as $idSet) {
$objects[] = $this->manager->getReference($className, $idSet);
}
return $objects;
}

I know this has already been answered but you could also use #Accessor.
This probably (may, I can't be sure) work with deserialization too.
/**
* #Type("Acme\SearchBundle\Entity\Country")
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*
* #Serializer\Accessor(getter="getCountryMinusId",setter="setCountryWithId")
*/
private $country;
/**
* #return string|null
*/
public function getCountryMinusId()
{
if (is_array($this->country) && isset($this->country['id'])) {
return $this->country['id'];
}
return null;
}
/**
* #param string $country
* #return $this
*/
public function setCountryWithId($country)
{
if (!is_array($this->country)) {
$this->country = array();
)
$this->country['id'] = $country;
return $this;
}

You can use #Type and #Accessor annotations:
/**
* #Type("string")
* #Accessor(getter="serializeType",setter="setType")
*/
protected $type;
public function serializeType()
{
return $this->type->getId();
}

The author wants to keep the property name, which doesn't apply to the accepted answer. As far as I understood, the answer by ScayTrase would keep the original property name but has another disadvantage according to the comments: The related object will be fetched if you are using Doctrine ORM #ManyToOne, thus decreasing performance.
If you want to keep the original property name, you have to define the #VirtualProperty at class level and #Exclude the original property. Otherwise, the serialized property name will be derived from the getter method (countryId in this case):
/**
* #Serializer\VirtualProperty(
* "country",
* exp="object.getCountryId()",
* options={#Serializer\SerializedName("country")}
* )
*/
class Ad {
/**
* #Serializer\Exclude
*/
private $country;
public function getCountryId() {
return $this->country === null ? null : $this->country->getId();
}
}

Alternatively, you can #inline $country which will serialize its properties into the parent relation. Then you can #Expose the Country $id and set its #SerializedName to "country". Unlike Virtual properties, both serialization and deserialization will work for inline properties.
For this to work, you need to use the #ExclusionPolicy("All") on each class and judiciously #Expose the properties that you need in any of your groups. This is a more secure policy anyways.
/**
* #ExclusionPolicy("All")
*/
class Ad
{
//...
/**
* #Type("Acme\SearchBundle\Entity\Country")
*
* #Expose()
* #Inline()
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*/
private $country;
//...
}
/**
* #ExclusionPolicy("All")
*/
class Country
{
//...
/**
* Get id
*
* #Expose()
* #Groups({"manage"})
* #SerializedName("country")
* #return string
*/
public function getId()
{
return $this->id;
}
}

Related

Symfony3 I have to show from a related entity

I have a question.
I have 2 entity
/**
* #ORM\Entity
* #ORM\Table(name="call_center")
*/
class Call {
/**
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Number", mappedBy="number")
* #ORM\Column(type="string")
*/
private $number;
/**
* #ORM\Column(type="string")
*/
private $value;
......
getters setters
/**
* #ORM\Entity
* #ORM\Table(name="number")
*/
class Number {
/**
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id
* #ORM\Column(type="integer")
*
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Call", inversedBy="number")
* #ORM\JoinColumn(nullable=false)
*/
private $number;
/**
* #ORM\Column(type="string")
*/
private $link;
And I would like to show my data in controller.
This is my controller
class DefaultController extends Controller
{
/**
* #Route("/pl/", name="homepage")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getRepository('AppBundle:Call')->findAll();
foreach ($em as $name) {
switch(1) {
case $name->getNumber():
echo $name->getValue();
echo $name->getLink(); <----PROBLEME
break;
default:
break;
}
}
return $this->render('default/index.html.twig', array(
'em' => $name
));
}
}
Data with entity call displayed but I don't know how dipsplay data from Number (getLink()). The problem is that I have a loop in which I have to display for a particular value relationship. Probably I have to create repository for entity?
Entity Call
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set number
*
* #param string $number
*
* #return Call
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* #return string
*/
public function getNumber()
{
return $this->number;
}
/**
* Set value
*
* #param string $value
*
* #return Call
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
return $this->value;
}
entity Number
/**
* Set number
*
* #param \AppBundle\Entity\Call $number
*
* #return Number
*/
public function setNumber(\AppBundle\Entity\Call $number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* #return \AppBundle\Entity\Call
*/
public function getNumber()
{
return $this->number;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set link
*
* #param string $link
*
* #return Number
*/
public function setLink($link)
{
$this->link = $link;
return $this;
}
/**
* Get link
*
* #return string
*/
public function getLink()
{
return $this->link;
}
Maybe you have a string because you tell doctrine to put a string ?
remove the following annotation from your relation :
#ORM\Column(type="string")
Have you tried to access it via $name->getNumber()->getLink() ?
as pointed out below, by tny, you should fix your annotations, or the above will not work, as getNumber() is currently returning a string instead of a Number instance

How to integrate forms of several entities? Symfony 2

I need to merge table 'fos_user' and 'institution'.And I need to display registration form from both entities.
I have a problem with FOSUserBundle.
I created new properties in User class
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
protected $workPhone;
protected $adminPhone;
protected $name;
protected $adress;
public function __construct()
{
parent::__construct();
}
public function setAdress($adress)
{
$this->adress = $adress;
}
public function setName($name)
{
$this->name = $name;
}
public function setWorkPhone($workPhone)
{
$this->workPhone = $workPhone;
}
public function setAdminPhone($adminPhone)
{
$this->adminPhone = $adminPhone;
}
public function getName()
{
return $this->name;
}
public function getAdress()
{
return $this->adress;
}
public function getWorkPhone()
{
return $this->workPhone;
}
public function getAdminPhone()
{
return $this->adminPhone;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
And I have entity Institution, which I want merge.
/**
* #var integer
*
* #ORM\Column(name="id_institution", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $idInstitution;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=false)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="work_phone", type="integer", nullable=false)
*/
private $workPhone;
/**
* #var integer
*
* #ORM\Column(name="admin_phone", type="integer", nullable=true)
*/
private $adminPhone;
/**
* #var string
*
* #ORM\Column(name="adress", type="string", length=255, nullable=false)
*/
private $adress;
/**
* #var \AppBundle\Entity\FosUser
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\FosUser")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_fos_user", referencedColumnName="id")
* })
*/
private $idFosUser;
/**
* Get idInstitution
*
* #return integer
*/
public function getIdInstitution()
{
return $this->idInstitution;
}
/**
* Set name
*
* #param string $name
* #return Institution
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set workPhone
*
* #param integer $workPhone
* #return Institution
*/
public function setWorkPhone($workPhone)
{
$this->workPhone = $workPhone;
return $this;
}
/**
* Get workPhone
*
* #return integer
*/
public function getWorkPhone()
{
return $this->workPhone;
}
/**
* Set adminPhone
*
* #param integer $adminPhone
* #return Institution
*/
public function setAdminPhone($adminPhone)
{
$this->adminPhone = $adminPhone;
return $this;
}
/**
* Get adminPhone
*
* #return integer
*/
public function getAdminPhone()
{
return $this->adminPhone;
}
/**
* Set adress
*
* #param string $adress
* #return Institution
*/
public function setAdress($adress)
{
$this->adress = $adress;
return $this;
}
/**
* Get adress
*
* #return string
*/
public function getAdress()
{
return $this->adress;
}
/**
* Set idFosUser
*
* #param \AppBundle\Entity\FosUser $idFosUser
* #return Institution
*/
public function setIdFosUser($idFosUser = null)
{
$this->idFosUser = $idFosUser;
return $this;
}
/**
* Get idFosUser
*
* #return \AppBundle\Entity\FosUser
*/
public function getIdFosUser()
{
return $this->idFosUser;
}
This is pert of InstitutionManager where I want save Entity, and this is like service now:
public function createNewEntity($user)
{
$entity = new Institution();
$entity->setName($user->getName());
$entity->setAdminPhone($user->getAdminPhone());
$entity->setWorkPhone($user->getWorkPhone());
$entity->setAdress($user->getAdress());
$entity->setidFosUser($user->getId());
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
}
And hear override RegistrationController:
public function registerAction()
{
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
$authUser = false;
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
$authUser = true;
$route = 'fos_user_registration_confirmed';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$institutionManager = $this->container->get('institution_manager');
$institution = $institutionManager->createNewEntity($user);
$url = $this->container->get('router')->generate($route);
$response = new RedirectResponse($url);
if ($authUser) {
$this->authenticateUser($user, $response);
}
return $response;
}
return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
'form' => $form->createView(),
));
}
services.yml:
services:
institution_manager:
class: AppBundle\Lib\Manager\InstitutionManager
arguments: [ #doctrine.orm.default_entity_manager ]
Your InstitutationController is not being properly initialized. There is a setContainer method which the router calls to, well, set the container. getDoctrine in turn needs the container, hence the null object error.
A simple hack would be to call the setContainer method yourself:
$entity = new InstitutionController();
$entity->setContainer($this->container);
$entity->createNewEntity($user);
But it's a hack you should do some redesigning. What you are calling a controller is not a controller at all. It's a service, maybe a sort of a factory or manager. Sort of like the FOS UserManager.
So read up on how to define a service: http://symfony.com/doc/current/book/service_container.html
It takes a bit of research but once you understand the process then services will become second nature. You will inject the doctrine entity manager directly into your service.
class InstitutionManager
{
protected $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function createNewEntity($user)
{
$entity = new Institution();
$entity->setName($user->getName());
$entity->setAdminPhone($user->getAdminPhone());
$entity->setWorkPhone($user->getWorkPhone());
$entity->setAdress($user->getAdress());
$entity->setidFosUser($user->getId());
$this->entityManager->persist($entity);
$this->entityManager->flush();
return $entity;
}
I will leave services.yml up to you. The entity manager service id is doctrine.orm.default_entity_manager
Your controller would then look like:
$institutionManager = $this->get('institution_manager');
$institution = $institutionManager->create($user);
You will also want to rethink how you are relating the user object. The userId stuff is a no no. You will want to make a one-to-one relation between $institution and $user. But that is really a different topic.
Enjoy.

Is there a way to insert many entities that reference a new entity all at once in Doctrine using ZF2 form?

I have a Product entity and a ProductImage entity. Product images are "ManyToOne" and contain a reference field to the product.
I'm using the Zend Framework 2 Form module with a "Collection" fieldset for the images. The form will populate with manually added rows in the database but does not create new rows or delete them.
The product image collection fieldset contains an id, url, and sort field for the image.
When i submit the form with new data, the DoctrineObject hydrator tries to load a ProductImage entity based on identifiers but ProductImage.id is NULL since there is no entity to reference obviously.
I tried creating my own hydrator to extend DoctrineObject and create the entities if they don't exist but the problem there is that Product does not have an ID to reference yet until it's created.
My question is do i need to go full custom on the images? Or is there a way to accomplish this using the DoctrineObject hydrator?
I am not shure if this will work for you, but I think I have a similar concept with a domain with OneToMany to a Properties table (one domain will have many properties)
Notice my code in Domain->addProperty() where I set the property's Domain id (pretty shure this will also work if the domain doesn't have an ID...
Hope this helps :)
public function indexAction()
{
$em = $this->getEntityManager();
$title = new Property();
/** #var \Common\Entity\Domain $domain */
$rep = $em->getRepository('Common\Entity\Domain');
$domain = $rep->findOneBy(array('id' => '3'));
$title->setProperty("title");
$title->setValue("WWW . RichardHagen . NO _ TEST");
$domain->addProperty($title);
$em->persist($domain);
$em->flush();
return new ViewModel(["domain" => $domain]);
}
This is my code for Domain:
<?php
/**
* Created by PhpStorm.
* User: Richard
* Date: 30.11.2014
* Time: 13:24
*/
namespace Common\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Common\Entity\Property;
/**
* #ORM\Entity
* #ORM\Table(name="domain")
*/
class Domain {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned":true})
*/
protected $id;
/** #ORM\Column(length=127,nullable=true) */
protected $subdomain;
/** #ORM\Column(length=255,nullable=true) */
protected $domain;
/**
* #ORM\OneToMany(targetEntity="Property", mappedBy="domain", fetch="EAGER", cascade={"persist"})
*/
protected $properties;
/**
* #param \Common\Entity\Property $property
*/
public function addProperty($property)
{
$property->setDomain($this);
$this->properties[] = $property;
}
/**
* #param string $name
* #param bool $default
* #return bool|string
*/
public function getPropertyValue($name, $default = false) {
/** #var Property $property */
foreach ($this->getProperties() as $property) {
if ($name == $property->getProperty()) {
return $property->getValue();
}
}
return $default;
}
/**
* #return mixed
*/
public function getProperties()
{
return $this->properties;
}
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain)
{
$this->domain = $domain;
}
/**
* #return mixed
*/
public function getSubdomain()
{
return $this->subdomain;
}
/**
* #param mixed $subdomain
*/
public function setSubdomain($subdomain)
{
$this->subdomain = $subdomain;
}
public function __construct() {
$this->properties = new ArrayCollection();
}
}
And in the end the code for Properties:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="property",options={"collate"="utf8_swedish_ci"})
*/
class Property {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned":true})
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Domain", fetch="EAGER", inversedBy="properties")
*/
protected $domain;
/**
* #ORM\Column(length=45,nullable=true)
*/
protected $property;
/**
* #ORM\Column(type="text",nullable=true)
*/
protected $value;
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain)
{
$this->domain = $domain;
}
/**
* #return mixed
*/
public function getProperty()
{
return $this->property;
}
/**
* #param mixed $property
*/
public function setProperty($property)
{
$this->property = $property;
}
/**
* #return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* #param mixed $value
*/
public function setValue($value)
{
$this->value = $value;
}
}

FatalErrorException: Error: Call to a member function getId() on a non-object in ...PostListener.php line 20

I'm new in Symfony2. I've a task to create blog. One of the necessary is displaying most popular posts. So I think that the best varient is create listener. It will be call, when visiter will read post. And listener will increment onŠµ of the fild in database(MySQL). I create method in repository, which makes selection by this field. And also create Action, which renders posts by this selection. But when I try to read post, a have error:
FatalErrorException: Error: Call to a member function getId() on a non-object in /var/www/blo/src/Blog/Bundle/BlogBundle/EventListener/PostVisitedListener.php line 20.
Please, help me.
This my Entity (Post):
namespace Blog\Bundle\BlogBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="Blog\Bundle\BlogBundle\Entity\PostRepository")
*/
class Post
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank
* #Assert\Length(min="13", max="255")
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="author", type="string", length=100)
* #Assert\NotBlank
* #Assert\Length(min="13", max="100")
*/
private $author;
/**
* #var string
*
* #ORM\Column(name="post", type="text")
* #Assert\NotBlank
* #Assert\Length(min="100")
*/
private $post;
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=100)
*/
private $image;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="createdAt", type="datetime")
*
*/
private $createdAt;
/**
*
* #ORM\Column(name="tags", type="text")
*/
private $tags;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="posts")
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="post")
*/
private $comments;
/**
* #var integer
*
* #ORM\Column(name="visitedIncrement", type="integer")
*/
private $visitedIncrement;
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
* #return Post
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set author
*
* #param string $author
* #return Post
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* Get author
*
* #return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set post
*
* #param string $post
* #return Post
*/
public function setPost($post)
{
$this->post = $post;
return $this;
}
/**
* Get post
*
* #return string
*/
public function getPost()
{
return $this->post;
}
/**
* Set image
*
* #param string $image
* #return Post
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set tags
*
* #param string $tags
* #return Post
*/
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
/**
* Get tags
*
* #return string
*/
public function getTags()
{
return $this->tags;
}
/**
* Set category
*
* #param $category
* #return $this
*/
public function setCategory($category)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return integer
*/
public function getCategory()
{
return $this->category;
}
/**
* Set comments
*
* #param string $comments
* #return Post
*/
public function setComments($comments)
{
$this->comments = $comments;
return $this;
}
/**
* Get comments
*
* #return string
*/
public function getComments()
{
return $this->comments;
}
/**
* #param \DateTime $createdAt
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* #param int $visitedIncrement
*/
public function setVisitedIncrement($visitedIncrement)
{
$this->visitedIncrement = $visitedIncrement;
return $this;
}
/**
* #return int
*/
public function getVisitedIncrement()
{
return $this->visitedIncrement;
}
public function __toString()
{
return $this->getTitle();
}
}
This is my PostRepository
public function visitedIncrement($id)
{
$query = $this->getEntityManager()
->createQuery(
'UPDATE BlogBlogBundle:Post p
SET p.visitedIncrement = p.visitedIncrement + 1
WHERE p.id = :post_id')
->setParameter(':post_id', $id);
$query->execute();
This is my PostVisitedEvent
namespace Blog\Bundle\BlogBundle\Event;
use Blog\Bundle\BlogBundle\Entity\Post;
use Symfony\Component\EventDispatcher\Event;
class PostVisitedEvent extends Event
{
protected $post;
/**
* #param Post $post
*/
public function setPost(Post $post)
{
return $this->post;
}
/**
* #return Post
*/
public function getPost()
{
return $this->post;
}
}
This is my PostVisitedListener
namespace Blog\Bundle\BlogBundle\EventListener;
use Blog\Bundle\BlogBundle\Entity\PostRepository;
use Doctrine\ORM\EntityManager;
use Blog\Bundle\BlogBundle\Event\PostVisitedEvent;
class PostVisitedListener
{
protected $repository;
public function __construct(PostRepository $repository)
{
$this->repository = $repository;
}
public function onPostVisited(PostVisitedEvent $event)
{
$this->repository->visitedIncrement($event->getPost()->getId());
}
This is my Action (it opens post and gives a opportunity to create comment):
public function showPostAction($id)
{
$postRepository = $this->container->get('blog_blog_bundle.post.repository');
$post = $postRepository->find($id);
if (!$post) {
throw $this->createNotFoundException('The post is not found!');
}
$commentRepository = $this->container->get('blog_blog_bundle.comment.repository');
$comments = $commentRepository->findCommentForPost($post->getId());
$event = new PostVisitedEvent();
$event->setPost($post);
$eventDispatcher = $this->get('event_dispatcher');
$eventDispatcher->dispatch('blog_blog_bundle.post_visited', $event);
return $this->render('BlogBlogBundle:Default:showPost.html.twig', array(
'post' => $post,
'comments' => $comments,
));
}
Yuo can see, that I also create services for repositories and listener. There are:
service id="blog_blog_bundle.post.repository" class="Blog\Bundle\BlogBundle\Entity\PostRepository" factory-service="doctrine.orm.entity_manager" factory-method="getRepository"
argument>BlogBlogBundle:Post argument
service
service id="blog_blog_bundle.comment.repository" class="Blog\Bundle\BlogBundle\Entity\CommentRepository" factory-service="doctrine.orm.entity_manager" factory-method="getRepository"
argument BlogBlogBundle:Comment argument
service
service id="blog_blog_bundle.post_visited_listener" class="Blog\Bundle\BlogBundle\EventListener\PostVisitedListener"
argument type="service" id="blog_blog_bundle.post.repository"
tag name="kernel.event_listener" event="blog_blog_bundle.post_visited" method="onPostVisited"
service
Please, help me.
public function onPostVisited(PostVisitedEvent $event)
{
if (!($event->getPost() instanceof PostInterface)) return;
$this->repository->visitedIncrement($event->getPost()->getId());
}
PostInterface is not a symfony interface. You have to code it. Asking for interface is better than asking for a concrete instance because symfony sometimes uses proxy classes instead of concrete classes.

Persist an entity and relate it to another entity without persiting the second again

I am building a front-end app with Durandal/Knockoutjs and a web service back-end with Symfony2 and I use Doctrine to access the database.
I have two entities that are in association of one-to-many, like this
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name = "drew_cat_door")
*/
class Door
{
public function __construct()
{
//$this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
if ($this->getAddTime() == null)
$this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
else
$this->setUpdateTime(new \DateTime(date('Y-m-d H:i:s')));
}
/**
* #ORM\Id
* #ORM\Column(type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\Column(type = "string", length = 30)
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity = "DoorType", inversedBy = "doors")
* #ORM\JoinColumn(name = "type_id", referencedColumnName = "id")
*/
protected $type;
/**
* #ORM\Column(type = "string", length = 30, nullable = true)
*/
protected $filename;
/**
* #ORM\Column(type = "string", length = 100, nullable = true)
*/
protected $description;
/**
* #ORM\Column(type = "integer", nullable = true)
*/
protected $views;
/**
* #ORM\Column(type = "datetime")
*/
protected $add_time;
/**
* #ORM\Column(type = "datetime", nullable = true)
*/
protected $update_time;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Door
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set filename
*
* #param string $filename
* #return Door
*/
public function setFilename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* Get filename
*
* #return string
*/
public function getFilename()
{
return $this->filename;
}
/**
* Set description
*
* #param string $description
* #return Door
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set views
*
* #param integer $views
* #return Door
*/
public function setViews($views)
{
$this->views = $views;
return $this;
}
/**
* Get views
*
* #return integer
*/
public function getViews()
{
return $this->views;
}
/**
* Set add_time
*
* #param \DateTime $addTime
* #return Door
*/
public function setAddTime($addTime)
{
$this->add_time = $addTime;
return $this;
}
/**
* Get add_time
*
* #return \DateTime
*/
public function getAddTime()
{
return $this->add_time;
}
/**
* Set update_time
*
* #param \DateTime $updateTime
* #return Door
*/
public function setUpdateTime($updateTime)
{
$this->update_time = $updateTime;
return $this;
}
/**
* Get update_time
*
* #return \DateTime
*/
public function getUpdateTime()
{
return $this->update_time;
}
/**
* Set type
*
* #param \Drewkol\AdminBundle\Entity\DoorType $type
* #return Door
*/
public function setType(\Drewkol\AdminBundle\Entity\DoorType $type = null)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* #return \Drewkol\AdminBundle\Entity\DoorType
*/
public function getType()
{
return $this->type;
}
}
/**
* #ORM\Entity
* #ORM\Table(name = "drew_cat_doortype")
*/
class DoorType
{
public function __construct()
{
$this->doors = new ArrayCollection();
if ($this->getAddTime() == null)
$this->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
else
$this->setUpdateTime(new \DateTime(date('Y-m-d H:i:s')));
}
/**
* #ORM\Id
* #ORM\Column(type = "integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type = "string", length = 30)
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity = "Door", mappedBy = "type")
*/
protected $doors;
/**
* #ORM\Column(type = "datetime")
*/
protected $add_time;
/**
* #ORM\Column(type = "datetime", nullable = true)
*/
protected $update_time;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return DoorType
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set add_time
*
* #param \DateTime $addTime
* #return DoorType
*/
public function setAddTime($addTime)
{
if ($addTime != null)
$this->add_time = $addTime;
return $this;
}
/**
* Get add_time
*
* #return \DateTime
*/
public function getAddTime()
{
return $this->add_time;
}
/**
* Set update_time
*
* #param \DateTime $updateTime
* #return DoorType
*/
public function setUpdateTime($updateTime)
{
$this->update_time = $updateTime;
return $this;
}
/**
* Get update_time
*
* #return \DateTime
*/
public function getUpdateTime()
{
return $this->update_time;
}
/**
* Add doors
*
* #param \Drewkol\AdminBundle\Entity\Door $doors
* #return DoorType
*/
public function addDoor(\Drewkol\AdminBundle\Entity\Door $doors)
{
$this->doors[] = $doors;
return $this;
}
/**
* Remove doors
*
* #param \Drewkol\AdminBundle\Entity\Door $doors
*/
public function removeDoor(\Drewkol\AdminBundle\Entity\Door $doors)
{
$this->doors->removeElement($doors);
}
/**
* Get doors
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getDoors()
{
return $this->doors;
}
}
Sorry for not ommiting any code. As you can see, a Door has a DoorType.
As it is easy, when adding a door, I post a JSON that goes
{"name":"nowe","type":{"id":5,"name":"loluk","add_time":"2013-09-25T01:05:05+0200"},"description":"hehe\n","filename":"hehe.jpg"}
and is a full door entity model with a type that has already been around. When I try to add this entity with doctrine with this code
$json_door = $this->get("request")->getContent();
if (empty($json_door))
return $this->createBadRequestException();
$door = $this->container->get("serializer")
->deserialize($json_door, "Drewkol\\AdminBundle\\Entity\\Door", "json");
$door->setAddTime(new \DateTime(date('Y-m-d H:i:s')));
$manager = $this->getDoctrine()->getManager();
$manager->persist($door);
$manager->flush();
I get an error that says
A new entity was found through the relationship 'Drewkol\AdminBundle\Entity\Door#type' that was not configured to cascade persist operations for entity: Drewkol\AdminBundle\Entity\DoorType#000000002d1b74500000000063b1c8fb. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'Drewkol\AdminBundle\Entity\DoorType#__toString()' to get a clue.
My question is: what is the most clean and efficient way to add door with a type that has already been added?
Is there any way to tell Doctrine to try and resolve if the type given in the entity being added is already in the database (as it must have been added before) or do I have to fetch and remove the type from the JSON deserialized entity to prevent it from being persisted (with the suggested cascade option) and then fetch the type with Doctrine and then set it as a type of my brand new deserialized door entity so Doctrine is aware of the type? I mean I like the data model generated by knockoutjs and transfered with JSON and it seems to me to be a general drawback not being able to do it the way I presented.
I recommend using the Doctrine prePersist event to handle this. In the prePersist event you need to write some logic that checks the database to determine if the doorType exists.
If it exists, then fetch the doorType from the database and use that instead of the doorType object that was created when you unserialized your knockout JSON.
If it does not exist, persist the doorType that was created from your unserialized knockout JSON and then persist your Door.
http://docs.doctrine-project.org/en/latest/reference/events.html

Categories