Override Persister:loadAll in EntityRepository - php

I have the need to display data from a table in a pager and at the same time get a count on records in a child table. There are too many child records to load into memory and count, so I want to modify how the query is built in my EntityRepository.
Ideally I don't want to re-implement the pager functionality, so I am looking to override findBy in my EntityRepository and add the count(), join and group by in my query?
How do I do this best? I'm using Symfony 2.8, Doctrine 2 and PagerFanta
I also found this http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/filters.html but it seems thin on documentation

I hope I can help you!
If I understand correctly, you want to load from the database for each instance of an object the number of child objects in addition to all the fields of this object - as an additional field. Right?
I don't know, how the parent entity and the child entity are named in your model. But I will give a working example for your tasks from my web application. You have only to rename the parent and child entity.
There are affiliate programs, each of which has a finite set of accounts in my application. So, I have a parent entity called 'AffiliateProgram' and I have a child entity called 'AffiliateProgramAccount'.
You should not override the standard method in the repository for class of your entity. You just have to add your method according to your needs.
First thing, create a repository for class of your parent entity (How to Create custom Repository Classes). I do it in the YAML file with a description as follows:
Analytics\TrafficStatisticsBundle\Entity\AffiliateProgram:
type: entity
table: affiliate_program
repositoryClass: Analytics\TrafficStatisticsBundle\Entity\AffiliateProgramRepository
Then you must create a repository class of your parent entity according to the path in the description for Doctrine. I keep the classes of repositories together with the model classes in the 'Entity' directory. Now you can create your custom methods in the created repository according to your individual needs.
I suggest to use Doctrine DBAL to solve your problem (How to use Doctrine DBAL). Here is an example of my query, absolutely identical to yours:
use Doctrine\ORM\EntityRepository;
class AffiliateProgramRepository extends EntityRepository {
/**
* #return array
* #throws \Doctrine\DBAL\DBALException
*/
public function findAllAffiliatePrograms() {
// $stmt = $this->getEntityManager()
// ->getConnection()
// ->prepare('
// SELECT COUNT(apa.id) AS Number_of_accounts, ap.*
// FROM affiliate_program AS ap
// LEFT JOIN affiliate_program_account AS apa ON apa.affiliate_program_id = ap.id
// GROUP BY ap.id');
// $stmt->execute();
// return $stmt->fetchAll();
$stmt = $this->getEntityManager()
->getConnection()
->prepare('
SELECT COUNT(apa.id) AS Number_of_accounts, ap.*
FROM affiliate_program AS ap,
affiliate_program_account AS apa
WHERE apa.affiliate_program_id = ap.id
GROUP BY ap.id');
$stmt->execute();
return $stmt->fetchAll();
}
}
Here notice that I don't use object names ('AnalyticsTrafficStatisticsBundle:AffiliateProgram' in my case) for operators FROM, [LEFT | RIGHT | INNER | OUTER] JOIN as it must be used in DQL, etc. Instead, I use the real table names.
Note: The query without using the JOIN operator executes faster. In my example I showed two ways - using the JOIN operator and the same with the using of WHERE operator. Proof:
and
Now you can get all the objects according to your query in the controller, simply by calling the newly created method:
<?php
namespace Testing\TestBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* TestController
*
* #Route("/test")
*/
class TestController extends Controller {
/**
* #Route("/", name="test_index")
* #Method("GET")
*/
public function indexAction() {
$em = $this->getDoctrine()->getManager();
$affiliatePrograms = $em->getRepository('AnalyticsTrafficStatisticsBundle:AffiliateProgram')->findAllAffiliatePrograms();
return $this->render('TestingTestBundle:Test:test.html.twig', [
'result' => $affiliatePrograms
]);
}
}
And to make sure that everything works, you just write the following snippet in .twig file (for example):
{{ dump(result) }}
Materials also, see here:
How to use Raw SQL Queries in Symfony 2
Raw SQL Queries
Executing SQL directly in Symfony2 Doctrine
I hope that's all you need!

Related

How to customize Doctrine Inheritance Type

I have a parent entity and many inherited entity with JOINED strategy.
in Doctrine when i want to update or select some row of child table doctrine create heavy query with inner join.
parent class:
namespace Foo\BarBundle\Entity;
/**
* #ORM\InheritanceType("JOINED")
*/
abstract class Parent {
//..
}
/**
* #ORM\Entity(repositoryClass="Foo\BarBundle\Entity\childRepository")
*/
class Child extends Parent {
//..
}
I want to remove inner join that automatically generated in create query builder inside of my repository class.
also findBy() or findOneBy() if it can.
In my repository class:
namespace Foo\BarBundle\Entity;
class ChildRepository extends ParentRepository {
public function getAllBySomeQuery() {
$qb = $this->createQueryBuilder('child');
//...
}
}
That was a sample of generated query:
SELECT ch.some_field FROM child_entity ch INNER JOIN parent_entity parent1 ON ch.id = parent1.id
I temporary solved this problem with change JOINED strategy to SINGLE_TABLE but this cause me other problem that not related to this question.
How can i doing nested join instead of using $qb = $this->createQueryBuilder('child') inside my repository class? or how to create view from database and pass it to doctrine in best way.

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

How to extend from Doctrine-Entity without Discriminator-Column

The challenge:
I want to re-use an existing entity of a third party bundle with class-inheritance in that way, that still only one table remains and no extra stuff will be necessary. That means: no discriminator-column and no JOINs.
Instead only the final most inherited class should be queryable, add some properties to the base entity and just use one table containing all columns, that are added to the entity from itself and thru inheritance.
To be clear here: I am not interested in classic table inheritance. I just want to extend a base class with additional columns in that way, that the table in the database represents the sum of all needed columns.
There is no need to be able to create an instance of the base entity.
For those who are interested, i explain the reason below.
Base entity of third party library:
Sylius\UserEntity (TableName: "sylius_user")
============================================
ColA, ColB, ColC
My class:
MyProject\UserEntity : Sylius\UserEntity (TableName: "user") <---- overwrite the base table name
========================================
ColD, ColE, ColF
The model above represents the final approach: my user entity extends syslius' user entity and should be persisted in and queried from the table "user" (instead of both "user" AND "sylius_user"), which contains all columns of the final extended entity:
The Table-Structure:
Thus, only one table would exist in my szenario.
Table "user":
=============
ColA, ColB, ColC, ColD, ColE, ColF
The first 3 columns are properties in base entity, and the last 3 columns are properties in "my" user entity which gots the first three thru inheritance.
What i did:
My user class looks like so:
use \Sylius\Component\User\Model\User as BaseUser;
/**
* User
*
* #ORM\Entity(repositoryClass="MyAppName\UserBundle\Repository\UserRepository")
* #ORM\Table(name="user", uniqueConstraints= ... */
class User extends BaseUser
{
The UserRepository:
class UserRepository extends EntityRepository
{
/**
* #param string $usernameOrEmail
* #return User
*/
public function findByUsernameOrEmail($usernameOrEmail)
{
$qb = $this
->createQueryBuilder('u')
->where('u.username = :search OR u.email = :search')
->setParameter('search', $usernameOrEmail);
try {
return $qb->getQuery()->getSingleResult();
}
catch(NoResultException $e) {
return null;
}
}
}
This query results in selecting columns from the table "user", but the query tries to select the columns twice each with a separate table alias. The resulting query fails because it is broken.
What Doctrine does here, looks like so:
SELECT s0.ColA, s0.ColB, s0.ColC,
u0.ColD, u0.ColE, u0.ColF
FROM user u0
As everyone can see, an additional table alias "s0" is used here without to reference it with a table. What i wanted doctrin to do, is:
SELECT u0.ColA, u0.ColB, u0.ColC,
u0.ColD, u0.ColE, u0.ColF
FROM user u0
Ho to achieve this?
For those who are interested in the purpose of this task:
We want to add sylius bundles to our long existing symfony application having already an own user-bundle with user model class and existing data out there.
So we'd like to extend the user class of sylius with adding our own properties to build a "bridge" between the sylius class and our user class. The difference between both is slim on the property-side and lies in only a few columns to rename and add some special properties and methods. But we have lots of relations from our user class to other entities, which wouldn't be an issue if we could doctrine get to ignore the table inheritance staff and just act as a plain class-re-using-thing here.
Is the base class a MappedSuperclass?
As long as the base class is defined as MappedSuperclass, you can simply extend the class and define the extending class as Entity.
Sylius defines the entities as a MappedSuperclass by default. A subscriber (LoadOrmMetadataSubscriber) passes each entity and sets MappedSuperclass to false if necessary, meaning it changes the MappedSuperclass to an Entity when the class is defined in the config.

Doctrine, MVC, Symfony: where may I use Doctrine? May I use it in controller?

Lets see my architect:
Model:
// links table: (ID, LINKNAME)
Class Link extends Link_base
{
}
Controller:
public function index()
{
$this->links = new Doctrine - here I build the query, SELECT, ORDER BY, etc
}
in this example, the model can be remain empty (no serious logic), all I need is a select with an order by. Im not sure I can use Doctrine in controller though - should I remake it like this?
Class Link extends Link_base
{
public function getLinks()
{
return new Doctrine - here I build the query, SELECT, ORDER BY, etc;
}
}
Controller:
public function index()
{
$this->links = Links::getLinks();
}
Im not sure which way seems to be OK. Of course, when selecting needs a more complex, formatting todo-s, it goes to the model or helper - but I feel like I just made a new (unnecessary) layer. This getLinks() used only once. In other words: Doctrine may be only used in model, or can it be used in controllers too?
Your entities (or models if you prefer that name) should not know how they are saved to / retrieved from the database. They should just be simple PHP objects, only containing a number of properties (corresponding to the database columns) and their getters and setters.
(If you are interested, read a bit about the single responsibility principle which states that every class should have one, and only one responsibility. If you make your entities both responsible for storing data and knowing how to save that data in the database, you will have a greater chance of introducing bugs when one of those things changes.)
You can fetch entities from inside your controller:
<?php
namespace Your\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LinkController extends Controller
{
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAll();
// do something with the result, like passing it to a template
}
}
However, you might need a more complex query (that includes sorting and filtering) and you might need to run that query from multiple controllers. In that case, you don't want to duplicate that logic to multiple controllers, you want to keep that logic in one central place.
To do so, create a repository:
<?php
namespace Your\Bundle\Repository;
use Doctrine\ORM\EntityRepository;
class LinkRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT l FROM YourBundle:Link l ORDER BY l.name ASC'
)
->getResult();
}
}
And add the repository class to your mapping:
Your\Bundle\Entity\Link:
type: entity
repositoryClass: Your\Bundle\Repository\LinkRepository
(check the Symfony's documentation about custom repositories if you're using XML or annotations instead of Yaml)
Now in your controller, you can simply update your fooAction method so it uses your custom repository method:
public function fooAction()
{
$links = $this->getDoctrine()
->getRepository('YourBundle:Link')
->findAllOrderedByName();
}
For more information, Symfony's documentation includes a great article about Doctrine. If you haven't done so already, I'd definately recommend reading it.

