Doctrine relationship with non-entity model - php

I'm trying to create an application where I have two related models: "User" and "Debtor".
User is a doctrine entity that is persisted in my database.
Debtor is a model that is stored on an external system and accessed through an API. I have already created a repository for accessing Debtors with find(), findAll() and findByUserId().
If "Debtor" was a Doctrine Entity, i would have a #OneToMany relationship on the User model in order to access debtors. Something like this:
/**
* #Entity
*/
class User {
/**
* #OneToMany
*/
private $debtors;
public function getDebtors() {
return $this->debtors;
}
public function setDebtors($debtors) {
$this->debtors = $debtors;
return $this;
}
}
One solution would be to call DebtorRepository::findByUserId($this->id) in the getDebtors() method, but I don't think that is the right way to go.
I also thought about have a #PostLoad method that loads the users debtors into the $debtors property.
I'm not much for having that much logic inside my models, but i don't know what other choices I have?
Furthermore, I also need to be able to access various other models through the Debtor model and so i have the issue again.

If resource for your API, I think you should have service which will take care of loading that (as probably you need to check for errors/authentication/...). So I would load it on demand from service.
But if you really want to have it accessible in the entity, then you can use Entity Listener, hook to the postLoad method and inject it there. In this case your entity will still stay thin, because this heavy logic will be in external service.

Related

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

Dependency injection and Model entities - the right way ?

My symfony2 application has the following structure:
There is a service data_provider, which gets data from various external sources and represents it as entity objects.
Some objects has relations. Currently I am loading relations in controller or helper-services if needed.
It is not very convenient, sometimes I want to get relations from my entity ojbect. To do this I need access to data_provider service.
I want to implement something like doctrine lazy-loading, what is the right way of doing this ?
Some obvious solutions - to inject data_provider in every entity instacne, or to some static property, or to make some static methods in service, or to use evenet dispatcher, but I don't think it is the right way
Made some research of ObjectManagerInterface as Cerad suggested, and found this peace of code: https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/PersistentObject.php
PersistentObject implements ObjectManagerAware interface, it has private static property where ObjectManager is stored.
So I ended with this:
class DataProvider
{
public function __construct()
{
...
AbstractEntity::setDataProvider($this);
}
}
abstract class AbstractEntity
{
private static $dataProvider;
public static function setDataProvider() {...};
protected static function getDataProvider() {...};
}
The main purpose of services in Symfony (and not only) is exactly this one - to deliver distinguished functionalitys globally over your project.
In this regard, a single service, in your case - dataProvider, should always deliver a single entity. If you have to deal with multiple entities returned from one data source, wrap the data source deliverer into a service itself, and then define one service per each entity with the deliverer injected into it.
Then you can inject the respective entity services into your controllers.

Override doctrine lazy loading method to include calculated data to an object

