Doctrine2: efficient mapping of inherited class - orm

I've a problem figuring out how to configure the mapping of my classes with Doctrine2.
Let say I've these tables:
Address table
---------------------
- id
- civic_no
- road
- state
- country
PersonnalAddress table
---------------------
- id
- base_address_id
- type
- is_primary
BusinessAddress table
---------------------
- id
- base_address_id
- business_name
- shipping_phone
- is_primary
And thoses PHP objects:
class Address{}
class BusinessAddress extends Address{}
class PersonalAddress extends Address{}
Considering the following requirements:
An address can exist by itself (the Address class is not abstract)
A personalAddress and a businessAddress can have the very same address data
If I delete or edit the address, it has an impact on all the business or personal address that are inherited from it.
I don't want any data duplication in the database (this is a requirement of the 2nd normal form)
Proxy methods mean code duplication, I prefer not to have any.
Magic methods are not good in term of testability, I prefer not to have any.
To better illustrate the problem, I expect the data in the data base to look like:
Address table:
id | civic_no | road | state | country
1 123 test qc ca
PersonnalAddress table:
id | base_address_id | type | is_primary
1 1 A 0
2 1 B 1
BusinessAddress table:
id | base_address_id | business_name | shipping_phone | is_primary
1 1 chic choc 1231234 1
What would be the best strategy to implement a solution that match theses requirements ?

