How to extend from Doctrine-Entity without Discriminator-Column - php

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.

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!

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

Symfony&Doctrine2: Value of `mappedBy` annotation?

I am learning Symfony 3 with Doctrine 2.
When I have OneToMany relationship in Doctrine entity, what exactly I have to put into mappedBy annotation?
Is it the table name of current entity?
Or is it the entity shortcut?
Or is it the actual class name?
Imagine this simple example:
<?php
namespace AppBundle\Entity;
/**
* #ORM\Entity
* #ORM\Table(name="blog_category")
*/
class Category
{
// ...
/**
* #ORM\OneToMany(targetEntity="Article", mappedBy="category")
*/
private $articles;
// ...
}
Why is the "category" correct value for mappedBy? Why isn't it "blog_category" or "Category" (uppercase "C")? Or "AppBundle:Category"?
Now I figured it out. It is the name of related's entity class variable :-)
To give a complement, almost everything you could do in doctrine (could surely be applied for any other ORM/ODM), in almost every contexts (QueryBuilder, findBy methods, ...) you'll use property names rather than column names.
The reason is quite simple, an ORM deals with objects and their properties, abstracting the real tables and their columns, no matter of the database engine or anything else.

Doctrine Single Table Inheritance Query All Instances Of

I'm working on a notification system, so I have a notification abstract class and sub-classes (forumPostNotification, privateMessageNotification, etc). They are stored using Single Table Inheritance, so they're all in one table with a discriminating field.
I would like to get all the notifications that apply to a user at once, instead of having to query each type of notification individually, however I'm not sure how to do this in DQL/symfony (it would be easy in SQL).
I believe this: (Doctrine 2: how to write a DQL select statement to search some, but not all the entities in a single table inheritance table) is similar to what I'd like to achieve, but I'm not sure how to query the abstract object. It's also not in the Entity directory, but in Entity/Notifications/Notification.php.
I'll add some code for clarification:
Notification.php
/**
* Notification Class
*#ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "notification"="Notification",
* "forumPostNotification"="ForumPostNotification",
* ...
* })
* #ORM\Table(name="notification")
*/
abstract class Notification
{
/**
* #ORM\ManyToOne(targetEntity="Acme\MainBundle\Entity\User", inversedBy="notifications")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
//...
}
ForumPostNotification.php
/**
* Forum Post Notification
* #ORM\Entity
*/
class ForumPostNotification extends Notification
{
//..
}
PrivateMessageNotification.php
/**
* Private Message Notification
* #ORM\Entity
*/
class PrivateMessageNotification extends Notification
{
//..
}
I'd like to be able to do something like this, one way or another (I understand that I can't query from Notification, since it's an abstract class. I just wrote it like this to convey what I'd like to achieve):
$notifications = $em->createQuery('
SELECT n
FROM AcmeMainBundle:Notification n
WHERE n.dateDeactivated IS NULL
ORDER BY n.dateCreated ASC
')->getResult();
we have created similar situation with orders and products. Because you can have different types of product inside one order we made one parent class Product and inherited ex. SpecialProduct, SalesProduct etc.
We were able to define a relation between Order (in your case User) and "Product" (in your case Notification), and that's all. We get every Products for the order by $order->getProducts(). The method returns us a list of well prepared products with specific classes ex
order->products[SingleProduct, SingleProduct, SingleProduct, SpecialProduct, SalesProduct, SingleProduct]
So, in conclusion. Only one thing you need to do to get all notifications per user is defining a proper relation between your user and abstract parent class.
It was simply, but... it's not so good when you're going to get only notification from specific type. The query passed in your link is not pretty. In my opinion you should create a proper queryBuilder - it's quite similar.
At the end you cannot use the $user->getNotifications(), but you have to get notifications directly from repository -
$em->get('AcmeBundle:User')->getForumPostNotifications()
Kind regards,
Piotr Pasich
This is in fact so simple, I'm amazed they've not documented it properly.
In your repository, do:
return $this->_em->createQueryBuilder()
->select('notification')
->from(Notification::class, 'notification')
// whatever else you need
please notice creating the query builder from entity manager, not entity repository itself

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.

Categories