Best practice - Check if Entity exist before presist - symfony

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...

Related

No identifier/primary key specified for Entity why using KNP Translatable

I am using Knp/DoctrineBehaviors to translate my database content.
I followed the manual and created 2 entities, 1 for non-translatable content and the other for translatable fields.
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
/**
* Class Test
* #package App\Entity
* #ORM\Entity()
*/
class Test implements TranslatableInterface
{
use TranslatableTrait;
/**
* #var integer
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id()
*/
private $id;
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
}
and the Translation entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait;
/**
* Class TestTranslation
* #package App\Entity
* #ORM\Entity()
*/
class TestTranslation implements TranslationInterface
{
use TranslationTrait;
/**
* #var
* #ORM\Column(type="string")
*/
private $name;
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name): void
{
$this->name = $name;
}
}
I also added the Bundle in my bundles.php file.
But when I run the command php bin/console doctrine:schema:update --force to create the table in give the error: No identifier/primary key specified for Entity "App\Entity\TestTranslation". Every Entity must have an identifier/p rimary key.
A primary key is missing in your entity "TestTranslation".
In doctrine every entity class must have an identifier/primary key. You can select the field that serves as the identifier with the #Id() annotation or in your case : #ORM\Id().
For example, add this to your entity :
/**
* #var integer
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id()
*/
private $id;
And then, you can play the command: php bin/console d:s:u --force
In my case, Symfony 3.4, I forgot to register the bundle and set the config.yml as:
knp_doctrine_behaviors:
translatable: true

Doctrine doesn't update/generate fields of ManyToOne and OneToMany

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
{
...
}

How to expose a property of related object in serializer configuration in Symfony 2?

I use FOSRestBundle with JMSSerializerBundle in my web service.
Here is a function in my action controller that returns a list of roles. I don't know how to return a "parent_id" field in this list.
<?php
use FOS\RestBundle\Controller\FOSRestController;
class RoleController extends FOSRestController
{
...
/**
* List all roles.
*
* #Annotations\View()
*
* #return array
*/
public function getRolesAction()
{
$roles = $this->repository->findRoles();
$view = $this->view($roles, 200);
return $this->handleView($view);
}
...
}
Here is my role entity. It has properties: id, name and parent. Parent is a role.
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Role
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\RoleRepository")
*/
class Role
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Role")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
// + getters and setters
...
}
Here is my serializer config file:
AppBundle\Entity\Role:
exclusion_policy: ALL
properties:
id:
expose: true
type: integer
xml_attribute: true
name:
expose: true
type: string
Now getRolesAction in action controller will output JSON with id and name properties. How to add parent_id property to the serializer output?
You could use something like:
/**
* #Serializer\VirtualProperty
* #Serializer\Type("integer")
* #Serializer\SerializedName("parent_id")
*
* #return integer
*/
public function getParentId()
{
return $this->parent->getId();
}
I can't tell you how to transfer the annotation in your config (probably YAML) format, but you'll be able to do that.
IMPORTANT
This only works for serialization - so don't expect this attribute to be deserialized later!!
Alternative:
If you only want to achieve the result you were asking for you could add this to your attribute:
/**
* #Accessor(getter="getParentId",setter="setParent") */
* #Serializer\Type("integer")
* #Serializer\SerializedName("parent_id")
*/
private $parent;
and additionally another getter:
/**
*
* #return integer
*/
public function getParentId()
{
return $this->parent->getId();
}
With the accessor you tell JMS from which getter to get the result to serialize while still being able to use the setter for deserialization.

FOSUserBundle add properties

i've extended FOSUserBundle with my custom User Entity in this way:
<?php
namespace Hu\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Entity\User as BaseUser;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="fname", type="string", length=255)
* #Assert\NotBlank()
*/
private $fname;
// other fields...
/**
* Set fname
*
* #param string $fname
* #return UserEntity
*/
public function setFname($fname)
{
$this->fname = $fname;
return $this;
}
/**
* Get fname
*
* #return string
*/
public function getFname()
{
return $this->fname;
}
public function __construct()
{
parent::__construct();
// ...
$this->fname = setFname($fname);
}
}
But when i try to load the /register route or to add a new user by:
php app/console fos:user:create
Symfony returns me:
FatalErrorException: Error: Call to undefined function Hu\UserBundle\Entity\setFname() in /Library/WebServer/Documents/sfprojects/quattro/src/Hu/UserBundle/Entity/User.php
What's wrong in the constructor? What i miss?
Thanks a lot,
setFname isn't function, but it is method in your class.
so to execute it you need use $this->setFname($fname).
Also in constructor you shouldn't run methods like setters or getters (if they don't do anything except set variable or get variable)
Your constructor should look like:
public function __construct()
{
parent::__construct();
$this->fname = $fname;
}
the commande " php app/console fos:user:create " uses the setters of the entity User to affect data inputs from the terminal ( in the same way that Forms uses them to affect the data to an object from the < input ... > tag ) .
You can add setters for all the fields you added to your User class to solve this problem
exemple :
/**
* #var string
*
* #ORM\Column(name="fname", type="string", length=255)
* #Assert\NotBlank()
*/
private $fname;
public function setFname($fname) {
$this->fname = $fname;
}

Symfony2/Doctrine2 Inheritance

