Symfony - Validate entity differently in INSERT, UPDATE or DELETE - doctrine-orm

I want validate an entity doctrine differently when the entity is created, updated or deleted.
There is an entity constraint validator in my entity class.
// src/AppBundle/Entity/AcmeEntity.php
use AppBundle\Validator\Constraints as AcmeAssert;
/**
* #AcmeAssert\CustomConstraint
*/
class AcmeEntity
{
// ...
protected $name;
// ...
}
In my CustomConstraint I want determine if the Entity will be updated, created or delete for execute a specific validator.
Using unit of work is a solution ?
What is the best way to make this?
I think this problematic is common in lot of application ?
Thank's all ;)

You could either use validation groups based on the submitted data or handle itwhen you create the form by passing the validation group.
For example, in your controller when you create the form;
$form = $this->createForm(new AcmeType(), $acme, ['validation_groups' => ['create']]);
Then you entity would be something like;
/**
* Get name
*
* #Assert\Length(min=2, max=11, groups={"create", "update"})
* #AcmeAssert\ContainsAlphanumeric(groups={"create"}) // only applied when create group is passed
* #return string
*/
public function getName()
{
return $this->name;
}

This is what validation groups are made for.
Since Symfony Forms read validations from entity annotations and use internally the Validator component you'd have a look at these articles in the documentation:
http://symfony.com/doc/current/form/validation_groups.html
http://symfony.com/doc/current/validation/groups.html
http://symfony.com/doc/current/validation/sequence_provider.html

Related

Symfony 4: Updating an existing entity through a deserialized entity ends up with a new related item or with an ORMInvalidArgumentException

That's my first question here on Stackoverflow.com and before I'll write to much. First the controller function:
/**
* #Rest\Patch("/identifiers/v2/{id}")
*
* #ParamConverter("identifier")
* #ParamConverter("identifierPatch", converter="fos_rest.request_body")
*/
public function patchAction(Identifier $identifier, Identifier $identifierPatch)
{
$identifier->setLandingPage($identifierPatch->getLandingPage());
$identifier->setIdentifier($identifierPatch->getIdentifier());
$identifier->setIsUsed($identifierPatch->getIsUsed());
$this->entityManager->flush();
/**
* Just for debugging...
*/
$view = $this->view([
'identifier' => $identifier,
'identifierPatch' => $identifierPatch
]);
return $this->handleView($view);
}
When i try to UPDATE an existing entity this way I get an ORMInvalidArgumentException with a message "A new entity was found through the relationship (...)"
When I set cascade={"persist"} on the related entity:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\LandingPage", inversedBy="identifiers")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotNull()
* #Serializer\Type("App\Entity\LandingPage")
*/
private $landing_page;
... the related entity will be inserted as new entity and that's not what I am looking for.
I could use $this->entityManager->merge($identifier) but that's not what I am looking for aswell, because I'll need to do some manual validations in future and I would like to return the entity as response (the related entity will be null when not updated) and $this->entityManager->merge() will be deprecated in Doctrine 3.
Question: Is there any way to update the given entity with the deserialized entity?
Greetings,
Danny Endert
EDIT (1):
Well, I guess i found a solution regarding this "issue".
services.yaml
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
Now I'm not getting any exception and the related entity will not be inserted as new entity.

Cleanest way to identify an entity with Doctrine inheritance mapping in zf2

I am using Doctrine inheritance mapping in a project which produces a set of unique entities that each extend a base entity. Because the route is not aware of which entities go with which base rows, I have to query the database twice in order to grab the row I want from the right fieldset:
// in a controller action:
// locate the event entity record and determine the event type
$entity = 'AdminEvents\Entity\Event';
$event = $this->getEntityManager()->find($entity, $eventID);
$eventType = $this->getEntityManager()->getClassMetadata(get_class($event))->discriminatorValue;
// locate the record we're really looking for in the unique extended entity
$entity = 'AdminEvents\Entity\\' . $eventType;
$event = $this->getEntityManager()->find($entity, $eventID);
Is there a cleaner way to do this?
You should probably define an \AdminEvents\Entity\AbsractEvent class, if you haven't already. Then each of your STI entities should extend this, and you can do instanceof (or other logic) to find out what concrete type you got:
// locate the record using the AbstractEntity
$entity = 'AdminEvents\Entity\AbstractEntity';
$event = $this->getEntityManager()->find($entity, $eventID);
A word of caution: the SPL function, get_class will often return the Doctrine Proxy class, so don't rely on that directly to test the return type. You can use the Doctrine class 'ClassUtils'
\Doctrine\Common\Util\ClassUtils::getRealClass(get_class($event));

Initializing empty relationships in entities

