Symfony where clause on the entity level - php

I've got two entities that are linked together by a one-to-many relationship and I'm using soft deletes on both entities. Because I'm using soft deletes however, reading data is a little bit more tricky because I need to check if the deleted flag is set to false before reading it out.
The basic setup of the entities are:
class Division extends MasterData {
...
/**
* #var Asset
*
* #ORM\OneToMany(targetEntity="Asset", mappedBy="division")
*/
private $assets;
public function __construct() {
$this->assets = new ArrayCollection();
}
public function getAssets() {
return $this->assets;
}
public function addAssets(Asset $asset) {
$this->assets[] = $asset;
return $this;
}
...
}
class Asset extends MasterData {
...
/**
* #var Division
*
* #ORM\ManyToOne(targetEntity="Division", inversedBy="assets")
*/
private $division;
...
}
class MasterData {
/**
* #ORM\Column(name="deleted", type="boolean", options={"default":0})
*/
protected $deleted;
public function __construct() {
$this->deleted = 0;
}
...
}
These are only snippets of the entities, not the entire thing.
When I am in the controller for a Division, I'd like to pull a list of all the Assets that are related to that division and are not marked as deleted. I can see a couple ways of doing this.
An easy solution would be to create a custom repository to handle the pull of data. This however would provide a limitation when I would like to further filter data (using findBy() for example).
A second solution would be to alter the getAssets() function in the Division entity to only return assets that are not deleted. This however means that I'm pulling all of the data from the database, then filtering it out post which is very inefficient.
Ideally, I'm looking for a way to alter the definition in the entity itself to add a where clause for the asset itself so that way the filtering is happening in the entity removing the needs for custom repositories and a more efficient option. Similar as to how I can define #ORM\OrderBy() in the annotations, is there a way to similar to this that lets me filter out deleted assets pre-execution and without a custom repository?
Thanks in advance :)

Doctrine does not support conditional associations in mapping. To achive this behavior you can use Criteria API in the entity methods. And yes, in this case all data will be fetched from DB before applying condition.
But Doctrine (>=2.2) supports Filters. This feature allows to add some SQL to the conditional clauses of all queries. Soft-deletes can be implemented through this feature.
The DoctrineExtensions library already has this functionality (SoftDeletable, based on Filters API).
Also, many don't recommend to use soft-deletes (1, 2).

Related

What is the return value of getSomethings in a Doctrine / Symfony One-To-Many Relationship?

I like to either type-hint or starting in PHP7 actually show the return value of a getter function. But with One-To-Many relationships in Doctrine / Symfony, I'm still stuck and am not sure what to add to the #var tag.
[...]
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $features;
/**
* What goes into var here?
*
* One Product has Many Features.
* #ORM\OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
public function __construct()
{
$this->features = new ArrayCollection();
$this->name = 'New Product Name';
}
/**
* #return Collection
*/
public function getFeatures(): Collection
{
return $this->features;
}
[...]
Currently I’m using #var Collection and can then use the Collection functions. But what would be the »proper« thing to return? Is it indeed Collection? Or is it ArrayCollection? I’m tempted to use Features[] in order to use the functions of Feature, if I need to (instead of typehinting), but it doesn’t feel right.
What would be the »cleanest« / stable way to do this?
If you want to keep the docblock I would use the union type | to both specify the Collection and the list of values it contains like:
/**
* #var Collection|Feature[]
*/
With this your IDE should both find the methods from Collection as well as the Feature-type hints when you get a single object from the collection, e.g. in a foreach.
As to the question of ArrayCollection vs. Collection, it is usually recommended to type hint for the interface (Collection in this case). ArrayCollection offers a few more methods, but unless you really need them I would not bother with the type hint just to get them.
What I tend to do in projects is keep the Collection inside the entity and only pass out an array in the getter like this:
public function getFeatures(): array
{
return $this->features->toArray();
}
public function setFeatures(array $features): void
{
$this->features = new ArrayCollection($features);
}
Be careful, the void return type is not supported in PHP 7.0 yet. The benefit of returning an array is that in your code you don't have to worry about what kind of Collection Doctrine uses. That class is mainly used to maintain reference between objects inside Doctrine's Unit Of Work, so it should not really be part of your concern.

