Symfony -> How to make created and modified fields dynamic with Doctrine? - date

I'm new to Symfony2 (or Symfony3) and I can't find how to set doctrine (with annotations config) to automatically save it in my entities when 'created' or 'modified' fields.

Here my solution after this time ...
You just need to put this directly into your entity class :
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class MyEntity {
//....
public function __construct() {
// we set up "created"+"modified"
$this->setCreated(new \DateTime());
if ($this->getModified() == null) {
$this->setModified(new \DateTime());
}
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function updateModifiedDatetime() {
// update the modified time
$this->setModified(new \DateTime());
}
//....
}
It works well actually

You can use StofDoctrineExtensionsBundle. This describes in symfony cookbook. It contains Timestampable behavior.
/**
* #var datetime $created
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $created;
/**
* #var datetime $updated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime")
*/
private $updated;

/**
*
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function updatedTimestamps()
{
$this->setModifiedAt(new \DateTime(date('Y-m-d H:i:s')));
if($this->getCreatedAt() == null)
{
$this->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
}
}
You dont need to call in __constructor anything. Just create getter and setter properties created, modified and that is all.
If you set first setCreated() on every update you will update also created colum. So put first setModifedAt()

Two more examples (if you're using Yaml or Xml mapping):
Entity\Product:
type: entity
table: products
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 32
created_at:
type: date
gedmo:
timestampable:
on: create
updated_at:
type: datetime
gedmo:
timestampable:
on: update
And xml:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping">
<entity name="Mapping\Fixture\Xml\Timestampable" table="timestampables">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="created_at" type="datetime">
<gedmo:timestampable on="create"/>
</field>
<field name="updated_at" type="datetime">
<gedmo:timestampable on="update"/>
</field>
</entity>
</doctrine-mapping>

The other answers suggest using if statements (which means repeating your property names) and having property-setting logic in the constructor that might never be used.
Alternatively, you could have onAdd and onUpdate methods that are called when needed:
/**
* #ORM\PrePersist
*/
public function onAdd()
{
$this->setAdded(new DateTime('now'));
}
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function onUpdate()
{
$this->setUpdated(new DateTime('now'));
}

Related

Rest API - Why is Postman sending me back empty curly brackets with GET

I'm working on a REST API. Hence I created my entities like for example this one musee.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Musee
*
* #ORM\Table(name="musee")
* #ORM\Entity(repositoryClass="AppBundle\Repository\MuseeRepository")
*/
class Musee
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", nullable=false)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(type="string", nullable=false)
*/
private$adresse;
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=false)
*/
private $horaireOuverture;
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=false)
*/
private $horaireFermeture;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Bateau", mappedBy="musee")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $bateaux;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return string
*/
public function getNom()
{
return $this->nom;
}
/**
* #param string $nom
*/
public function setNom($nom)
{
$this->nom = $nom;
}
/**
* #return string
*/
public function getAdresse()
{
return $this->adresse;
}
/**
* #param string $adresse
*/
public function setAdresse($adresse)
{
$this->adresse = $adresse;
}
/**
* #return int
*/
public function getHoraireOuverture()
{
return $this->horaireOuverture;
}
/**
* #param int $horaireOuverture
*/
public function setHoraireOuverture($horaireOuverture)
{
$this->horaireOuverture = $horaireOuverture;
}
/**
* #return int
*/
public function getHoraireFermeture()
{
return $this->horaireFermeture;
}
/**
* #param int $horaireFermeture
*/
public function setHoraireFermeture($horaireFermeture)
{
$this->horaireFermeture = $horaireFermeture;
}
/**
* #return mixed
*/
public function getBateaux()
{
return $this->bateaux;
}
/**
* #param mixed $bateaux
*/
public function setBateaux($bateaux)
{
$this->bateaux = $bateaux;
}
}
My MuseeRepository also exists and in my config.yml I put (both were installed with composer) :
# Nelmio CORS
nelmio_cors:
defaults:
allow_credentials: false
allow_origin: ['*']
allow_headers: ['Content-Type']
allow_methods: ['POST', 'PATCH', 'GET', 'DELETE', 'OPTIONS']
max_age: 3600
paths:
'^/api':
# FosRest
fos_rest:
routing_loader:
include_format: false
view:
view_response_listener: true
format_listener:
rules:
- { path: '^/', priorities: ['json'], fallback_format: 'json' }
In my MuseeController here is what I put:
<?php
/**
* Created by PhpStorm.
* User: Fanny
* Date: 28/03/2018
* Time: 16:13
*/
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\View\View;
use AppBundle\Entity\Musee;
class MuseeController extends Controller
{
/**
* #Rest\View(serializerGroups={"musee"})
* #Rest\Get("/musee")
*/
// Fonction qui renvoie toutes all musee
public function findMusee()
{
$musee = $this->getDoctrine()
->getRepository('AppBundle:Musee')
->findAll();
return $musee;
}
}
I also used a serializer file, I wonder if my problem does not come from there... Though all this elements are in my database as I generated it with doctrine.
AppBundle\Entity\Musee:
attributes:
id:
groups: ['musee']
nom:
groups: ['musee']
adresse:
groups: ['musee']
horaire_ouverture:
groups: ['musee']
horaire_fermeture:
groups: ['musee']
bateaux:
groups: ['musee']
My problem is when I try to call my localhost:8000/musee, I get the good amount of brackets compared to what is inside my database, but they appear empty.
I think I might be missing a step but I'm not sure where to search. Where do you think I should look?
UPDATE:
The version of Symfony that I have is 3.4.
I enabled serializer in my config.yml and installed with composer : jms/serializer-bundle.
In my app Kernel I have :
new FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle(),
new Nelmio\CorsBundle\NelmioCorsBundle(),
The problem is common to all my entities. I get the right amount of brackets but no content inside.
FOSRestBundle's docs cover the serialization here: Enable a Serializer
But CSS seems to be falling apart presently, so I'll quote it:
C) Enable a Serializer
This bundle needs a serializer to work correctly. In most cases, you'll need to enable a serializer or install one. This bundle tries the following (in the given order) to determine the serializer to use:
The one you configured using fos_rest.services.serializer (if you did).
The JMS serializer, if the JMSSerializerBundle is available (and registered).
The Symfony Serializer if it's enabled (or any service called serializer).
Questions:
Which version of Symfony do you use?
Have you enabled Serializer component?
Is the issue limited to Musee or all entities?
Please update the question with these and we can follow up.
Update:
Can't help but wonder if it has anything to do with all services becoming private in Symfony 3.4. Maybe FOSRestBundle cannot find the JMSSerializerBundle. Which version of FOSRestBundle you have installed?
I see that all released versions of FOSRestBundle specify the ^3.0 dependency requirement, but the above is specific to v3.4.
That happens because you are returning an array of entities instead of valid json response. I am working in REST/API too, but without FOSRest, so I cant be completely sure how serialization happens with FOSREST, but definitively is that.
A workaround could be returning a JSONResponse in your controller, with all of your entities serialized somehow instead of returning the array of entities as you are currently doing here
return $musee;
I recreated my project and this time didn't get an error... I'm not sure of what was the reason but I used the same Symfony versions and commands. So there was probably a problem somewhere even if I try to compare both and din't see any difference. I'll try to tell you if I find it.