I'm attempting to accomplish BASIC inheritance in Doctrine 2, but I'm running into several major issues. Such a task should not be so complicated. Let's get down to business...
I have three classes, BaseFoodType, Drink, and Snack. My BaseFoodType has the following class definition:
/** #ORM\MappedSuperclass */
class BaseFoodType {
/**
* #ORM\Column(type="integer", length=7)
*/
public $budget = 0;
}
Which follows the instructions for inheritance on the doctrine website: http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html
Here is what the sub-classes look like prior to generating my entities:
namespace MySite\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MySite\MainBundle\Entity\EventDrink
*
* #ORM\Table(name="drink")
* #ORM\Entity
*/
class Drink extends BaseFoodType {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="integer", length=5, nullable=true)
*/
public $people_count;
}
Both Drink, and Snack inherit from this base class but I'm running into numerous issues when attempting to build my entities using the doctrine:generate:entities command. First, Symfony inserts a private "budget" property into each subclass, along with getters and setters (THIS DEFEATS THE PURPOSE INHERITANCE)
/**
* #var integer
*/
private $budget;
/**
* Set budget
*
* #param integer $budget
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* #return integer
*/
public function getBudget()
{
return $this->budget;
}
Second, I'm getting a fatal error:
Fatal error: Access level to MySite\MainBundle\Entity\Drink::$budget
must be public (as in class MySite\MainBundle\Entity\BaseFoodType) in
C:\xampp\htdocs\MySite\src\MySite\MainBundle\Entity\Drink.php on line
197
I could probably make the generated properties public and be on my way, but again, that defeats the purpose of inheritance!
Thanks in advance for any insight.
Doctrine provides the means to specify the visibility of generated fields. Either protected or private. The default is private.
The problem is that the Symfony command that invokes Doctrine offers no way to change this.
Creating your own subclass of the standard Symfony command will allow you more control over the generation process. This might help you along.
namespace Foo\Bundle\FooBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Command as DC;
use Doctrine\ORM\Tools\EntityGenerator;
class GenerateEntitiesDoctrineCommand extends DC\GenerateEntitiesDoctrineCommand
{
protected function configure()
{
parent::configure();
$this->setName('foo:generate:entities');
}
/**
* get a doctrine entity generator
*
* #return EntityGenerator
*/
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(true);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
return $entityGenerator;
}
}
This does two things. It sets the property visibility to protected. This prevents php errors.
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
It also copies the annotations from mapped super class into the entity class.
$entityGenerator->setGenerateAnnotations(true);
Here's some example code where properties are inherited from a base class and their visibility and annotations copy correctly into the inheriting class
/** #ORM\MappedSuperclass */
class DataSuper {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Campaign", inversedBy="data")
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* #Exclude
*/
protected $campaign;
/**
* #ORM\Column(type="text", nullable=true, name="data")
*/
protected $data;
/**
* #ORM\Column(type="datetime")
*/
protected $createdDate;
}
/**
* #ORM\Entity(repositoryClass="Foo\Bundle\FooBundle\Entity\DataRepository")
* #ORM\Table(name="data")
* #ExclusionPolicy("none")
*/
class Data extends DataSuper
{
}
After generation the Data class looks like:
class Data extends DataSuper
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="data", type="text", precision=0, scale=0, nullable=true, unique=false)
*/
protected $data;
/**
* #var \DateTime
*
* #ORM\Column(name="createdDate", type="datetime", precision=0, scale=0, nullable=false, unique=false)
*/
protected $createdDate;
/**
* #var \Foo\Bundle\FooBundle\Entity\Campaign
*
* #ORM\ManyToOne(targetEntity="Foo\Bundle\FooBundle\Entity\Campaign", inversedBy="data")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id", nullable=true)
* })
*/
protected $campaign;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set data
*
* #param string $data
* #return Data
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* #return string
*/
public function getData()
{
return $this->data;
}
/**
* Set createdDate
*
* #param \DateTime $createdDate
* #return Data
*/
public function setCreatedDate($createdDate)
{
$this->createdDate = $createdDate;
return $this;
}
/**
* Get createdDate
*
* #return \DateTime
*/
public function getCreatedDate()
{
return $this->createdDate;
}
/**
* Set campaign
*
* #param \Foo\Bundle\FooBundle\Entity\Campaign $campaign
* #return Data
*/
public function setCampaign(\Foo\Bundle\FooBundle\Entity\Campaign $campaign = null)
{
$this->campaign = $campaign;
return $this;
}
/**
* Get campaign
*
* #return \Foo\Bundle\FooBundle\Entity\Campaign
*/
public function getCampaign()
{
return $this->campaign;
}
}
And the table structure is correct once you do:
php app/console doctrine:schema:update --force
The exception is being thrown because BaseFoodType::budget is a public property and doctrine:generate:entities created a private property in your Drink / Snack classes extending BaseFoodType ( which is not correct but the way the command works by now ).
Property visibility in a subclass can only be the same level or more liberate ( private -> protected -> public ) but never more restrictive.
doctrine:generate:entities did not take superclass's public property into account when generating the getters/setters as the implementation with a public property is non-standard.
Therefore you will have to adjust the generated class manually.
I recommend using private/protected properties combined with getters & setters.

Resources