Ok this is a bit of a long one but I think it covers all your bases, if you have any questions then feel free to ask.
This comes with a caveat that I don't know if you can do Many-To-One on a MappedSuperclass. If that isn't possible then you may be able to use Class Table Inheritance instead. Give it a try and tell us if it works.
Keep in mind I pushed this code out pretty quickly, it is untested so it may not be correct but hopefully you'll get the idea of how it works.
Here we go!
Interface to make it easier to say "what is an address" without making abstract classes then overriding methods and causing the "bad design" feeling ;)
interface Address {
function getCivicNo();
function getRoad();
function getState();
function getCountry();
}
Abstract Entity which you don't really need but if you dont use it you need to duplicate the ID code.
/**
* #ORM\MappedSuperclass
*/
abstract class AbstractEntity {
/**
* Entity ID column.
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
public function getId() {
return $id;
}
}
BasicAddress which you can store alone or have linked to a "ComplexAddress"
/**
* #ORM\Entity
*/
class BasicAddress extends AbstractEntity implements Address {
/** #ORM\Column() */
private $road;
public function getRoad() {
return $this->road;
}
// etc etc
}
"ComplexAddress" is just here to let you re-use the code for delegating calls to a basic address.
/**
* #ORM\MappedSuperclass
*/
abstract class ComplexAddress extends AbstractEntity implements Address {
/** #ORM\Many-To-One(targetEntity="BasicAddress")
private $basicAddress;
public function __construct(BasicAddress $basicAddress) {
$this->basicAddress = $basicAddress;
}
public function getRoad() {
return $this->basicAddress->getRoad();
}
// other methods for implementing "Address" just delegate to BasicAddress
}
PublicAddress
/**
* #ORM\Entity()
*/
class PersonalAddress extends ComplexAddress {
/** #ORM\Column(type="boolean") */
private $isPrimary;
public function isPrimary() {
return $isPrimary;
}
// other personal address methods here
}
BusinessAddress
/**
* #ORM\Entity()
*/
class BusinessAddress extends ComplexAddress {
/** #ORM\Column() */
private $businessName;
public function getBusinessName() {
return $this->businessName;
}
// other business address methods here
}
Edit: Just noticed I forgot to put cascade parameters for deletion, you might need to handle this directly though - when a BasicAddress is deleted also delete the other addresses that use it.

Instead of extending the Address class, you'll have to create a OneToMany relationship on the Address and a ManyToOne relationship on the PersonalAddress and BusinessAddress. Something like this:
<?php
// ...
use Doctrine\Common\Collections\ArrayCollection;
// ...
class Address
{
// ...
/**
* #ORM\OneToMany(targetEntity="PersonalAddress", mappedBy="address")
*/
private $personalAddresses;
/**
* #ORM\OneToMany(targetEntity="BusinessAddress", mappedBy="address")
*/
private $businessAddresses;
public function __construct()
{
$this->personalAddresses = new ArrayCollection();
$this->businessAddresses = new ArrayCollection();
}
// ...
}
And, for the child classes:
<?php
// ...
// ...
class PersonalAddress
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Address", inversedBy="personalAddresses")
* #ORM\JoinColumn(name="base_address_id", referencedColumnName="id")
*/
private $address;
// ...
}

Related

API Platform - Entity Translation, Doctrine Translatable

I'm trying to add KnpLabs Doctrine Behaviors - and precisely, the Translatable Behavior - on one of the entity in my API Platform project.
Here's what I've done so far :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
* #ApiResource
*/
class Article
{
use ORMBehaviors\Translatable\Translation,
ORMBehaviors\Timestampable\Timestampable
;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
And here's the Entity translation :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translatable;
/**
* #var string
*
* #ORM\Column(name="someFieldToTranslate", type="string", length=255, nullable=true)
*/
private $someFieldToTranslate;
public function getSomeFieldToTranslate(){...}
public function setSomeFieldToTranslate($someFieldToTranslate){...}
}
Here's the basic "configuration" for getting Translatable Behavior working according to the doc.
Issues start when I try to update the DB schema : I got this error:
No identifier/primary key specified for Entity "App\Entity\ArticleTranslation". Every Entity must have an identifier/primary key in . (which is being imported from "/Sites/bookshop-api/config/routes/api_platform.yaml"). Make sure there is a
loader supporting the "api_platform" type.
However in Translatable Traits, there's already an ID and documentation precise that Translation Entity should only have fields we want to translate...
Anyway, I've put an ID to this ArtcleTranslation Entity to get rid of the error :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translatable;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="content", type="string", length=255, nullable=true)
*/
private $content;
public function getContent(){...}
public function setContent($someContent){...}
}
From here, no error when I update the DB schema. Perfect !
Now I can take a look at the Swagger Documentation :
Everything looks fine ! But when I do take a look at the DB :
In Article table :
no "local" field
no "empty" field
In ArticleTranslation table :
no "translatable_id" field
no "currentLocal"
no "defaultLocal"
I guess it must be linked but in the swagger POST Tab, the model is different too.
article_translation model
I only tried /GET and /POST method on both entities, they're working (I can see it in DB) but no relation between the 2 of them.
I hope my post is not too long but I tried to be the more specific !
Thanks in advance
I did another answer as the first question was to resolve a mistake and not to explain how I integrated Knp Labs Doctrine Translations with Api Platform.
Summary :
We have an entity Article which has some fields translated inside an ArticleTranslation. We want to retrieve the entity through Api platform with its translations and we want to add or update translations through the api.
What I did :
1 - Article entity has a use ORMBehaviors\Translatable\Translatable. If we look inside of this trait, it has 2 attributes : $translations and $newTranslations. We need to expose these attributes inside the Article entity :
class Article {
use ORMBehaviors\Translatable\Translatable;
protected $translations;
protected $newTranslations;
}
Now we have a new attribute translations which is automatically filled from ArticleTranslation when we are getting some Article from the api
2 - Now we want to add/edit some translations : We need to fill the newTranslations attribute inside the Article when we are sending to the api:
"newTranslations": {
"en": {
"description": "Firstname"
},
"fr": {
"description": "Prénom"
}
}
Now we are receiving the new translations into the api but it's not persisted because we have to call the function mergeNewTranslations(). This function just take all translations inside the attribute $newTranslations and merge it with the $translations attribute in order to persist it.
3 - I created a new trait that I called TranslatableOverride. I imported it on directly on my Entity next to ORMBehaviors\Translatable\Translation:
trait TranslatableOverride
{
/**
* Set collection of new translations.
*
* #return ArrayCollection
*/
public function setNewTranslations($newTranslations)
{
if ($newTranslations) {
foreach ($newTranslations as $locale => $translations) {
foreach ($translations as $key => $value) {
$tr = $this->translate($locale);
$setter = 'set' . ucfirst($key);
if (method_exists($tr, $setter)) {
$tr->{$setter}($value);
}
}
}
$this->mergeNewTranslations();
}
}
}
I'm not sure if it's pretty but it works like a charm with api-platform.
I didn't think about getting only one translation at a time. For the moment, I retrieve my entities with the whole bunch of translations which is definitely not efficient. I will add an override for the getTranslations in my override trait I guess.
I think you did a mistake. You have an entity Article which should be the translatable and you want to translate some fields which will be the translations?
So you should make the opposite, put the use ORMBehaviors\Translatable\Translatableon the Article and the use ORMBehaviors\Translatable\Translation on the ArticleTranslation
The fix for your Article entity :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
* #ApiResource
*/
class Article
{
use ORMBehaviors\Translatable\Translatable,
ORMBehaviors\Timestampable\Timestampable
;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
and the fix for your ArticleTranslation entity :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Traits as CustomTraits;
/**
* #ORM\Entity(repositoryClass="App\Repository\ArticleTranslationRepository")
* #ApiResource
*/
class ArticleTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var string
*
* #ORM\Column(name="someFieldToTranslate", type="string", length=255, nullable=true)
*/
private $someFieldToTranslate;
public function getSomeFieldToTranslate(){...}
public function setSomeFieldToTranslate($someFieldToTranslate){...}
}
Let me know if everything is alright now.