let say that I have something like this:
class User{
/**
* Object that is lazy loaded
*/
private $statistic; //object with some stored data and some calculated data
}
Some of the $statistic's properties are stored in the DB but some other of them are calculated by analyzing the user activity (querying data records).
the thing is that I got a $user and when I run $user->getStatistic() as spected, I get the stored $statistic data and I need to add more data using sql queries and I don't know where to program this functionality.
¿overriding the Repository? I try overriding the find() method but it doesn't work
I know that if I use the active record pattern this can be done with no problem giving that I can access the DB in the construct method or the getters maybe, etc.
but I don't know how this could be done with doctrine standard behavior.
I believe that there must be a way to ensure that every instance of the Statistic Class have this calculated data on it.
I'm using symfony... maybe a service or something...
There are a number of ways to solve your problem.
Doctrine listener
This is probably the easiest one. Use Doctrine postLoad event to fill out data you need on your User model.
This is a completely valid approach, but has a couple of drawbacks:
This will be ran every time doctrine fetches an User entity instance from database. If it fetches a list of 100 users, it will be ran 100 times. This could make a performance problem if you do some time-consuming tasks in there.
They are hard to debug: if an error is encountered, events usually make code flow a lot less clear and therefore make debugging harder. If you do simple stuff, and don't overuse them, then it's probably fine, otherwise think about other options.
Abstracting away doctrine
I'm strongly in favor of this approach and I use it in almost every project.
Even though I'm one of the people who try to have the least amount of layers and indirection necessary, I do think that wrapping data persistence into your own services is a good idea. It isolates rest of your system from having to know how your data is stored.
I suggest not using Doctrine repositories/Entity manager directly. Instead, wrap them in your own services.
This makes your persistence API squeaky clean and obvious, while giving you ability to manipulate your models before they reach your business logic.
Here is an example of how I would approach your problem:
# src/AppBundle/Repository/UserRepository.php
class UserRepository
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function findById($userId)
{
$user = $this->em->getRepository(User::class)->find($userId);
$this->calculateUserStatistics($user);
return $user;
}
public function save(User $user)
{
$this->em->persist($user);
$this->em->flush();
}
// ...
private function calculateUserStatistics(User $user)
{
// calculate and set statistics on user object
}
}
This approach has a number of advantages:
Decoupling from Doctrine
Your business code is no longer coupled to Doctrine, it doesn't know that Doctrine repositories/entity manager exist at all. If need arises, you can change UserRepository implementation to load users from remote API, from file on disk....from anywhere.
Model manipulation
It allows you to manipulate your models before they get to business logic, allowing you to calculate values not persisted as a field in database. Eg, to fetch them from Redis, from some API or other...
Cleaner API
It makes it really obvious what abilities your system has, making understanding easier and allowing easier testing.
Performance optimisation
It doesn't suffer from performance issues as first approach. Take the following example:
You have $eventsCount field on your User model.
If you load list of 100 users and use first approach, you would need to fire 100 queries to count number of events belonging to each user.
SELECT COUNT(*) FROM events WHERE user_id = 1;
SELECT COUNT(*) FROM events WHERE user_id = 2;
SELECT COUNT(*) FROM events WHERE user_id = 3;
SELECT COUNT(*) FROM events WHERE user_id = 4;
...
If you have your own UserRepository implementation, however, you can just make method getEventCountsForUsers($userIds) which would fire one query:
SELECT COUNT(*) FORM events WHERE user_id IN (:user_ids) GROUP BY user_id;
You can implement your own repository to include your own sql queries, Symfony have documented it pretty well in their documentation, see here.
Here's how I've done it previously using annotations (this can be done via yaml too, just check the link above)...
Entity:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
// ...
private $statistic;
// ...
}
User Repository:
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function getUserStats()
{
// Use query builder to build your query for stats here
}
}
Since your custom repository is extending the EntityRepository, you will still have access to Doctrines lazy load methods (find, findBy, findAll etc...)
Maybe you don't need exactly User instance.
You can use NEW() syntax in DQL.
class UserStatDTO
{
private $user;
private $statistic;
private $sum;
public function __construct(User $user, $statistic, $sum)
{
$this->user = $user;
$this->statistic = $statistic;
$this->sum = $sum;
}
public function getUser()
{
return $this->user;
}
public function getSum()
{
return $this->sum;
}
public function getStatistic()
{
return $this->statistic;
}
}
class UserRepository
{
public function getUsersWithCalculatedStat()
{
return $this->getEntityManager()->createQuery('
SELECT NEW UserStatDTO(
u, u.statistic, u.count1 + u.count2
) FROM User
')->getResult();
}
}
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#new-operator-syntax

Laravel Eloquent Relationships with Repository/Service Design Pattern

I am currently working on a web app that has been set up using the Repository/Service Layer Design Pattern, i.e. I have service layer that does any necessary business logic before running any methods within the repository. I have facades for each one of my models which access their respective service layers, and this has been fine for the most part. However, now that I am trying to set up Eloquent relationships, the facades seem to be causing a massive headache as I am not sure which direction I should be going.
Take the following code:
class Account extends Eloquent {
// Our table name
protected $table = "accounts";
// Our primary key
protected $primaryKey = "id";
/**
* Role Relationship
*
* Returns a list of roles associated with
* this account
*/
public function roles() {
return $this->hasMany('Role');
}
}
This will not work as is, because instead of using the entity class of Role, it is using the Role Facade. I have figured out a workaround for this, by setting an alias for the Entity with a slightly different name, such as RoleEntity so that
public function roles() {
return $this->hasMany('RoleEntity');
}
will work, however this doesn't seem like the most optimal solution.
My question is, is the practice ok? Or better yet, should this be happening at all? And if not, how do I fix it/where did I go wrong?
You have two classes with the same name in the same namespace. Use different namespaces so you can use the same class names.
I usually use \Models to locate my models classes.
At the top of each model file:
namespace Models;
In your controller or any part of your app:
\Models\Role::first();
Note that changing the namespace on your model will require you to add the namespaces of other classes i.e. Str, Eloquent, Url, Redirect, etc.
use Eloquent;
use URL;
In your model, you also have to pass the namespaces in the relationship functions, i.e.:
public function roles() {
return $this->hasMany('\Models\Role');
}

Symfony 2 - FOSUserBundle - Create and Persist another entity inside the constructor of User class

I'm making a website where users can create albums.
I would like to create a default album per user.
I would like to create an Album entity and to persist it in the constructor of my User class.
Is it possible ?
I just know that the entityManager is not accessible from an Entity... That's why it's a problem for me.
Even though this technically IS possible I would strongly recommend you not to do this.
To answer your question, it is possible and it would be done like this:
class User extends FOSUser
{
/**
* #ORM\OneToMany(targetEntity="Album", cascade={"persist"})
*/
private $albums;
public function __construct()
{
$this->albums = new ArrayCollection();
$this->addAlbum(new Album());
}
public function addAlbum(Album $album)
{
$this->albums[] = $album;
}
public function getAlbums()
{
return $this->albums:
}
}
With setup like this whenever you create a new user and save it, a related album will be created together with it. I have to repeat, even though it's possible, don't do it like this.
Good solutions
There are few strategies that can be used to achieve what you want.
FOSUserBundle master
If you're not using 1.3.x version of FOSUserBundle but master, you can see that RegistrationController fires a few events. The one you're interested in is FOSUserEvents::REGISTRATION_INITIALIZE. You should create an event listener and add album to user in your listener.
FOSUserBundle 1.3.x
If you're using one of older versions, these events don't exist unfortunately and you can do it two ways.
Extend FOSUserBundle UserManager and override createUser method. You can add your album adding logic there. I would prefer this approach.
Override FOSUserBundle RegistrationController::registerAction. It can be viable option sometimes but in your case I think option 1 is better.

Categories