I have an abstract parent (mapped super-)class which has several children with different properties which I'd like to deserialize.
I'm storing the data using MongoDB and Doctrine ODM, so I also have a discriminator field which tells doctrine which subclass is used (and also have a custom "type" property ontop which is used elsewhere to determine which class is currently processed).
When deserializing my model, I get an exception telling me that its impossible to create an instance of an abstract class (ofcourse) - now I'm wondering how I can tell the JMS Deserializer which inherited class it should use (that is why I use a custom type instance variable for example - because I have no access to doctrine's discriminator field mapping).
I can successfully hook into the preDeserializeEvent- so maybe it is possible to make some switch/cases there (or using the )?
My Model in short (abstract class):
<?php
namespace VBCMS\Bundle\AdminBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use JMS\Serializer\Annotation as Serializer;
/**
* abstract Class Module
* #Serializer\AccessType("public_method")
* #MongoDB\MappedSuperclass
* #MongoDB\InheritanceType("SINGLE_COLLECTION")
* #MongoDB\DiscriminatorField(fieldName="_discriminator_field")
* #MongoDB\DiscriminatorMap({
* "module"="Module",
* "text_module"="TextModule",
* "menu_module"="MenuModule",
* "image_module"="ImageModule"
* })
*/
abstract class Module {
const TYPE_MODULE_TEXT = 'module.text';
const TYPE_MODULE_MENU = 'module.menu';
const TYPE_MODULE_MEDIA_ITEM = 'module.media.item';
/**
* #Serializer\Type("string")
* #MongoDB\Field(type="string")
* #var String
*/
protected $type;
/**
* #Serializer\Type("boolean")
* #MongoDB\Field(type="boolean")
* #var boolean
*/
protected $visible;
// getter/setter methods etc..
}
?>
One of the subclasses
<?php
namespace VBCMS\Bundle\AdminBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use JMS\Serializer\Annotation as Serializer;
/**
* Class TextModule
* #package VBCMS\Bundle\AdminBundle\Document
* #Serializer\AccessType("public_method")
* #MongoDB\EmbeddedDocument
*/
class TextModule extends Module {
const TEXT_TYPE_SPLASH_HEADLINE = 'splashscreen.headline';
const TEXT_TYPE_SPLASH_SUBLINE = 'splashscreen.subline';
/**
* the actual text
*
* #var string
* #Serializer\Type("string")
* #MongoDB\Field(type="string")
*/
protected $text;
/**
* how it is called in the admin interface
*
* #var string
* #Serializer\Type("string")
* #MongoDB\Field(type="string")
*/
protected $label;
/**
* #var string
* #Serializer\Type("string")
* #MongoDB\Field(type="string")
*/
protected $textType;
// getter/setter methods etc..
?>
Another test was to not make the Module class abstract and to create a custom static method
/**
*
* #Serializer\HandlerCallback("json", direction="deserialization")
* #param JsonDeserializationVisitor $visitor
*/
public static function deserializeToObject(JsonDeserializationVisitor $visitor)
{
// modify visitor somehow to return an instance of the desired inherited module class??
}
any ideas?
I found a discriminator mapping in the Tests directory of the plugin, unfortunately, this is not yet documented: https://github.com/schmittjoh/serializer/blob/master/tests/JMS/Serializer/Tests/Fixtures/Discriminator/Vehicle.php
Documentation is updated http://jmsyst.com/libs/serializer/master/reference/annotations#discriminator
namespace JMS\Serializer\Tests\Fixtures\Discriminator;
use JMS\Serializer\Annotation as Serializer;
/**
* #Serializer\Discriminator(field = "type", map = {
* "car": "JMS\Serializer\Tests\Fixtures\Discriminator\Car",
* "moped": "JMS\Serializer\Tests\Fixtures\Discriminator\Moped",
* })
*/
abstract class Vehicle
{
/** #Serializer\Type("integer") */
public $km;
public function __construct($km)
{
$this->km = (integer) $km;
}
}
Related
I have 2 Entities.
Part and Inventory
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $partNumber;
/** #ORM\Column(name="part_name", type="string") */
private $partName;
/** #ORM\Column(type="string") */
private $warehouseStatus;
....
Inventory.php
class Inventory
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One Product has One Shipment.
* #ORM\OneToOne(targetEntity="Part")
* #ORM\JoinColumn(name="part_number", referencedColumnName="part_number")
*/
private $partNumber;
/** #ORM\Column(type="decimal") */
private $inStock;
I create the Part in this way
class one {
private function method1 {
$part = new Part();
$part->partNumber = 'blabla';
$part->warehouseStatus = 1;
.....
}
class two {
private function method1 {
$inv = new Inventory();
$inv->partNumber = 'blabla'; // it crashes here
$inv->inStock = 1;
.....
}
}
In class two I'm trying to make a relation with the first object but partNumber crashes since he is expecting an Entity Object as Part and not a string. Is there an integrated doctrine method to create a reference to the part entity without having to instantiate repositories and so forth.
You need to use the getReference function from the EntityManager for that:
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* #param string $entityName The name of the entity type.
* #param mixed $id The entity identifier.
*
* #return object|null The entity reference.
*
* #throws ORMException
*/
public function getReference($entityName, $id);
In your case:
$inv->partNumber = $entityManager->getReference(Part::class, $thePartIdYouReference);
I have a superclass that currently works fine (all relations and properties are updating to the database)
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\JoinColumn;
use JMS\Serializer\Annotation as JMS;
/**
* Document
*
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document
{
/**
* #var string
*
* #Column(name="id", type="string")
* #Id
* #GeneratedValue(strategy="UUID")
*/
protected $id;
/**
* #var string
* #Column(name="name", type="string", length=255)
*/
protected $name;
/**
* #var string
* #Column(name="type", type="string", length=255)
*/
protected $type;
/**
* #var boolean
* #Column(name="has_attachments", type="boolean")
*/
protected $hasAttachments;
/**
* #ManyToOne(targetEntity="Delivery")
* #JoinColumn(name="delivery_id", referencedColumnName="id", nullable=false)
* #JMS\Exclude()
*/
protected $delivery;
/**
* #OneToMany(targetEntity="Extension", mappedBy="document", cascade={"persist","remove"})
**/
protected $extensions;
public function __construct()
{
$this->extensions = new ArrayCollection();
}
/* getter and setters */
}
Now I've created a entity called Note that extends to Document entity
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
/**
* Note
*
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends Document
{
}
I am suppose that the table/entity note should generate the same things of the class that extends. But not do it
I run php bin/console doctrine:schema:update -f
this only generates properties and not FK (foreing Keys), in this case #ManyToOne and #OneToMany.
Additionally maybe help us, i have those entities on the same database
I am doing something wrong ?
As per docs I think you're missing the #MappedSuperclass annotation or you're using Doctrine inheritance in the wrong way. Be aware that a MappedSupperClass is not an entity by itself instead is just a class for share common methods and properties among it is children classes (same inheritance concept that you should already know).
/**
* #MappedSuperclass
*/
class DocumentSuperClass
{
...
}
/**
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document extends DocumentSuperClass
{
...
}
/**
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends DocumentSuperClass
{
...
}
I have a class
/**
* #ORM\Table(name="registration_number")
* #ORM\Entity
* #ORM\Entity(repositoryClass="PNC\MISDashboardBundle\Repositories\RegistrationNumberRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\Entity#EntityListeners({"RegistrationNumberListener"})
*/
class RegistrationNumber {
}
and the repo class
namespace PNC\MISDashboardBundle\Repositories;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
/**
* RegistrationNumberRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class RegistrationNumberRepository extends EntityRepository {
public function findByTotalMatches($keyword)
{
/* your awesome code block */
return 34;
}
}
and I am calling the method in this way;
$check = $em->getRepository('PNCMISDashboardBundle:RegistrationNumber')
->findTotalMatches(5);
But it says that;
Undefined method 'findTotalMatches'. The method name must start with
either findBy or findOneBy!
I have built lot of other custom repo and works, i don't know that wrongs with this one. has anyone any hint what is wrong with this.
As said in comment,
Change :
/**
* #ORM\Table(name="registration_number")
* #ORM\Entity
* #ORM\Entity(repositoryClass="PNC\MISDashboardBundle\Repositories\RegistrationNumberRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\Entity#EntityListeners({"RegistrationNumberListener"})
*/
class RegistrationNumber {
To :
/**
* #ORM\Table(name="registration_number")
* #ORM\Entity(repositoryClass="PNC\MISDashboardBundle\Repositories\RegistrationNumberRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\EntityListeners({"RegistrationNumberListener"})
*/
class RegistrationNumber {
And it should works.
Whats the best practice to check if entity fields exist before persisting it.
Here's the example
Entity
class Pile{
/**
* #var \ABC\CoreBundle\Entity\Record
*
* #ORM\OneToMany(targetEntity="Record")
*
*/
private $records;
/**
* #var \CSC\CoreBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="Project")
*
*/
private $project;
/**
* #var string
*
* #ORM\Column(name="Block", type="string", length=255)
*/
private $block;
/**
* #var string
*
* #ORM\Column(name="Type", type="string", length=255)
*/
private $type;
}
class Record{
/**
* #var \CSC\CoreBundle\Entity\Pile
*
* #ORM\ManyToOne(targetEntity="Pile")
*
*/
private $records;
}
There are two controllers that handle the CRUD of Pile and Records.
To create Pile there must not be any duplicate fields [project, block, type]
In Record Controllers I could create Pile together with Record.
Here's the problem where and when do I check the db if a similar Pile entity is created?
Whats the Best Practice?
Copy and paste the query checker in both controller?
Can I use $form->valid() to perform any check in PileType class?
Must I use a service and have both controller to call the service?
In entity life-cycle use pre-insert?
Thanks
Therefore, the fields must be unique?
If so, then it is very simple: UniqueEntity
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
...
/**
* #ORM\Entity
* #UniqueEntity(
* fields={"project", "block", "type"}
* )
*/
class Pile{
/**
* #var \ABC\CoreBundle\Entity\Record
*
* #ORM\OneToMany(targetEntity="Record")
*
*/
private $records;
/**
* #var \CSC\CoreBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="Project")
*
*/
private $project;
/**
* #var string
*
* #ORM\Column(name="Block", type="string", length=255, unique=true)
*/
private $block;
/**
* #var string
*
* #ORM\Column(name="Type", type="string", length=255, unique=true)
*/
private $type;
}
You can use a custom validation constraint in your form, so that $form->isValid() will do the check.
Follow this documentation entry on How to create a Custom Validation Constraint to create the custom validator and then inject doctrine into it to do the check.
UPDATE: Well, I didn't know there was an UniqueEntity Constraint already included in Symfony.
To inject doctrine do the following:
services:
validator.unique.unique_pile:
class: ABC\CoreBundle\Validator\Constraints\UniquePileValidator
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: validator.constraint_validator, alias: unique_pile }
The validator class might then look like this:
// src/ABC/CoreBundle/Validator/Constraints/UniquePileValidator.php
namespace ABC\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UniquePileValidator extends ConstraintValidator
{
protected $em;
function __construct($em) {
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$repo = $this->em->getRepository('ABC\CoreBundle\Entity\Record');
$duplicate_project = $repo->findByProject($value);
$duplicate_block = $repo->findByBlock($value);
$duplicate_type = $repo->findByType($value);
if ($duplicate_project || $duplicate_block || $duplicate_type) {
$this->context->addViolation(
$constraint->message,
array('%string%' => $value)
);
}
}
}
And to be complete, the constraint class:
// src/ABC/CoreBundle/Validator/Constraints/ContainsAlphanumeric.php
namespace ABC\CoreBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class ContainsAlphanumeric extends Constraint
{
public $message = 'This Pile already exists!';
public function validatedBy()
{
return 'unique_pile';
}
}
Should be nearly copy/pasteable...
I trying to create CRUD panel from FOSUserBundle but i have some troubles. I mean that i created User entity for FOS and made crud panel for this entity. Now when i trying to add new user i have error like below
Neither the property "expiresAt" nor one of the methods "getExpiresAt()", "isExpiresAt()", "hasExpiresAt()", "_get()" or "_call()" exist and have public access in class "Bn\UserBundle\Entity\User".
It's my first project so please understand when i will ask for simple function, some suggestion ? What is wrong ?
<?php
namespace Bn\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="fos_user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get expiresAt
*
* #return \DateTime
*/
public function getExpiresAt()
{
return $this->expiresAt;
}
/**
* Get credentials_expire_at
*
* #return \DateTime
*/
public function getCredentialsExpireAt()
{
return $this->credentialsExpireAt;
}
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Now is working but i don't know why i must declare again function for getter.
I believe this means you need to add public accessors setExpiresAt() and getExpiresAt() to your User entity.
You need only add getExpiresAt to your User.php class. FOSUserBundle\User doesn't have getter for this field, but Sensio generator creates views for all fields.
public function getExpiresAt()
{
return $this->expiresAt;
}