Spring Data Rest ResourceProcessor not applied on Projections

I am using a ResourceProcessor to add additional links to my resource object when listed in a collection or fetched individually. However, when I apply a projection (or an excerpt project) to my repository, the ResourceProcessor does not get run and thus my links for that resource do not get created. Is there a means to allow my custom resource links to be added to a resource regardless of how the resource content is projected?
I think this issue is describing your case:
https://jira.spring.io/browse/DATAREST-713
Currently, spring-data-rest does not offer functionality to solve your problem.
We are using a little workaround that still needs a separate ResourceProcessor for each projection but we do not need to duplicate the link logic:
We have a base class that is able to get the underlying Entity for a Projection and invokes the Entity's ResourceProcessor and applies the links to the Projection.
Entity is a common interface for all our JPA entities - but I think you could also use org.springframework.data.domain.Persistable or org.springframework.hateoas.Identifiable.
/**
* Projections need their own resource processors in spring-data-rest.
* To avoid code duplication the ProjectionResourceProcessor delegates the link creation to
* the resource processor of the underlying entity.
* #param <E> entity type the projection is associated with
* #param <T> the resource type that this ResourceProcessor is for
*/
public class ProjectionResourceProcessor<E extends Entity, T> implements ResourceProcessor<Resource<T>> {
private final ResourceProcessor<Resource<E>> entityResourceProcessor;
public ProjectionResourceProcessor(ResourceProcessor<Resource<E>> entityResourceProcessor) {
this.entityResourceProcessor = entityResourceProcessor;
}
#SuppressWarnings("unchecked")
#Override
public Resource<T> process(Resource<T> resource) {
if (resource.getContent() instanceof TargetAware) {
TargetAware targetAware = (TargetAware) resource.getContent();
if (targetAware != null
&& targetAware.getTarget() != null
&& targetAware.getTarget() instanceof Entity) {
E target = (E) targetAware.getTarget();
resource.add(entityResourceProcessor.process(new Resource<>(target)).getLinks());
}
}
return resource;
}
}
An implementation of such a resource processor would look like this:
#Component
public class MyProjectionResourceProcessor extends ProjectionResourceProcessor<MyEntity, MyProjection> {
#Autowired
public MyProjectionResourceProcessor(EntityResourceProcessor resourceProcessor) {
super(resourceProcessor);
}
}
The implementation itself just passes the ResourceProcessor that can handle the entity class and passes it to our ProjectionResourceProcessor. It does not contain any link creation logic.
Here is a generic solution:
#Component
public class ProjectionProcessor implements RepresentationModelProcessor<EntityModel<TargetAware>> {
private final RepresentationModelProcessorInvoker processorInvoker;
public ProjectionProcessor(#Lazy RepresentationModelProcessorInvoker processorInvoker) {
this.processorInvoker = processorInvoker;
}
#Override
public EntityModel<TargetAware> process(EntityModel<TargetAware> entityModel) {
TargetAware content = entityModel.getContent();
if (content != null) {
entityModel.add(processorInvoker.invokeProcessorsFor(EntityModel.of(content.getTarget())).getLinks());
}
return entityModel;
}
}
It gets links for original entities and adds them to corrseponding projections.