I have entities with 1:1 or 1:M relations to other entities. All relations however are nullable.
I want to proxy some operations to the related entity. I'm giving example below. The problem is that if the relation still does not exist, I have null, so I'm ending up constantly checking for nulls, which obviously is wrong. What I would like to do is to hydrate my entities with empty objects. Reasons:
Doctrine knows what instance should be created for the field anyway. So it should just provide empty instance instead of null
I don't want to fill my code with initializations, like
$object->setSettings(new SettingsEntity)
If the requests should be proxied is somehow disputable, but I want to hide the DB representation from the client code. If my direction however is totally wrong, please point me to the right direction. I may accept that this is responsibility of the model, not of the entity, but Doctrine always returns entities to me
Sure, I can add the initialization either in the constructor of the entity, or to provide getter that creates a new instance of the object, if such does not exists. There are couple of reasons I don't want this:
I don't know how objects are actually hydrated. I assume such initialization should happen in an event and not in the constructor
I don't want to write the code for each entity (at some point, someone will forget to add the initialization in the getter) and want to make it automatically for each relation instead.
Some example code:
/**
* SomeObject
* #ORM\Entity()
* #ORM\Table(
name="some_object"
* )
*/ class SomeObject implements DataTransfer {
/**
* #ORM\OneToOne(targetEntity="Settings", mappedBy="SomeObject")
*/
protected $settings;
public function getSettings() {
return $this->settings;
}
public function get() {
$record = new \stdClass();
$record->id = $this->getId();
...
$settingsObject = $this->getSettings();
$record->someKey = $settingsObject ? $settingsObject->getSomeKey() : null;
$record->someOtherKey = $settingsObject ? $settingsObject->getSomeOtherKey() : null;
return $record;
}
Any suggestions, including hacking Doctrine, are welcome.
P.S. Doctrine-ORM version is 2.3. I can upgrade if this will help solving the problem.
I won't discuss your proxy-thingie-theory: your code, your design, I don't have enough knowlegde of these to have an opinion.
About you knowing how Doctrine hydrates its entities, you can see how it's done in \Doctrine\ORM\UnitOfWork::createEntity. It doesn't seem to invoke the constructor (uses \ReflectionClass::newInstanceWithoutConstructor, which obviously shouldn't use the constructor), but you may be interested in listening to Doctrine's post-load event (part of the lifecycle events logic).
About initializing your null properties, i.e. the code that your post-load event should trigger, you should begin by having a superclass over all of your entities: instead of class SomeObject implements DataTransfer {...}, you'd have class SomeObject extends MyEntity {...} (and have MyEntity implement DataTransfer to keep your interface). This MyEntity class would be a "mapped superclass", it would be annotated with #HasLifecycleCallbacks, and declare a method annotated with #PostLoad. There you have your hook to run your null-to-something code.
For this code to be generic (as it'd be coded from this superclass), you can rely on Doctrine's entity metadata, which retains association mappings and all data that the Unit Of Work needs to figure out its low-level DB-accessing business. It should look like the following:
/** #HasLifecycleCallbacks #MappedSuperclass ... */
public class MyEntity implements DataTransfer {
...
/** #PostLoad */
public function doPostLoad(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) { //the argument is needed here, and is passed only since 2.4! If you don't want to upgrade, you can work around by using event listeners, but it's more complicated to implement ;)
$em = $event->getEntityManager();
$this->enableFakeMappings($em);
}
private function enableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$mappings = $em->getClassMetadata(get_class($this))->getAssociationMappings(); //try and dump this $mappings array, it's full o'good things!
foreach ($mappings as $mapping) {
if (null === $this->{$mapping['fieldName']}) {
$class = $mapping['targetEntity'];
$this->{$mapping['fieldName']} = new $class(); //this could be cached in a static and cloned when needed
}
}
}
}
Now, consider the case where you have to new an entity, and want to access its properties without the null values checks: you have to forge a decent constructor for this job. As you still need the Entity Manager, the most straightforward way is to pass the EM to the constructor. In ZF2 (and Symfony I believe) you can have a service locator injected and retrieve the EM from there. Several ways, but it's another story. So, the basic, in MyEntity:
public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->enableFakeMappings($em);
}
Doing this, however, would probably confuse Doctrine when the entity is persisted: what should it do with all these instantiated empty objects? It'll cascade-persist them, which is not what you want (if it is, well, you can stop reading ;)). Sacrificing cascade-persisting, an easy solution would be something like this, still in your superclass:
/** #PrePersist */
public function doPrePersist(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
/** #PreUpdate */
public function doPreUpdate(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
private function disableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$uow = $em->getUnitOfWork();
$mappings = $em->getClassMetadata()->getAssociationMappings();
foreach ($mappings as $mapping) {
if (!$this->{$mapping['fieldName']} instanceof MyEntity) {
continue;
}
//"reset" faked associations: assume they're fake if the object is not yet handled by Doctrine, which breaks the cascading auto-persist... risk nothing, gain nothing, heh? ;)
if (null === $uow->getEntityState($this->{$mapping['fieldName']}, null)) {
$this->{$mapping['fieldName']} = null;
}
}
}
Hope this helps! :)

Doctrine2 - Get entity ID before flush

Is there any way to get an entity ID before the persist/flush?
I mean:
$entity = new PointData();
$form = $this->createForm(new PointDataType(), $entity);
If I try $entity->getId() at this point, it returns nothing.
I can get it working by:
$em->persist($entity);
$em->flush();
(supposing $em = $this->getDoctrine()->getEntityManager();)
How can I achieve this?
If you want to know the ID of an entity before it's been persisted to the database, then you obviously can't use generated identifiers. You'll need to find some way to generate unique identifiers yourself (perhaps some kind of hash function can produce unique-enough values).
This is rarely a good idea, though, so you should be careful.
I would think very carefully about why I need to know the identifier before flush. Doctrine is quite good at letting you build up a big object graph, and persist/flush it all at once. It seems likely that you've got something ugly in your architecture that you're trying to work around. It might be a good idea to review that before going down the application-generated-id route.
You can use the #PostPersist annotation. A method annotated with that will be executed just before the flush terminate and the entity Id is available already.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/events.html
postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event.
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class PointData
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\PostPersist
*/
public function onPostPersist()
{
// Put some simple logic here that required the auto-generated Id.
$this->doSomething($this->id);
}
...
}
you can use an auto generate ID to get a key like universally unique identifiers (UUID) or you can take the events of symfony:
postFlush - The postFlush event occurs at the end of a flush operation.
Doctrine best practices says,
You should avoid auto-generated identifiers. because:
Your DB operations will block each other
You are denying bulk inserts
You cannot make multi-request transactions
Your object is invalid until saved
Your object does not work without the DB
So you can use UUIDS instead
public function __construct() {
$this->id = Uuid::uuid4();
}
Also, Doctrine supports the UUID generation strategy since version 2.3.
Not sure why you need the ID before flushing, but, if you really need to persist the entity without saving to the database you can try using Transactions.
Try something like this:
$em->beginTransaction();
$em->persist($entity);
$em->flush();
$id = $entity->getId();
//do some stuff and save when ready
$em->commit();
$em = $this->getDoctrine()->getManager();
$entity = new PointData();
$em->persist($entity);
$entity->getId() <-- return <int>
$em->flush();
after persist you can get id

