How to customize Doctrine Inheritance Type - php

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.

Related

Override Persister:loadAll in EntityRepository

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!

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.

Discriminator in a Joined Table with Doctrine2

I have an abstract parent class called Divers which is extended by few other classes.
So, I use inheritance mapping with D2 using Single Table Inheritance strategy.
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ParentClass
*
* #ORM\Table(name="PARENTCLASS")
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="idtable", type="string")
* #ORM\DiscriminatorMap({
* "CHILD-CLASS1" = "ChildClassOne",
* "CHILD-CLASS2" = "ChildClassTwo",
* "CHILD-CLASS3" = "ChildClassThree",
* "CHILD-CLASS4" = "ChildClassFour"
* })
*/
abstract class ParentClass
{
...
}
What I want to achieve is to display the discriminator in the browser with a little description that explains what is it to the user.
I googled for a solution like putting the discriminator in a joined table but found nothing.
Do you have any advice to achieve my goal ?
Thanks by advance for your help.
The discriminator column has special meaning for Doctrine 2 and thus cannot be part of a relation.
But there is an easy work around. Just add another column and give it the same value that your discriminator column has. The value will never change so it's easy enough to do. You can then of course use your new column in the same way as any other column.
I know having two columns with the same value is not ideal from a database perspective. But from an object perspective, it's no big deal since the discriminator column is never exposed as a property. And it's just the way doctrine works. It wants that column all to itself.
You can achieve it using PHP, whitout adding another field in the db as long as you don't need the field in a SQL query.
Since the discriminator is an abstract class, just adding a public abstract method returning your hard-coded discriminator value would do the trick. Then you can use your entity in twig or a json serializer.
abstract class ParentClass {
public abstract function getDiscriminator(): string; // The discriminator type
}
class ChildClassOne extends ParentClass
{
public function getDiscriminator(): string
{
return 'CHILD-CLASS1';
}
}
If you need to fetch in SQL, use $qb->andWhere($qb->isInstanceOf(ChildClassOne::class)) since the method or discriminator attribute is not available in sql.

Symfony2: Handling multiple ORM relationships

I need to create relationships for my entities, whereby I have the following scenario:
Entity A - OneToMany relationship with Entity B
Entity B - OneToMany relationship with Entity C
When doing a 'find' on Entity A, ORM automatically queries for all matching Entity B rows. What isn't clear to me is whether ORM would automatically query for all matching Entity C rows.
$em = $this->getDoctrine()->getEntityManager();
$project = $em->getRepository('MyAppMainBundle:Project')->find($id);
$client = $project->getClient();
$clientProjects = $client->getClientProjects();
If this scenario is possible, what is best practice to implement it?
Thanks,
JB
UPDATE
I actually figured out how to do this with the mapping relationships. Key is setting up the mapping in the entities, so that Entity B properly maps in turn to Entities C. See the answer.
Just map the relationships properly and you'll be set. It looks like you want a OneToMany/ManyToOne.
Here's how I solved the issue:
Parent Entity 'Client':
/**
* #ORM\OneToMany(targetEntity="ClientProject", mappedBy="client")
*/
private $clientProjects;
Child Entity 'ClientProject':
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="ClientProject")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
The controller can then use the following code:
$em = $this->getDoctrine()->getEntityManager();
$project = $em->getRepository('MyAppMainBundle:Project')->find($id);
$client = $project->getClient();
$clientProjects = $client->getClientProjects();
foreach ($clientProjects as $clientProject) {
echo $clientProject->getSomeProperty();
}
If u want all Entities to be fetched in a single request it is better to create custom repository class and use QueryBuilder to make a query where A joins B joins C. In other case the result will depend on LAZY_FETCH options, AFAIR

Creating One-To-Many mapping where Many can be different object types

I am exploring the feasibility of retrofitting an existing database to using Doctrine.
I have three tables, a StockRequest, SalesOrder, WorkOrder. StockRequest has fields Type and TypeNo. Type is used to decide whether it has a relationship with a Sales Order or a WorkOrder, and TypeNo is the key of the SalesOrder/WorkOrder.
What is the recommended method to maintain this relationship?
Reading the doctrine documentation, it mentions a repository class, which I could use to conditionally fetch based on Type, maintaining only one StockRequest Entity.
The other possibility would be to Subclass the StockRequest class so that I have SalesOrderStockRequests and WorkOrderStockRequests.
I would make it with inheritanceand relationship, example:
abstract class AbstractOrder {
common fields
}
class SalesOrder extends AbstractOrder {
}
class WorkOrder extends AbstractOrder {
}
then
class StockRequest {
relationship pointing to AbstractOrder
}
and when u getOrder() { return $this->order; }
Doctrine can return u instanceof either SalesOrder or WorkOrder

Categories