Sylius - Editable Product Code (SKU)

I need to implement an SKU code for products, I was just wondering has anybody any thought on the best way to do this. The SKU needs to be editable after creation.
I feel I have a couple of ways:
(Idealy) I would like to use Product.Code, but this is not an editable field after product creation. I would seem I need to override the ProductType#buildForm class/method to not use AddCodeFormSubscriber(). Although I can't seem to figure out how to get the system to use a different form.
Add SKU to the model of Product and figure out how to add it to the ProductType form and again try and figure out how to use a different form.
I am open to suggestions on how to do it the right way.
Would any of the Sylius developers care to elaborate why they decided to make the Code field un-editable?
If you want use product code as editable field in Sylius Beta.1, you can create ProductType extension to current product type and add your custom subscriber which will make code field editable. I did this in my bundle and it works:
create subscriber class wchich will change disabled state to false:
namespace App\Bundle\Form\EventListener;
/* add required namespaces */
/**
* Custom code subscriber
*/
class CustomCodeFormSubscriber implements EventSubscriberInterface
{
private $type;
private $label;
/**
* #param string $type
* #param string $label
*/
public function __construct($type = TextType::class, $label = 'sylius.ui.code')
{
$this->type = $type;
$this->label = $label;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
/**
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$disabled = false;
$form = $event->getForm();
$form->add('code', $this->type, ['label' => $this->label, 'disabled' => $disabled]);
}
}
create form extension and use custom subscriber:
namespace App\Bundle\Form\Extension;
use App\Bundle\Form\EventListener\CustomCodeFormSubscriber;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Sylius\Bundle\ProductBundle\Form\Type\ProductType;
/* use other required namespaces etc */
/**
* Extended Product form type
*/
class ProductTypeExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
/* custom stuff for ur form */
$builder->addEventSubscriber(new CustomCodeFormSubscriber());
}
public function getExtendedType()
{
return ProductType::class;
}
}
register your form extension as a service:
app.form.extension.type.product:
class: App\Bundle\Form\Extension\ProductTypeExtension
tags:
- { name: form.type_extension, priority: -1, extended_type: Sylius\Bundle\ProductBundle\Form\Type\ProductType }

ZF2 - Doctrine ORM, Simple Table Join

