Question is simple.
I have an Entity with validation contraints.
I also have a service which is going to create such entities.
Problem is, it does not trigger validations like #Assert\Expression. See below.
This is a huge bummer. How can I trigger such validation programmatically please ?
I need to automatically validate a Booking instance against all the constraints defined in the Booking class definition.
<?php
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ApiResource()
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
*/
class Booking
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Slot", inversedBy="bookings")
* #Groups({"booking"})
* #ORM\JoinColumn(nullable=true)
* #Assert\Expression(
* "value == null or (value.getStatus() == constant('App\\Entity\\Slot::STATUS_ACTIVE'))",
* message="Vous ne pouvez pas réserver cette date."
* )
*/
private $slotId;
}
You need to use the validator service from Symfony (documentation about the Validator component).
Call the validate method on your newly created entity in your service.
Related
I have to check a submitted form against the existing database to make sure it is not duplicated by the combination of several fields, say supplier_id, invoice_no and amount.
Is there a built-in method or should I write a code myself? If so - what are guidelines: where to put it, what are the good practices?
At the moment I am extending the CRUD controller and overwriting the createAction adding the condition there. Not sure whether this method is a good practice.
Example:
<?php
namespace AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity({"name", "email"}, message="This value is in a database.")
*/
class User
{
/**
* #var string
*/
protected $name;
/*
* #var string
*/
protected $email;
...
}
I'm trying to get the following working:
I've got an entity like:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Contact
*
* #ORM\Table()
* #ORM\Entity()
*/
class Contact
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts")
* #ORM\JoinColumn(name="service_client", referencedColumnName="service_client")
*
* #JMS\Type("AppBundle\Entity\ServiceClient")
* #JMS\SerializedName("serviceClient")
*/
private $serviceClient;
}
I'm sending the following JSON over an HTTP request (Post, it's a new Contact, no ID):
{
"name": "Lorem Ipsum",
"serviceClient": {"service_client": "ipsum"}
}
What I expect is for the JMS Serializer to parse that relationship, and leting me persist the Contact object like this:
<?php
$contact = $this->get('serializer')->deserialize(
$request->getContent(),
Contact::class, 'json'
);
$this->em->persist($contact);
$this->em->flush();
In fact I got that working (I swear it was working) but now it's giving me the follwing error:
A new entity was found through the relationship
'AppBundle\Entity\Contact#serviceClient' that was not configured to
cascade persist operations for entity:
AppBundle\Entity\ServiceClient#000000006fafb93e00007f122bd10320. To
solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={\"persist\"}). If you
cannot find out which entity causes the problem implement
'AppBundle\Entity\ServiceClient#__toString()' to get a clue."
So it's tryign to persist the entity... a thing I do not want since the entity already exists. I just want Doctrine to put the reference, the foreign key.
Edit: It seems it's the constructor, if I set it to the doctrine_object_constructor it works like magic, the thing I do not understand is why it stop working in the first place.
Can anyone share any ideas or a cleaner way to do what I did?
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
This problem happens when Doctrine cannot map your relationship to an existing record in the database, so it will try to create a new one with the data from the JSON object.
In your case, the JSON object: {"service_client": "ipsum"} cannot be mapped to an existing ServiceClient instance.
It's because the default JMS object constructor call the unserialize function (will be the one from your Entity if you defined this method) to construct the object, which mean this object will always be treated by Doctrine as new (has never been persisted).
By using doctrine_object_constructor, JMS will get the object from Doctrine. The object came from Doctrine not only have the attributes and methods you define in your entity, but also meta-data about whether it's an existing one, it's corresponding row from the database ( so Doctrine can detect update made on the record later and handle it), therefore Doctrine are able to avoid incorrect persisting.
Doctrine will try to persist the Contact with a reference of a ServiceClient entity given in the deserialization. In the entity definition at the level of the manyToOne definition you need to add :
#ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts", cascade={"persist"})
For legacy reasons I have table that is used for many purposes. Only subset of rows is relevant to Entity I'm writing. Criteria is simple, just check 'project' field for specific value.
Instead of reimplementing find, findAll, findByName, findByID, findBy.... Just notify doctrine to append single condition to them all. Is that possible without reimplementing each and every find* ?
Or maybe it can be done on lover level still?
UPDATE:
Reworked question, to specify what kind of solution would be acceptable.
An available easy-to-use solution is to create a Repository with your custom find function.
Then, if all your entities has a specific Repository, make them (Repository) extending from yours (which contains the custom find method), otherwise (you doesn't have a Repository per entity), assign the repository to all your entities with the repositoryClass option of the #ORM\Entity annotation like :
#ORM\Entity(repositoryClass="YourMainRepository")
Otherwise, if you doesn't want put any repository in your entities, override the whole default repository and customise the find method.
I already used the last option because of a specific need, also I invite you to see the following question :
Abstract repository and #Entity annotation inheritance
Look at the solution wich contains a gist of all required steps for override the default doctrine repository.
See Custom Repository classes
Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Phrase
*
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
.............................................................
.............................................................
.............................................................
Your Repository:
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/** For example **/
public function getByName($name)
{
$qb = $this->createQueryBuilder('u')
->where('u.name = :name')->setParameter('name', $name)
->andWhere('u.lastname LIKE :name')->setParameter('lastname', '%'.$name.'%');
$query = $qb->getQuery();
return $query->getResult();
}
}
In Your Controller:
/**
* #Route("/", name="index")
*/
public function indexAction(Request $request)
{
$userRepository = $this->getDoctrine()->getRepository('AppBundle:User');
$userName = $userRepository->getByName($name);
..................................................................................
..................................................................................
Maybe it is just my misunderstanding of this annotation however it does not seam to work as expected.
I have the following object graph
User
-> Company
-> Users
-> Groups
-> Permissions
As you can see there will be some recursion. JMS handles this quite well by not serialising the other user's company properties as well as not the current user.
However I want the serialization to stop at and include company.
I have tried this expecting that once the level $context->level = 2 it would stop
<?php
namespace FinalConcept\TimeTracker\EntitiesBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* FinalConcept\TimeTracker\EntitiesBundle\Entity
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="FinalConcept\TimeTracker\EntitiesBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\ManyToOne(targetEntity="company", inversedBy="users")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* #JMS\MaxDepth(depth=1)
*/
private $company;
}
However this is not the case. Even stepping through the code has not shed any light on how to stop this.
I am happy to create a custom handler if I can only invoke it for a specific path i.e. User.Company
I also need this for User.Groups which has the following graph
User
-> Groups
-> Permissions
-> Users
-> Groups
-> users ....
Thanks in advance for any help how to limit the depth of serialization for an object graph
Because I did not have access to the latest version of the serializer I had to find a workaround to the #MaxDepth. This may also help you.
Use #JMS\ExclusionPolicy("all") on all entities that are connected.
Now use #JMS\Expose only on those properties that you want to have serialize.
On join relations only use this annotation in one direction. That will fix your problem.
namespace FinalConcept\TimeTracker\EntitiesBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* FinalConcept\TimeTracker\EntitiesBundle\Entity
*
* #JMS\ExclusionPolicy("all")
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="FinalConcept\TimeTracker\EntitiesBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #JMS\Expose
* #ORM\ManyToOne(targetEntity="company", inversedBy="users")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
*/
private $company;
}
As of the latest version, using #MaxDepth() annotation and SerializationContext::create()->enableMaxDepthChecks() in the controller does the job.
I have tried following the instructions on Doctrines manuals:
http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-self-referencing
I'm trying to setup a pages entity, which has many subPages and I would also like to be able to access a pages parentPage.
Following the instructions above I am told by symfony2 that I need to setup a "use" due to a semantic error.
Could anybody instruct me on what to do to allow me to do this as I am quite stuck.
Sample code is:
namespace Pages\Bundle\PageBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Page
*
* #ORM\Table(name="Page")
* #ORM\Entity(repositoryClass="Pages\Bundle\PageBundle\Entity\PageRepository")
*/
class Page
{
/**
* Constructor
*/
public function __construct()
{
$this->subPages = new \Doctrine\Common\Collections\ArrayCollection();
$this->parentPages = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ManyToMany(targetEntity="Page", mappedBy="subPages")
**/
private $parentPages;
/**
* #ManyToMany(targetEntity="Page", inversedBy="parentPages")
* #JoinTable(name="sub_pages",
* joinColumns={#JoinColumn(name="parent_page_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="sub_page_id", referencedColumnName="id")}
* )
**/
private $subPages;
...
(other variables follow but are content / metas)
Error response when running is:
[Semantical Error] The annotation "#ManyToMany" in property
Pages\Bundle\PageBundle\Entity\Page::$parentPages was never imported. Did you maybe forget to add a "use" statement for this annotation?
Try using #ORM\ManyToMany instead of just #ManyToMany
(and also #ORM\JoinTable)