Best practise for placing custom Entity methods in Symfony - php

I'm creating Symfony project. And now I'm trying to find best practice for adding custom methods.. What is yours?
Visual explanation:
users table
id | name | surname
---+------+--------
1 | John | Smith
2 | Matt | Malone
Entity\User.php
namespace TestBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
/**
* User
* #ORM\Table(name="users")
* #ORM\Entity
*/
class User
{
/**
* #ORM\Column(name="id", type="string", length=36)
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*/
private $name;
/**
* #ORM\Column(name="surname", type="string", length=255, nullable=true)
*/
private $surname;
/**
* OneToMany
*/
private $userCompanies;
{{ Setters and Getters }}
}
Where I should store custom method, like:
function getFullName()
{
return sprintf("%s %s", $this->getName(), $this->getSurname());
}
Or more complex:
function getCurrentUserCompany()
{
foreach ($this->getUserCompanies() as $company) {
if ($company->isActive()) {
return $company;
}
}
return null;
}
Please note, that all data returned via JSON
So far I tried extending class, but annotations not working as expected. Placing custom methods in same file looks trashy, since there will be more than one of them.
But.. but if there is repositoryClass - maby there is place for custom methods as well?
Thanks!

If it's about methods that are used mainly for display purposes then they are very similar to the getters, in my opinion they best fit is in the Entity itself, so inside your User.php class.
The repository is for defining methods for getting the entity from your storage level (DB, cache...), but the view level (your twig) should take the data from the entity itself.
If you need something more complicated or you need to reuse it, like a date filter, then it's better to create a Twig extension.

Methods like that belong to entity class and there is no reason to split code. If many entity classes share some methods, you can always create shared base abstract class or trait for them.
If you really want separated files for sake of your aesthetic, then use traits, but remember that it's not proper and conventional use of them.

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 4 table relationships do not work out of naming strategy?