How to get a Respository from an Entity?

I have an Entity called Game with a related Repository called GameRepository:
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue() {
$this->slug = $repo->createUniqueSlugForGame();
}
}
In the prePersist method, I need to ensure that the Game's slug field is unique, which requires a database query. To do the query, I need access to the EntityManager. I can get the EntityManager from inside GameRepository. So: how do I get the GameRespository from a Game?
You actually can get the repository in your entity and only during a lifecycle callback. You are very close to it, all you have to do is to receive the LifecycleEventArgs parameter.
Also see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* #ORM\Entity(repositoryClass="...\GameRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Game {
/**
* #ORM\prePersist
*/
public function setSlugValue( LifecycleEventArgs $event ) {
$entityManager = $event->getEntityManager();
$repository = $entityManager->getRepository( get_class($this) );
$this->slug = $repository->createUniqueSlugForGame();
}
}
PS. I know this is an old question, but I answered it to help any future googlers.
You don't. Entities in Doctrine 2 are supposed to not know of the entity manager or the repository.
A typical solution to the case you present would be to add a method to the repository (or a service class) which is used to create (or called to store) new instances, and also produces a unique slug value.
you can inject the doctrine entity manager in your entity
(using JMSDiExtraBundle)
and have the repository like this:
/**
* #InjectParams({
* "em" = #Inject("doctrine.orm.entity_manager")
* })
*/
public function setInitialStatus(\Doctrine\ORM\EntityManager $em) {
$obj = $em->getRepository('AcmeSampleBundle:User')->functionInRepository();
//...
}
see this : http://jmsyst.com/bundles/JMSDiExtraBundle/1.1/annotations
In order to keep the logic encapsulated without having to change the way you save the entity, instead of the simple prePersist lifecycle event you will need to look at using the more powerful Doctrine events which can get access to more than just the entity itself.
You should probably look at the DoctrineSluggableBundle or StofDoctrineExtensionsBundle bundles which might do just what you need.