Extend shopware database, creating relations

I'm trying to implement a plugin to add sales representative data to my shop and associate this data to users.
On this context (users and sales representative) I have:
sales_rep - Sales representative table
sales_rep_user - Relation between User and Sales Representative
1st For the swg_sales_rep and swg_sales_rep_user relation (OneToMany) I could create that without problems
SwgSalesRepresentative.php
...
**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep")
*/
class SwgSalesRepresentative extends ModelEntity
{
...
/**
* INVERSE SIDE
*
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative",
* mappedBy="salesRepresentative",
* orphanRemoval=true
* )
*/
protected $salesRepresentativeUsers;
...
SwgSalesRepresentativeUsers.php
/**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep_users")
*/
class SwgSalesRepresentativeUsers extends ModelEntity
{
...
/**
*
* #ORM\ManyToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative")
* #ORM\JoinColumn(name="sales_rep_id", referencedColumnName="id")
*/
protected $salesRepresentative;
/**
* #return mixed
*/
public function getSalesRepresentative()
{
return $this->salesRepresentative;
}
/**
* #param $salesRepresentative
* #return ModelEntity
*/
public function setSalesRepresentative($salesRepresentative)
{
return $this->setManyToOne(
$salesRepresentative,
'\Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative',
'salesRepresentativeUsers'
);
}
And after install I get my tables with foreign key ok.
For the relation between swg_sales_rep_user and s_user (OneToOne) I have problems. My first idea was extend the User model and add the additional logic we need. But this implies to overwrite my users table, take the risk to lose data.
What I did was create a SwgUser model that extends User model, like
SwgSalesRepresentativeUsers.php
/**
* #ORM\Entity
* #ORM\Table(name="swg_sales_rep_users")
*/
class SwgSalesRepresentativeUsers extends ModelEntity
{
...
/**
* #var \Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser $user
*
* #ORM\OneToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser", inversedBy="salesRepresentative")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param $user
* #return ModelEntity
*/
public function setUser($user)
{
return $this->setOneToOne(
$user,
'\Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser',
'user',
'salesRepresentative'
);
}
...
SwgUser.php
/**
* #ORM\Entity
* #ORM\Table(name="s_user")
*/
class SwgUser extends User
{
/**
*
* #ORM\OneToOne(targetEntity="Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers", mappedBy="user")
*/
protected $salesRepresentative;
...
And bootstrap.php install/uninstall looks like
/**
* Install method
*
* #return bool
*/
public function install()
{
$this->updateSchema();
return true;
}
/**
* Uninstall method
*
* #return bool
*/
public function uninstall()
{
$this->registerCustomModels();
$em = $this->Application()->Models();
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers')
);
$tool->dropSchema($classes);
return true;
}
/**
* Creates the database scheme from existing doctrine models.
*
* Will remove the table first, so handle with care.
*/
protected function updateSchema()
{
$this->registerCustomModels();
$em = $this->Application()->Models();
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentative'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgUser'),
$em->getClassMetadata('Shopware\CustomModels\SwagUserSalesRepresentative\SwgSalesRepresentativeUsers')
);
try {
$tool->dropSchema($classes);
} catch (Exception $e) {
//ignore
}
$tool->createSchema($classes);
}
I tried to use the unidirectional association mapping and it creates the field but not the relation with s_user table (Foreign key).
So question is, how can I create relations with core tables on shopware without have to recreate (drop/create) the core tables?
Is it possible to alter tables programmatically? what is the best approach for these needs. Do you have an example that demonstrate this?
Thanks for helping.
there is no way to create bidirectional associations with shopware core tables yet. You can have unidirectional associations for sure, but you will not be able to add relational properties to core entities so far.
Except you intend to modify the shopware core itself which should be avoided at any time.
The only - and very tiny - possibility would be by trying to create a relation over a core entities attribute table which is quite "magic stuff" in shopware.

How to nest Symfony form classes?

I am having issues figuring out the logic of how to create nested forms for the three entities I have: Films, Actors and Locations. I generated my Symfony entities (+orm.xml) from my database following the instructions in the symfony docs here.
My ultimate goal would to be have one page where the user can perform any of the following actions:
Create a new Films object
Select Films from a dropdown menu, and then create a new Actors object to associate to it
Select Films from a dropdown menu, and then create a new Locations object to associate to it
(Actors and Locations both have a 1-to-many join with the Films table)
However, I've been struggling with the concept of nested forms in Symfony for a long time and in order to "walk before I can run" I'm just trying to put each of the above into separate routes with separate forms:
/newfilm
/newactor
/newlocation
/New-film I can get working without problem. However, with either of the other two, anything I try doesn't seem to work. The below is my code, if someone can explain the "theory" of nested forms in Symfony to avoid keep hitting this wall would be very much appreciated...!
As my problem is the same for both Actor and Location, I'm only putting the code for for Actors (and Films) as I realise it's quite a lot already:
~~~~~Controller~~~~~
It is this second route (/newactor) which has the embedded/nested formType:
class DefaultController extends FOSRestController
{
/**
* #Route("/newfilm", name="new_film")
*/
public function newFilmAction(Request $request)
{
$film = new Films();
$form = $this->CreateFormBuilder($film)
->add('film_title','text',array('label'=>'Film title'))
->add('Save','submit',array('label'=>'Add new film'))
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($film);
$em->flush();
return $this->redirectToRoute('success_addFilm');
}
return $this->render('AppBundle:Default:newfilm.form.html.twig', array(
'form' => $form->createView(),
));
}
/**
* #Route("/newactor", name="new_actor")
*/
public function newActorAction(Request $request)
{
$actor = new Actors();
$form = $this->createForm(new ActorType(), $actor);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($actor);
$em->flush();
return $this->redirectToRoute('success_addActor');
}
return $this->render('AppBundle:Default:newactor.form.html.twig', array(
'form' => $form->createView(),
));
}
}
~~~~~Films~~~~~
Films.php
/**
* Films
*/
class Films
{
/**
* #var integer
*/
private $filmid;
/**
* #var string
*/
private $film_title;
/**
* #var \AppBundle\Entity\Actors
*/
private $actor;
/**
* Get filmid
* #return integer
*/
public function getFilmid()
{
return $this->filmid;
}
/**
* Get film_title
*
* #return string
*/
public function getFilm_title()
{
return $this->film_title;
}
/**
* Set film_title
* #param string $film_title
* #return Films
*/
public function setFilm_title($film_title)
{
$this->film_title = $film_title;
return $this;
}
/**
* Set actor
*
* #param \AppBundle\Entity\Actors $actor
*
* #return Actors
*/
public function setActor(\AppBundle\Entity\Actors $actor = null)
{
$this->actor = $actor;
return $this;
}
/**
* Get actor
*
* #return \AppBundle\Entity\Actors
*/
public function getActor()
{
return $this->actor;
}
}
Films.orm.xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="AppBundle\Entity\Films" table="Films">
<indexes>
<index name="fk_Films_actors1_idx" columns="actor_id"/>
</indexes>
<id name="filmid" type="integer" column="filmid">
<generator strategy="IDENTITY"/>
</id>
<field name="film_title" type="text" column="film_title" length="65535" nullable="false">
<options>
<option name="fixed"/>
</options>
</field>
<many-to-one field="actor" target-entity="Actors" fetch="LAZY">
<join-columns>
<join-column name="actor_id" referenced-column-name="actorid"/>
</join-columns>
</many-to-one>
</entity>
</doctrine-mapping>
FilmType.php
class FilmType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('film_title');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class'=>'AppBundle\Entity\Films'
));
}
public function getName()
{
return 'film';
}
}
~~~~~Actors~~~~~
Actors.php
/**
* Entries
*/
class Entries
{
/**
* #var integer
*/
private $actorid;
/**
* #var string
*/
private $actorName;
/**
* Set actorid
*
* #param integer $actorid
*
* #return Actors
*/
public function setActorid($actorid)
{
$this->actorid = $actorid;
return $this;
}
/**
* Get actorid
*
* #return integer
*/
public function getActorid()
{
return $this->actorid;
}
/**
* Set actorName
*
* #param string $actorName
*
* #return Actors
*/
public function setActorName($actorName)
{
$this->actorName = $actorName;
return $this;
}
/**
* Get actorName
*
* #return string
*/
public function getActorName()
{
return $this->actorName;
}
}
Actors.orm.xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="AppBundle\Entity\Actors" table="Actors">
<id name="actorid" type="integer" column="actorid">
<generator strategy="IDENTITY"/>
</id>
<field name="actorName" type="text" column="actorName" length="65535" nullable="true">
<options>
<option name="fixed"/>
</options>
</field>
</entity>
</doctrine-mapping>
ActorType
class ActorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('actorName')
->add('film','entity',array(
'class'=>'AppBundle:Films',
'query_builder'=>function(EntityRepository $er) {
return $er->createQueryBuilder('f')
->orderBy('f.film_title','ASC');
}
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class'=>'\AppBundle\Entity\Actors'
));
}
public function getName()
{
return 'actor';
}
}
My current error message is:
Catchable Fatal Error: Object of class AppBundle\Entity\Films could not be converted to string
500 Internal Server Error - ContextErrorException
I have read answers that say to add in the function to my Films.php:
public function __toString() {
return $this->name;
}
However, when I do that, I then get the error:
Error: Method AppBundle\Entity\Films::__toString() must not throw an exception
Other possible ideas I've come across online (but unfortunately with no success) are:
Setting the Forms as services
Data transformers
Add 'choice_label' => 'film_title', to your form builder. That or you can implement a __toString() function within your Film entity that returns the film title.
Solution:
$builder
->add('actorName')
->add('film','entity',array(
'class'=>'AppBundle:Films',
'choice_label' => 'film_title',
'query_builder'=>function(EntityRepository $er) {
return $er->createQueryBuilder('f')
->orderBy('f.film_title','ASC');
}
));
Also, you may want to keep your entity names singular (eg: Film, not Films; Actor not Actors) as this may cause unnecessary issues when dealing with x-to-many entity relationships.