PHP Pattern - how to retrieve collection of objects

Situation:
There is simple User class which is Doctrine entity (I skipped comments to keep the code short):
class User {
protected $iUserId;
protected $sName;
}
Question:
How to retrieve collection of objects of class User from the controller?
Follow up:
Until now, we were creating methods like getUsers() in User class which gets data from DB, creates User class objects and returns it.
Now, I'm wondering if it's not a better solution to create class like UserCollection which would handle data retrieving and creating User objects? Maybe I should make use of \Doctrine\Common\Collections\Collection class somehow?
What I'd like to accomplish is easy way to handles i.e. where clauses. UserCollection class could have QueryBuilder object on which I could operate from controller like this:
$oUserCollection = new UserCollection();
$oUserCollection->setWhere( 'u.iUserId = 1' );
$aUsers = oUserCollection->getUsers();
...
Please share your thoughts on that topic.
Update:
Doctrine provides a concept of repositories of entities which I think maybe the solution I'm looking for.
You have two options:
If your criterias are simple, eg. you just want to filter on a single property, or you don't need filtering at all, you can use the findBy() and findAll() methods, on the EntityRepository:
// find all users
$repository = $em->getRepository('My\\User');
$users = $repository->findAll();
// find users who are marked as active
$users = $repository->findBy(array('active' => true));
// sort users by age
$users = $repository->findBy(array(), array('age' => 'DESC'));
If you have complex(er) requirements, or your finders will be used from multiple places, you will have to create a custom EntityRepository, and group your finder logic there. You have to specify in the mapping that you want to create your own EntityRepository for this entity, the method for this varies depending on what mapping driver you use (annotation, yaml, xml). For example, in yaml you would have to place this line in your mapping:
RepositoryClass: My\Repository\UserRepository
And then create the file:
namespace My\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository {
public function findVIPUsers() {
$query = $this->_em->createQuery("Your query here");
return $query->getResult();
// you can also use $this->findBy(), or $this->findAll() here
}
}
And then in your controller, when you call $em->getRepository('My\User') it will return the custom repository you just created.

Categories