Symfony 4 Validation on whole Entity - php

So, I need a validation for a reservation for a sports club.
A reservation has a start and an end datetime and you can reservate for 1 or more tables.
So the Entity looks like
class Reservation
{
use TimestampAble;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="reservations")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Table", inversedBy="reservations")
*/
private $tables;
/**
* #ORM\Column(type="datetime")
*/
private $start;
/**
* #ORM\Column(type="datetime")
*/
private $end;
...
}
(Don't worry about the entity "Table" - the db table for it is named "snooker_table" ;))
Now I need to validate, that in the requested time range with the requested tables no other reservation already exist.
And this gives me headaches...
I know I can make it "manually" in the Controller Actions create / update. But I'm also using Symfonys Easy-Admin, so I need to put the code there as well.
I thought about putting the validation as an annotation directly into the entity. But I don't know where... If I put it on "$tables" I just get an ArrayCollection without the needed start and end datetimes. And it's also not an unique entity (as I need to go on a range of datetimes and so on).
So: any ideas how to achieve this in the entity directly? or at least in the form type (and for easy admin i care later)?
Thx in advance.

Ok, I'm going to do it in this way: Symfony 2 UniqueEntity repositoryMethod fails on Update Entity
Creating a repository method (not a constraint) for validation and use it for the UniqueEntity constraint on the whole entity itself. Feels a little bit dirty but ok...
Hope this works in easy admin as well.

Related

Doctrine in Symfony: use a single “Author” associative entity related to different entities

I'm developing a custom content management system with Symfony 5 and Doctrine.
I'm trying to implement a relation between the entities Document and Video (actually there are many more, but for simplicity sake let's say are just two) and the User entity.
The relation represent the User who wrote the document or recorded the video. So the relation here is called Author. Each document or video can have one or more author. Each User can have none or more document or video.
I would like to use just a single associative Author associative entity, like this:
entity_id|author_id|entity
Where:
entity_id: is the id of the document or video
author_id: is the user_id who authored the entity
entity: is a constant like document or video to know to which entity the relation refer to
The problem is that I cannot understand how to build this in Doctrine. Was this a classic SingleEntity<-->Author<-->Users relationship I would have build it as a ManyToMany item, but here it's different.
Author would probably contain two ManyToOne relations (one with the User entity and one with either the Document or the Video entity) plus the entity type field, but I really don't know how to code the "DocumentorVideo`" part. I mean:
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=??????????, inversedBy="authors")
* #ORM\JoinColumn(nullable=false)
*/
private $entity; // Document or Video
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="smallint")
*/
private $entityType;
How should I manage the first field?
Don't know if would be better to store it under two differents attributes. If not and mandatory, I think those "objects" should have a common interface or something, so take a look to doctrine inheritance that should fulfill your needs
My suggestion is to store the entity namespace Ex. Acme\Entity\Document in a property and the id in another and to use the entity manager to get the entity.
Edit: Though you won't have the relation, I prefer that way over others because it is reusable and the performance is rather the same. Also if I need to pass it to a JSON response, I just create a normalizer and I am good to go.
/**
* #ORM\Column(type="string")
*/
private $entityNamespace;
/**
* #ORM\Column(type="integer")
*/
private $entityId;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntity()
{
return $this->em->getRepository($this->entityNamespace)->find($this->entityId);
}

Symfony : is it bad practice to add custom functions in an Entity?