Doctrine 2 Modular App With Entity Replacement

I'm building an application using ZF2 and Doctrine2.
The ideia is to have a base app entity (lets call it UserEntity).
But in one Module A, I will have another UserEntity-like entity that will "upgrade" the base one, with new fields. And another Module B that will add more fields.
Ex:
BaseUserEntity {
protected $id;
// ...
}
ModuleAUserEntity extends BaseUserEntity {
protected moduleAId;
}
ModuleBUserEntity extends BaseUserEntity {
protected moduleBUserName;
}
Is it possible, somehow, to get a method so when I call UserEntity, it will return the full, upgraded-by-module, entity? Ex:
UserEntity {
protected $id;
// ...
protected moduleAId;
protected moduleBUserName;
}
Is there another way to achieve something like this? The possibility to "extension" of an entity?
I have 2 different approaches:
1.First one:
you should take a look at:
http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html
thats the legitimate and recommend by doctrine way of doing this.
2. Second one
If you dont want doctrine to mess with the database, you can use a different aproach:
Create an interface that defines the common behavior of all classes
Write your base class, implementing that behavior.
write your child classes, implementing the interface, containing the new methods, and wrapping an instance of the base class
You implement the methods that are defined in the interface, but since they are already implemented in the parent class, you just bypass the call to the wrapped object.
So, you are using composition over inheritance to avoid doctrine (and probably you) to get crazy
In order to have a really clean behavior with doctrine, the database that i imagine is:
a table with the parent entity
a table with the child entity, containing
a foreign key with the id of the related parent entity (this is, the row in the parent table that contains the values asociated to this, since the child has to have the parent and the child fields)
all the extra columns
For instance:
Interface:
namespace DBAL\Entity;
interface IProfesional
{
public function setName($name);
public function getName();
public function getId();
}
Parent class:
namespace DBAL\Entity;
use Doctrine\ORM\Mapping as ORM;
use DBAL\Entity\User\IUserAware;
/**
* Profesional
*
* #ORM\Table(name="profesional")
* #ORM\Entity
*/
class Profesional implements IProfesional
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=45, nullable=true)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Set nombre
*
* #param string $nombre
* #return Profesional
*/
public function setName($nombre)
{
$this->name = $nombre;
return $this;
}
/**
* Get nombre
*
* #return string
*/
public function getNombre()
{
return $this->name;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
child class:
namespace DBAL\Entity;
use DBAL\Entity\User\IUserAware;
use Doctrine\ORM\Mapping as ORM;
/**
* Jugador
*
* #ORM\Table(name="jugador")
* #ORM\Entity(repositoryClass="DBAL\Repository\JugadorRepository")
*/
class Player implements IProfesional
{
/**
* #var integer
*
* #ORM\Column(name="profesional_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Profesional
*
* #ORM\OneToOne(targetEntity="Profesional")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="profesional_id", referencedColumnName="id", unique=true)
* })
*/
private $profesional;
/**
* Constructor: you create an empty Profesional,
so you are sure you will never use a reference to an in-existent object.
But anyways, if this is an entity loaded from the database by doctrine,
doctrine will fill that field whith an actual professional from the parent table,
based in the foreign key id
*/
public function __construct()
{
if(!isset($id)){
$this->profesional=new Profesional();
}
}
/**
* Set profesional
*
* #param \Profesional $profesional
* #return Jugador
*/
public function setProfesional( Profesional $profesional = null)
{
$this->profesional = $profesional;
return $this;
}
/**
* Get profesional
*
* #return \Profesional
*/
public function getProfesional()
{
return $this->profesional;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->profesional->getId();
}
///New fields, for instance:
/**
* #var integer
*
* #ORM\Column(name="height", type="integer", nullable=true)
*/
private $height;
public function getHeight()
{
return $this->height;
}
public function setHeight($h)
{
$this->height=$h;
}
}

JAXB Jackson - How to handle maps?

I have an web application in which I generate POJOs from my domain objects. One of my domain objects contain a map and JAXB generates the following schema:
<xs:element name="persons">
<xs:complexType>
<xs:sequence>
<xs:element name="entry" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="key" minOccurs="0" type="xs:string"/>
<xs:element name="value" minOccurs="0" type="person"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
This is generated from HashMap<String, Person> persons:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "personConfiguration", propOrder = {
"persons",
})
#XmlRootElement(name = "personConfiguration")
public class PersonConfiguration
{
#XmlElement(required = true)
protected PersonConfiguration.Persons persons;
/**
* Gets the value of the persons property.
*
* #return
* possible object is
* {#link PersonConfiguration.Persons }
*
*/
public PersonConfiguration.Persons getPersons() {
return persons;
}
/**
* Sets the value of the persons property.
*
* #param value
* allowed object is
* {#link PersonConfiguration.Persons }
*
*/
public void setPersons(PersonConfiguration.Persons value) {
this.persons = value;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"entry"
})
public static class Persons
{
protected List<PersonConfiguration.Persons.Entry> entry;
/**
* Gets the value of the entry property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the entry property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEntry().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {#link PersonConfiguration.Persons.Entry }
*
*
*/
public List<PersonConfiguration.Persons.Entry> getEntry() {
if (entry == null) {
entry = new ArrayList<PersonConfiguration.Persons.Entry>();
}
return this.entry;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"key",
"value"
})
public static class Entry
{
protected String key;
protected Person value;
/**
* Gets the value of the key property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getKey() {
return key;
}
/**
* Sets the value of the key property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setKey(String value) {
this.key = value;
}
/**
* Gets the value of the value property.
*
* #return
* possible object is
* {#link Person }
*
*/
public Person getValue() {
return value;
}
/**
* Sets the value of the value property.
*
* #param value
* allowed object is
* {#link Person }
*
*/
public void setValue(Person value) {
this.value = value;
}
}
}
}
As one can see JAXB added this extra level of indirection entry -> key, value. Other pieces of the puzzle are Spring MVC, REST call using JSON objects.
Now XML based REST Calls work fine with object schema above but when send the same call with JSON message with the same schema I get a JSONMappingException.
Any ideas of why this might be happening?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
When using different XML and JSON-binding providers it is difficult to keep the XML and JSON representations consistent. Below is and example of how this can be simplified using MOXy as both your XML and JSON provider with all mapping information provided as JAXB annotations.
Root
Below is a sample domain object in which the persons field would generate the XML schema fragment from your question.
package forum13784163;
import java.util.Map;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
Map<String, Person> persons;
}
Person
Below is a sample of what your Person class might look like. Note how I have mapped the id field to an XML attribute.
package forum13784163;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
#XmlAttribute
int id;
String name;
int age;
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
input.json
Below is what the JSON representation would look like if MOXy is used as your JSON binding provider.
{
"persons" : {
"entry" : [ {
"key" : "Jane",
"value" : {
"id" : 123,
"name" : "Jane",
"age" : 30
}
} ]
}
}
Demo
In the demo code below the JSON is unmarshalled into objects and then those same objects are marshalled to XML. This is done from one JAXBContext that contains one set of metadata.
package forum13784163;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
StreamSource json = new StreamSource("src/forum13784163/input.json");
Root root = unmarshaller.unmarshal(json, Root.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the resulting XML.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<persons>
<entry>
<key>Jane</key>
<value id="123">
<name>Jane</name>
<age>30</age>
</value>
</entry>
</persons>
</root>
For More Information
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html