I am currently learning how to use Doctrine ORM with ZF2 and currently my goal is to retrieve data from a simple table join and display it to screen.
I have read the documents and it looks pretty simple to do.
These are my tables:
user
------------------------
|user_id | name | email |
--------------------------
| 1 | John | j#b.com |
--------------------------
| 2 | Bob | b#j.com |
--------------------------
user_role_linker
--------------------------
|user_id | role_id |
--------------------------
| 1 | administrator |
--------------------------
| 2 | staff |
--------------------------
What I want to achieve is a list to my view as follows:
ID Name Email Role Actions
--------------------------------------------------------
1 John j#b.com Administrator Edit
2 Bob b#j.com Staff Edit
--------------------------------------------------------
Paging goes here
----------------
This is what I currently have and it seems to work except I am not sure how to grab the joined table data:
User entity::
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\ManyToMany;
use Doctrine\ORM\Mapping\JoinTable;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\Common\Collections\ArrayCollection;
/** #ORM\Entity */
class User {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="integer", name="parent_id") */
protected $parent_id;
/** #ORM\Column(type="string", name="name") */
protected $name;
/** #ORM\Column(type="string", name="email") */
protected $email;
//Setters and getters
public function getUserId() {
return $this->user_id;
}
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
}
/**
* #ManyToMany(targetEntity="UserRoleLinker")
* #JoinTable(
* name="user_role_linker",
* joinColumns={
* #JoinColumn(
* name="user_id",
* referencedColumnName="id")
* },
* inverseJoinColumns={
* #JoinColumn(
* name="user_id",
* referencedColumnName="id",
* unique=true)
* })
*/
private $role_id;
public function __construct()
{
$this->role_id = new ArrayCollection();
}
/** #return Collection */
public function getRoleId()
{
return $this->role_id;
}
}
User role linker entity::
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity */
class UserRoleLinker {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="string", name="role_id") */
protected $role_id;
/** #param User|null */
public function getRoleId() {
return $this->role_id;
}
}
My Administration controller::
public function usersAction() {
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$userFunctions = new UserFunction($em);
$userArray = $userFunctions->getUsers();
$viewModel = new ViewModel(array('users' => $userArray));
return $viewModel;
}
This calls my UserFunctions class::
public function getUsers()
{
//This function returns the users
return $this->em->getRepository('Administration\Entity\User')->findAll();
}
And in my view I list the data like this:
<?php
foreach ($this->users AS $user) {
?>
<tbody>
<tr class="odd gradeX">
<td width="5%"><?php echo $user->getUserId(); ?></td>
<td><?php echo $user->getName(); ?></td>
<td><?php echo $user->getEmail(); ?></td>
<td class="center">*** HOW DO I SHOW THE ROLE ?? ***</td>
<td>Edit</td>
</tr>
<?php } ?>
How do I grab the role to show in the view?
You have to define the correct relationship between user and role in your entity definition.
I am not sure why you use a linker table here. Do you want to actually have a many to many relationship between user and roles (every user can have several roles?).
If not you can easily move the role id into the user table and then you define a ManyToOne relationship between user and role.
Your user table would then look like this:
-------------------------------------------
|user_id | name | email | role_id |
-------------------------------------------
| 1 | John | j#b.com | administrator |
-------------------------------------------
| 2 | Bob | b#j.com | staff |
-------------------------------------------
I would suggest to take a look at ManyToOne with user as the owning side. You can check how to properly define your unidirectional many to one relation inside your entity definition here in the Doctrine2 documentation
After that you can simply call $user->getRole(); in your view...
EDIT
Answer to fix a one to many using a join table:
This is also described in the doctrine documentation here...
You need three tables; a user table, a role table and a user-role-linker table
The user is an entity, the role is an entity the role-linker table is not an entity in your case. You should drop that entity, the linker table is only used for connecting the user and role in the database.
User table
---------------------------
|id | name | email |
---------------------------
| 1 | John | j#b.com |
---------------------------
| 2 | Bob | b#j.com |
---------------------------
Role table
-----------------
| id |
-----------------
| administrator |
-----------------
| staff |
-----------------
| guest |
-----------------
| another role |
-----------------
Linker table
--------------------------
|user_id | role_id |
--------------------------
| 1 | administrator |
--------------------------
| 2 | staff |
--------------------------
In your user entity:
/** ONE-TO-MANY UNIDIRECTIONAL, WITH JOIN TABLE ONLY WORK WITH MANY-TO-MANY ANNOTATION AND A UNIQUE CONSTRAINT
* #ORM\ManyToMany(targetEntity="Administration\Entity\Role")
* #ORM\JoinTable(name="user_role_linker",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id", unique=true)}
* )
*/
protected $roles;
/**
* Get roles.
*
* #return ArrayCollection
*/
public function getRoles()
{
return $this->roles;
}
/**
* Add a role to the user.
*
* #param Role $role
*
* #return User
*/
public function addRole(Role $role)
{
$this->roles[] = $role;
return $this;
}
/**
* Remove a role from the user
*
* #param Role $role
*
* #return User
*/
public function removeRole(Role $role)
{
$this->roles->removeElement($role);
return $this;
}
Your role entity:
/**
* An example entity that represents a role.
*
* #ORM\Entity
* #ORM\Table(name="role")
* #property string $id
*/
class Role
{
/**
* #var string
* #ORM\Id
* #ORM\Column(type="string", length=255, unique=true, nullable=false)
*/
protected $id;
/**
* Get the id.
*
* #return string
*/
public function getId()
{
return $this->id;
}
}
I think this should help you solve it...
Maybe you should add the following to your application module:
public function doctrineValidate(MvcEvent $event){
$application = $event->getParam('application');
$serviceManager = $application->getServiceManager();
$entityManager = $serviceManager->get('doctrine.entitymanager.orm_default');
$validator = new SchemaValidator($entityManager);
$errors = $validator->validateMapping();
if (count($errors) > 0) {
// Lots of errors!
var_dump($errors);
}
}
And then in bootstrap:
$eventManager->attach('dispatch', array($this, 'doctrineValidate'));
in module:
Doctrine will help you by checking your entity definitions. It might tell you in advance that something is wrong in your entity definitions...
It's confusing, but as you defined in your entities, you could get the roles collection using User entity getRoleId's function. Then, for each UserRoleLinker entity you have to use, again, the getRoleId function, which will return the 'Administrator' or 'Staff' string. Loop Example:
$roles = $user->getRoleId();
foreach ( $roles as $role ) {
$roleId = $role->getRoleId();
}
I suggest you to do it another way. One entity should be User, with a property called roles. On the other side you could have the entity Role. The link between them should be a One-To-Many, Unidirectional with Join Table (which is the user_role_linker table).
As suggested by lluisaznar (although I need a many to 1 relationship since each user only has one role).
I am trying the following:
<?php
namespace Administration\Entity;
//use stuff
/** #ORM\Entity */
class User {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
//setters getters
/**
* #ManyToOne(targetEntity="Role")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $role;
public function __construct()
{
$this->role = new ArrayCollection();
}
/** #return Collection */
public function getRoleId()
{
return $this->role;
}
}
And the Role entity:
<?php
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity */
class Role {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer",name="user_id")
*/
protected $user_id;
/** #ORM\Column(type="string", name="role_id") */
protected $role;
/** #param User|null */
public function getRoleId() {
return $this->role;
}
}
When I run this I get the following notices:
Notice: Undefined index: id in trunk/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 2611
and
Notice: Undefined index: user_id in trunk/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php on line 121
Also the role is not being displayed, when I dump the $user object under role I get this:
private 'role' =>
object(DoctrineORMModule\Proxy\__CG__\Administration\Entity\Role)[609]
public '__initializer__' =>
object(Closure)[595]
public '__cloner__' =>
object(Closure)[596]
public '__isInitialized__' => boolean false
protected 'user_id' => null
protected 'role' => null