Symfony 3 / Doctrine - Get changes to associations in entity change set

So I already know that I can get changes to a specific entity in the preUpdate lifecycle event:
/**
* Captures pre-update events.
* #param PreUpdateEventArgs $args
*/
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof ParentEntity) {
$changes = $args->getEntityChangeSet();
}
}
However, is there a way to also get changes for any associated Entities? For example, say ParentEntity has a relationship setup like so:
/**
* #ORM\OneToMany(targetEntity="ChildEntity", mappedBy="parentEntity", cascade={"persist", "remove"})
*/
private $childEntities;
And ChildEntity also has:
/**
* #ORM\OneToMany(targetEntity="GrandChildEntity", mappedBy="childEntity", cascade={"persist", "remove"})
*/
private $grandChildEntities;
Is there a way to get all relevant changes during the preUpdate of ParentEntity?
All of the associated entities from a OneToMany or ManyToMany relationships appear as a Doctrine\ORM\PersistentCollection.
Take a look at the PersistentCollection's API, it have some interesting public methods even if they are marked as INTERNAL: https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/PersistentCollection.php#L308
For example you can check if your collection is dirty which means that its state needs to be synchronized with the database. Then you can retrieve the entities that have been removed from the collection or inserted into it.
if ($entity->getChildEntities()->isDirty()) {
$removed = $entity->getChildEntities()->getDeleteDiff();
$inserted = $entity->getChildEntities()->getInsertDiff();
}
Also you can get a snapshot of the collection at the moment it was fetched from the database: $entity->getChildEntities()->getSnapshot();, this is used to create the diffs above.
May be this is not optimal, but it can do the job. You can add a version field on ParentEntiy with a timestamp, then on each related entity setter function (Child or GranChild) you need to add a line updating that parent timestamp entity. In this way each time you call a setter you will produce a change on the parent entity that you can capture at the listener.
I have used this solution to update ElasticSearch documents that need to be updated when a change happens on a child entity and it works fine.

Is it considered a bad practice to add fields to Symfony entity in controller?

Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html

Doctrine classes, can I add custom functions?

I'm having a difficult time finding if I can add custom functions to doctrine classes.
Lets say I have
use Doctrine\ORM\Mapping as ORM;
/**
* Map
*/
class Map
{
/**
* #var integer
*/
private $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
In my classes I would like some custom functions that return values that do not per se need to be stored in databse but merely provide a checking of certain values functionality.
For examply I would like to add a function isAboveTen();
function isAboveTen()
{
return this->id > 10;
}
Can I just go ahead and do this or do I need to define them as a special field in the xml file or annotations?
You can safely add functions working on simple member types, Doctrine will ignore them if you do not add any annotations.
The question whether you should avoid doing this depends on your overall architecture and coding guidelines. As mentioned in the comments both flavors with logic possibly inside vs outside the entities exist.
However, you should keep in mind that:
All persistent properties/field of any entity class should always be private or protected, otherwise lazy-loading might not work as expected. In case you serialize entities (for example Session) properties should be protected (See Serialize section below).
Which is described in the documentation. Since you are accessing these members inside your class, magic methods like __get() will not be called.

how to filter a getter in doctrine2 entity?

I have an entity that has a one-to-many association (many-to-many with extra fields):
class Game {
/**
/* #OneToMany(targetEntity="GamePlayer", mappedBy="game", cascade={"persist"})
/* #JoinColumn(name="id", referencedColumnName="game_id", onDelete="cascade")
*/
private $gamePlayer;
}
The class has automated getter for all the authors: getGamePlayers()
I would like to add a filter to it, so it would query the database only for the relevant details in the most efficient way:
public function getGamePlayersWithScoreHigherThan($score){
//what to write here? (return array)
}
What is the best way to achieve such a getter from within the entity (not using the repository)?
Thank you very much!
You could try creating a separate method on your entity that uses the Doctrine\Common\Collections\Criteria to filter the associated collection. See this link for details.

Categories