I have created two entities of existing database tables, these tables use the doctrine conventions for table relationships, I need to relate the tables to be able to work, the entities work by consulting data, but not between them.
Table name "Articulos"
class Articulos
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $ID_Articulo;
/**
* #ORM\Column(name="ID_Clasificacion_Articulo", type="integer")
* #ORM\ManyToOne(targetEntity="ClasificacionesArticulos")
*/
private $ID_Clasificacion_Articulo;
.......
Table name "ClasificacionesArticulos"
class ClasificacionesArticulos
{
/**
* #ORM\Column(name="ID_Clasificacion_Articulo", type="integer")
* #ORM\Id()
* #ORM\OneToMany(targetEntity="Articulos", mappedBy="ID_Clasificacion_Articulo")
*/
private $ID_Clasificacion_Articulo;
/**
* #ORM\Column(type="string", length=150)
*/
private $Codigo;
/**
* #ORM\Column(type="string", length=150)
*/
private $Nombre;
.........
When I consult any of the entities, returns result without children of relationships. I suppose it's because of the names of the fields id does not use the name strategies, but I can not change them in the database, I have to adapt to it by requirements.
If someone has an idea, I thank you very much
This can be accomplished by implementing custom Doctrine naming strategy. In Symfony entity, use camelCase to avoid any problems with naming. But, if you need specific table names follow the guide
You have to implement NamingStrategy class:
class CustomNamingStrategy implements NamingStrategy
{
}
register it as a service by adding following to the end of the the config/services.yaml :
app.naming_strategy.custom:
class: App\Service\CustomNamingStrategy
autowire: true
Then specify naming strategy by editing config/packages/doctrine.yaml as follows:
naming_strategy: app.naming_strategy.custom
I believe you are looking for propertyToColumnName method, as Doctrine says
If you have database naming standards, like all table names should be
prefixed by the application prefix, all column names should be lower
case, you can easily achieve such standards by implementing a naming
strategy.

Simple index definition in Doctrine 2

Is there a simple way to define a non-unique index over a column?
When I define unique index, this is perfectly sufficient:
/** #ORM\Entity */
class Foo {
/** #ORM\Column(type="string", unique=true) */
private $foo;
}
However, for non-unique index, I need this bunch of boilerplate:
/**
* #ORM\Entity
* #ORM\Table(indexes={#Index(name="foo_idx", columns={"foo"})})
*/
class Foo
{
/** #ORM\Column(type="string") */
private $foo;
}
I'd prefer something like #ORM\Index annotation on the single property, or index=true etc...
I have to disappoint you...
#ORM\Table(indexes={#Index(name="foo_idx", columns={"foo"})})
is as simple as the annotation can get.
If $foo would be a relating entity it would be indexed automatically, but for indexing fields you need to add the index as you did.
You can find all #Column attributes here in the Doctrine 2 documentation. Sadly enough index is not among them.
Maybe you can make a feature request here on GitHub.

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

Symfony2 / Doctrine mapped superclass in the middle of class table inheritance

I currently have a model structure as follows:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="related_type", type="string")
* #ORM\DiscriminatorMap({"type_one"="TypeOne", "type_two"="TypeTwo"})
*/
abstract class BaseEntity {
... (all the usual stuff, IDs, etc)
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="baseEntity")
*/
private $comments;
}
/**
* #ORM\Entity
*/
class TypeOne extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeTwo extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class Comment {
... (all the usual stuff, IDs, etc)
/**
* #ORM\ManyToOne(targetEntity="BaseEntity", inversedBy="comments")
*/
private $baseEntity;
}
The idea here is to be able to tie a comment to any of the other tables. This all seems to be working ok so far (granted, I'm still exploring design options so there could be a better way to do this...), but the one thing I've noticed is that the subclasses have some common fields that I'd like to move into a common parent class. I don't want to move them up into the BaseEntity as there will be other objects that are children of BaseEntity, but that won't have those fields.
I've considered creating a MappedSuperclass parent class in the middle, like so:
/**
* #ORM\MappedSuperclass
*/
abstract class Common extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeOne extends Common {}
/**
* #ORM\Entity
*/
class TypeTwo extends Common {}
I figured this would work, but the doctrine database schema generator is complaining that I can't have a OneToMany mapping on a MappedSuperclass. I didn't expect this to be a problem as the OneToMany mapping is still between the root BaseEntity and the Comment table. Is there a different structure I should be using, or other way to make these fields common without adding them on the BaseEntity?
From the Docs:
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that is
common to multiple entity classes.
That said, how can you associate one entity with one that is not?
More from the docs:
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
assocations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For
further support of inheritance, the single or joined table inheritance
features have to be used.
Source: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
Update
Because your MappedSuperClass extends BaseEntity it also inherits the BaseEntity's associations, as if it were its own. So you effectively DO have a OneToMany on a MappedSuperClass.
To get around it, well, you'd need to modify/extend doctrine to work the way you want.
As far as native functionality goes you have two options:
Class Table Inheritance
You Common class and the resulting DB representation would have the common fields and child classes will now only have the fields specific to themselves. Unfortunately this may be a misrepresentation of your data if you are simply trying to group common fields for the sake of grouping them.
Make Common an Entity
It appears that all a Mapped Super Class is is an Entity that isn't represented in the DB. So, make common a Entity instead. The downside is that you'll end up with a DB table, but you could just delete that.
I recommend that you take a second look at your data and ensure that you are only grouping fields if they are common in both name and purpose. For example, a ComputerBox, a ShoeBox, a Man, and a Woman may all have the "height" property but in that case I wouldn't suggest have a Common class with a "height" property that they all inherit from. Instead, I would have a Box with fields common to ComputerBox and ShoeBox and I'd have a Person with fields common to Man and Woman. In that situation Class Table Inheritance or single table if you prefer would work perfectly.
If your data follows that example go with Single Table or Class Table Inheritance. If not, I might advise not grouping the fields.

Categories