Why doesn't #JsonUnwrapped work for Lists?

I am using Jackson 2.1.0. Given:
public static final class GetCompanies
{
private final List<URI> companies;
/**
* Creates a new GetCompanies.
* <p/>
* #param companies the list of available companies
* #throws NullPointerException if companies is null
*/
#JsonCreator
public GetCompanies(#JsonUnwrapped #NotNull List<URI> companies)
{
Preconditions.checkNotNull(companies, "companies");
this.companies = ImmutableList.copyOf(companies);
}
/**
* #return the list of available companies
*/
#JsonUnwrapped
#SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<URI> getCompanies()
{
return companies;
}
}
When the input list contains http://test.com/, Jackson generates:
{"companies":["http://test.com/"]}
instead of:
["http://test.com/"]
Any ideas?
UPDATE: See https://github.com/FasterXML/jackson-core/issues/41 for a related discussion.
In this case, if this was to work, you'd end up trying to produce following:
{ "http://test.com" }
which is not legal JSON. #JsonUnwrapped really just removes one layer of wrapping. And although it theoretically could be made to work for "arrays in arrays" case, it does not.
And in fact I wonder if adding this feature was a mistake: mostly because it encourages use that is often against data-binding best practices (simplicity, one-to-one mapping).
But what would work instead is #JsonValue:
#JsonValue
private final List<URI> companies;
which means "use value of this property instead of serializing the object that contains it".
And the creator method would actually work as-is, no need for either #JsonUnwrapped or #JsonProperty.
Here is the corrected code:
public static final class GetCompanies
{
private final List<URI> companies;
/**
* Creates a new GetCompanies.
* <p/>
* #param companies the list of available companies
* #throws NullPointerException if companies is null
*/
#JsonCreator
public GetCompanies(#NotNull List<URI> companies)
{
Preconditions.checkNotNull(companies, "companies");
this.companies = ImmutableList.copyOf(companies);
}
/**
* #return the list of available companies
*/
#JsonValue
#SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<URI> getCompanies()
{
return companies;
}
}