Symfony2: Handling multiple ORM relationships - php

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

Related

Working with Doctrine array of enum and store it in a separate table

I'm currently building Entity model and one of my Doctrine Entities have ManyToMany relation with an external dictionary (like ENUM). So the entity field will be an Array of Enum.
I'm looking for a way to have it as an array field on my entity, but to store it as a separate DB table.
Would like to get any advice/links/etc.
The question is a bit out of context but..
A many to many is already an array (Iterator) in your entity.
You can create your own entity acting as a Many To Many and set the column as enum.
Finally, I've decided to create an Entity to store this relation. To make sure it will be deleted on unlinking from the parent entity, I've used the orphanRemoval=true option on the OneToMany relation side.
class Entity {
/**
* #ORM\OneToMany(targetEntity="EntityType", mappedBy="entity", orphanRemoval=true)
*/
protected $types;
}
class EntityType {
/**
* #ORM\ManyToOne(targetEntity="Entity")
*/
protected $entity;
/**
* #ORM\Column(type="MyEnum")
*/
protected MyEnum $type;
}

Can I use the same table to represent different Entities in Symfony?

I am migrating an old PHP project to Symfony. I am trying to create the entities based on the existing database schema which I can not change. I am facing a problem :
There is a table that would represent two different entities. Basically, there is a boolean (a tinyint(1)), if the boolean is false, then the row of the table is representing a cart. If the boolean is true, then the row is representing an order.
Is it possible for Doctrine to make the distinction between these and to fetch those entities accordingly ? The solution I was willing to implement was creating several entities and overwrite the find() and findAll() methods in these entities' repositories. Is there another way to achieve that ?
This is what doctrine call Inheritance Mapping.
So you'll have one Cart entity and one Order entity extended it.
/**
* #ORM\Table()
* #ORM\Entity(repositoryClass="App\Repository\CartRepository")
* #ORM\InheritanceType(value="SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="is_order", columnDefinition="BOOL DEFAULT FALSE")
* #ORM\DiscriminatorMap(
* value={
* CART::IS_CART=Cart::class,
* CART::IS_ORDER=Order::class
* }
* )
*/
class Cart {
const IS_CART = FALSE;
const IS_ORDER = TRUE;
... // Entity field, getters, setters, functions...
}
Then your Order Entity.
/**
* #ORM\Entity(repositoryClass=OrderRepository::class)
*/
class Order extends Cart {...}
There is maybe some mistake in this code I didn't test it but it should be ok.

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.

Symfony2 & PHP Traits with entities relation embedded

In Symfony2, I just try recently to think in terms of traits, to create some sort of behaviors.
Let's say I have an address attribute in an entity. I externalized attributes, getters and setters related to this in an AddressableTrait.
But what if address become an entity? I started to try to define my OneToMany relation in my trait, as if it was in a regular entity :
use Doctrine\ORM\Mapping as ORM;
class AddressableTrait {
/**
* #var
* #ORM\OneToMany(targetEntity="XXXX\GlobalBundle\Entity\Address", inversedBy="What to put here" )
*/
protected $addresses;
/**
* #return ArrayCollection
*/
public function getAddresses()
{
return $this->addresses;
}
/**
* #param ArrayCollection $addresses
*/
public function setAddresses($addresses)
{
$this->addresses = $addresses;
}
}
What to put in the inversedBy? The purpose of the trait if precisely to embed all the behavior feature, so I think that at least using traditionnal annotation/YML/XML,it's not possible to achieve.
I digged a bit into it and found this very interesting link that seems to allow you to defines relation via events, but there is still logic to add to "finish" relations.
UPDATE :
Using the above link, I managed to created dynamic ManyToMany relation. the schema update works when creating, but if I comment the dynamic relation, a schema:update --dump-sql doesn't remove it. It seems to work add-only. Any clue to force the dynamic mapping to stick to the real relations addition/removal?
Thanks a lot for your answers !
Nicolas
I encountered a problem using traits in entities. For regular database values (scalar, DateTime) traits worked fine, but when I tried to define entity relations in traits the doctrine migrations bundle would convert the property to a varchar field.
The only way I could find to fix creating proper entity relation properties was by moving them out of the trait and into the entity class itself.

Categories