I understand that an Entity is a basic class that holds data.
But is it bad practice if the Entity has custom functions that manipulates the data ?
I personally think that this kind of functions should go into a different Service. But in this case, the getNextPayroll is quite useful :
<?php
class Payroll
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="last_payroll", type="datetime", nullable = true)
*/
private $lastPayroll;
/**
* Set lastPayroll
*
* #param \DateTime $lastPayroll
* #return CompanyBase
*/
public function setLastPayroll($lastPayroll)
{
$this->lastPayroll = $lastPayroll;
return $this;
}
/**
* Get lastPayroll
*
* #return \DateTime
*/
public function getLastPayroll()
{
return $this->lastPayroll;
}
public function getNextPayroll()
{
$payrollNext = clone $this->getLastPayroll();
$payrollNext->add(new \DateInterval("P1M"));
return $payrollNext;
}
}
The date of the next payroll is not stored in database. Only the date of the last payroll. Should I get the next payroll date in a different service or is it OK to use use a custom function non-generated by doctrine in an entity ?
It's not a bad practice, if your code still satisfies SOLID principles (mainly, Single Responsibility principe in that case)
So, if the method isn't related to entity logic (for example, sending emails or persisting something to database right from your entity) -- it's wrong. Otherwise, it's absolutely OK.
The major attribute of the logic related to entity -- it should be in the same layer with another stuff in the entity.
Actually, Doctrine entities is not just Data Transfer Objects (without behavior). Doctrine's developers insist using the entities as Rich Models (look the video by Marco Pivetta, one of Doctrine's developers and see his nice presentation)
As far as I know it shouldn't be bad practice as long as your entity doesn't get into the Database layer, which the Repository should take care of.
So things where you more or less just get EntityData back (like your method which returns just modified data that belongs to the Entity) should be fine in the Entity it self. This way you also easily use the methods inside Twig, which automaticaly searchs for the method name (ex. {{ User.name }} will search for User->getName() and if it doesn't find it, it will search for User->name())
If you reusing this part and want to be dynamic, it also could be a good idea to create a custom Twig extension.
I think you will only need a service if you going to do very complicated things where you actually also need to inject the EntityManager and also retrive data from other entities that maybe are not part of the usual relations.

How to restrict associations to a subset of another association in Doctrine?

I got a bit stuck with multiple mappings of the same object in Doctrine. The app is build on Symfony btw, hence the slightly different annotations.
Basically I have the following objects:
Organisation: an umbrella holding attributes about an organisation
Department: a department within the organisation
User: a generic user object
Those objects are related as follows:
An organisation always has one and only one owner, which is a User
An organisation has many members, which are all User's
A department consists of many User's, but only members of the Organisation the Department is a part of are allowed
I'm a bit stuck at the third requirement... First of all, this is how my objects more or less look like atm:
/**
* #ORM\Entity
* #ORM\Table(name="organisations")
*/
class Organisation
{
// ...
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="organisation")
*/
private $owner;
/**
* ORM\OneToMany(targetEntity="User", mappedBy="organisation")
*/
private $members
}
/**
* #ORM\Entity
* #ORM\Table(name="departments")
*/
class Department
{
// ...
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="departments")
*/
private $members
/**
* #ORM\ManyToOne(targetEntity="Organisation", inversedBy="departments")
*/
private $organisation;
}
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User
{
// ...
/**
* The organisation this user "owns"
*
* #ORM\OneToOne(targetEntity="Organisation", mappedBy="owner", nullable=true)
*/
private $owning_organisation;
/**
* #ORM\ManyToOne(targetEntity="Organisation", inversedBy="members")
*/
private $organisations;
/**
* #ORM\ManyToMany(targetEntity="Department", inversedBy="members")
* #ORM\JoinTable(name="users_departments")
*/
private $departments;
}
Now this basically works, if and only of in the controllers I do all the checking (something like (if( $user->isPartOfOrganisation($department-getOrganisation()) { $department->addMember($user); }).
But is there a way to restrict possible object associations on design level? So basically what I want is that if a user is added to a department, it is solely possible if the user is already part of the organisation the department is also a part of. Or should I do the check in the addMember() method of the Department object? I can imagine (but cannot find it) that there is some kind of a subset-restriction (Department::members is subset of Organisation::members).
To implements this check low-level as possible (nearest to the db) I think the only solution is a Doctrine Event Listener that in the pre-persist event check for your custom constraint. Read more about Doctrine Event System .
BTW I think you can manage this situation in a more simply manner: I suggest you to incapsulate the business logic into a service (so you can reuse it more simply) and use it in a custom validator that you will use in the form where you manage this situation.
Let me know if you need more tips to develop one of this solutions or if you found something more useful.
Hope this help

Symfony Association Mapping OneToOne and OneToMany to Same Entity

I have a View entity that represents the primary page record, and then I have an associated entity called ViewVersion which stores multiple versions of the entity as it's changed over time. The View entity sets the current "Published" ViewVersion in the VersionId field. This makes for a simple OneToOne association. But in some contexts I will also want to get all the versions associated with this View entity, e.g. if I want to allow the user to review older versions and revert back. So I will need another mapping which is a OneToMany. The first viewVersion will map to the active "published" version, and the second viewVersions will show all the versions.
Entity Definitions
/**
* #ORM\Entity
* #ORM\Table(name="view")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewRepository")
*/
class View extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", inversedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="versionId", referencedColumnName="id")
*/
protected $viewVersion;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $versionId = NULL;
/**
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="viewAll", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $viewVersions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewVersionRepository")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", mappedBy="viewVersion", cascade={"persist"})
*/
protected $view;
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="viewVersions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $viewAll;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
This "works" but is it recommended to have two associations with the same entity like this? Or is this a really bad idea?
The ViewVersion entity will reference a single View entity in both cases, but the mapped associations need two separate variables, e.g. View and ViewAll. I'm not exactly sure how the internals work for the association, and how the reference variable with the mapping is used.
Alternatively, I could get rid of the OneToOne association, and just set a ViewRepository function to get the current published version based on the versionId (just like the old mapped entity used to do with the getVersion()). That would work, but is it more internal overhead, because it would make two queries... or will Doctrine be smart enough to optimize this, just like it did with the getVersion().
NOTE:
These other answers are not complete.
References:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
http://doctrine-orm.readthedocs.org/en/2.0.x/reference/association-mapping.html#one-to-many-bidirectional
Typically, I have found the best approach is to solve this in a different way.
One common pattern I have seen before is you use a single table to hold all records, and have an 'active' flag.
If your query to select the active one works like so:
SELECT * FROM table WHERE active = true ORDER BY updated_at DESC LIMIT 1;
Then enabling a new one becomes as simple as:
UPDATE table SET active = 1, updated_at = '<timestamp>' WHERE id = <new id>;
UPDATE table SET active = 0, updated_at = '<timestamp>' WHERE id = <old id>;
Your new page will be active as soon as the first query hits, and your second query will avoid any sort of weirdness as that row will already be no longer active.
If you have other models that depend on a consistent ID to reference, then another route which also maintains some sanity would be to have one table for the active entries (in whole, not in part) and then a second table with additional metadata to track versions.
The latter approach could be nicely handled via Doctrine's inheritance system (http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html) which would let you define the base View class, and then for the "ViewRevision" model, extend View and add a "Revised on" type timestamp.
Per the advice from #jmather I've decided this model is "okay", because I need a single View entity that other entities can access (e.g. Routing urls that point to a single View, i.e. "page").
I've changed the OneToOne relationship for View to be unidirectional only, because the ViewVersion already has an association back to the View via the other OneToMany (so it doesn't need two paths back).
This allows me to keep a simple method for $view->getPublished() handy and seems more logical.
/**
* #ORM\Entity
* #ORM\Table(name="view")
*/
class View extends Entity\Base {
/**
* This is a OneToOne Unidirectional association, just so that we can get the
* current published version easily, based on the publishedId.
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\TestVersion")
* #ORM\JoinColumn(name="publishedId", referencedColumnName="id")
*/
protected $published;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $publishedId = NULL;
/**
* This is the regular OneToMany Bi-Directional Association, for all the versions.
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $versions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="versions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $view;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
However, I've discovered that as long as the $view->publishedId is set the view can't be deleted from the database because of foreign key constraints (even though it's uni-directional). So I have to break that foreign key link before removing. I think that's fine. I posted details about that here: Overlapping Entity Association causing Database Foreign Key Constraint Errors when Removing Entity

Symfony2 ManyToMany embedded forms

I have a Post and Tag entity in my application, and I need many to many association between them. I think I managed it right, but not enirely sure. Here are my entities:
Post:
/**
* #ORM\Table(name="posts")
*/
class Post
{
( ... )
/**
* #ORM\OneToMany(targetEntity="PostTag", mappedBy="post_id")
*/
private $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
( ... )
}
Tag:
class Tag
{
/**
* #ORM\Column(name="tagname", unique=true, type="string", length=255)
*/
private $tagname;
/**
* #ORM\OneToMany(targetEntity="PostTag", mappedBy="tag_id")
*/
private $posts;
public function __construct()
{
$this->posts = new ArrayCollection();
}
( ... )
}
I also created a PostTag entity to store these relations:
/**
* #ORM\Table(name="post_tags")
* #ORM\Entity
*/
class PostTag
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Post", inversedBy="tags")
* #ORM\JoinColumn(name="post_id", referencedColumnName="id")
*/
private $post_id;
/**
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="posts")
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
*/
private $tag_id;
( ... )
}
Of course all 3 with appropriate getters/setters. Are the relations okay this way?
I believe I have it right, but now I'm struggling to make an embedded form for the Post entity. What I need is, to create a tags field in the PostType, where one could type in tags which are saved in the tags table and the id of both the newly created tag and post in the post_tags table. I also want the already saved tags to be pickable in another field, that's why I have the entities build this way.
I tried to write this, but got really confused with bad codes, so I don't even try to copy what I had. Can someone briefly enlighten me how should I accomplish this?
Thanks
You don't need intermediary entity between Post and Tag. I myself struggled to get it working a few months back, but after carefully reading Many-To-Many, Unidirectional, I managed to do it.
The point is that you don't create Many-To-One and One-To-Many relations but a single Many-To-Many.
Regarding the embedded forms, once you establish Many-To-Many relation between Post and Tag you'll need to use collection field form type. Basically, you'll be saying: "OK, I have a form that has fields of Post which can have many Tags.
Of course, I would suggest you try managing data manually (persist, update, delete) before trying to make it work with forms. If you have an error in your model it'll be much more difficult to locate the source of a problem, as forms themselves can be tricky.
Official Symfony docs have a great article on this, although, I must say, it's a little overwhelming for a Symfony beginner as I was in a time of reading it.

Categories