Doctrine Single Table Inheritance Query All Instances Of - php

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

Related

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.

Doctrine2 architecture design - entity with supplemental data

first of all I'd like to apologise in advance for the question title as it might not ideally explain the situation I'm dealing with. I've done some research but had no luck finding a suitable solution.
My Case:
I'm having an entity, say an Activity, which represents activities that customers on my website perform. It can be of a different type like: page_view, comment, login, purchase etc. What I'm trying to do is to assign supplemental data to each activity eg. for a comment activity supplemental data would be a comment entity, for purchase an array of product entities etc.
For now I'm simply defining activity types as constants in ActivityInterface, and then use that values to get an EntityManager instance that is capable of resolving the correct entity/entities from parameters provided in http request. This solution has many downsides and I'm not sure if that's event a right approach.
Could you guys help me out by giving me some hints or redirect me to some useful publications on how to bite that subject? Thanks.
I recommend using a Mapped Superclass like so:
/** #MappedSuperclass */
class Activity
{
/** #Id #Column(type="integer") */
protected $id;
/** #Column(type="string") */
protected $generalActivityProperty;
// ... more fields and methods
}
/** #Entity */
class Comment extends Activity
{
/** #ManyToOne(targetEntity='CommentEntity') */
private $commentEntity;
// ... more fields and methods
}
/** #Entity */
class Purchase extends Activity
{
/** #OneToMany(targetEntity='ProductAssociations') */
private $products;
// ... more fields and methods
}
This will create a single table per Class (but not for 'Activity') each containing all fields/methods defined in 'Activity' and the additional fields/methods as defined in the specific classes.
You can even query all activities by the entityManager->getRepository('Acticity')->find() e.g. and get a list of Comment, PageView etc. Objects back.
It's not tested, but you should get an idea how to do it.
You could as well use Single Table Inheritance or Class Table Inheritance. Check it out here.
But I guess in your case a Mapped Superclass should be perfect.

Advice for implementing field whitelists with Symfony/FosRestBundle/JMS Serializer

I'm currently learning how to implement a relatively simple API using Symfony 3 (with FOSRestBundle) and JMS Serializer. I've been trying recently to implement the ability to specify, as a consuming client, which fields should be returned within a response (both fields within the requested entity and relationships). For example;
/posts with no include query string would return all Post entity properties (e.g. title, body, posted_at etc) but no relationships.
/posts?fields[]=id&fields[]=title would return only the id and title for posts (but again, no relationships)
/posts?include[]=comment would include the above but with the Comment relationship (and all of its properties)
/posts?include[]=comment&include[]=comment.author would return as above, but also include the author within each comment
Is this a sane thing to try and implement? I've been doing quite a lot of research on this recently and I can't see I can 1) restrict the retrieval of individual fields and 2) only return related entities if they have been explicitly asked for.
I have had some initial plays with this concept, however even when ensuring that my repository only returns the Post entity (i.e. no comments), JMS Serializer seems to trigger the lazy loading of all related entities and I can't seem to stop this. I have seen a few links such as this example however the fixes don't seem to work (for example in that link, the commented out $object->__load() call is never reached anyway in the original code.
I have implemented a relationship-based example of this using JMSSerializer's Group functionality but it feels weird having to do this, when I would ideally be able to build up a Doctrine Querybuilder instance, dynamically adding andWhere() calls and have the serializer just return that exact data without loading in relationships.
I apologise for rambling with this but I've been stuck with this for some time, and I'd appreciate any input! Thank you.
You should be able to achieve what you want with the Groups exclusion strategy.
For example, your Post entity could look like this:
use JMS\Serializer\Annotation as JMS;
/**
* #JMS\ExclusionPolicy("all")
*/
class Post
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $id;
/**
* #ORM\Column(type="string")
*
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation", "withoutAssociations"})
*/
private $title;
/**
* #JMS\Expose
* #JMS\Groups({"all", "withFooAssociation"})
*
* #ORM\OneToMany(targetEntity="Foo", mappedBy="post")
*/
private $foos;
}
Like this, if your controller action returns a View using serializerGroups={"all"}, the Response will contains all fields of your entity.
If it uses serializerGroups={"withFooAssociation"}, the response will contains the foos[] association entries and their exposed fields.
And, if it uses serializerGroups={"withoutAssociation"}, the foos association will be excluded by the serializer, and so it will not be rendered.
To exclude properties from the target entity of the association (Fooentity), use the same Groups on the target entity properties in order to get a chained serialisation strategy.
When your serialization structure is good, you can dynamically set the serializerGroups in your controller, in order to use different groups depending on the include and fields params (i.e. /posts?fields[]=id&fields[]=title). Example:
// PostController::getAction
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder;
$serializer = SerializerBuilder::create()->build();
$context = SerializationContext::create();
$groups = [];
// Assuming $request contains the "fields" param
$fields = $request->query->get('fields');
// Do this kind of check for all fields in $fields
if (in_array('foos', $fields)) {
$groups[] = 'withFooAssociation';
}
// Tell the serializer to use the groups previously defined
$context->setGroups($groups);
// Serialize the data
$data = $serializer->serialize($posts, 'json', $context);
// Create the view
$view = View::create()->setData($data);
return $this->handleView($view);
I hope that I correctly understood your question and that this will be sufficient for help you.

How do I search by properties of entity which do not have #Column anotation in Doctrine2?

/**
* #ORM\Entity
*/
class Order extends BaseEntity
{
// this is trait for #Id
use Identifier;
/**
* #ORM\Column(type="integer")
*/
protected $costPerUnit;
/**
* #ORM\Column(type="integer")
*/
protected $numberOfUnits;
// i want to search by this property
protected $totalCost;
public function getTotalCost()
{
return $this->numberOfUnits * $this->costPerUnit;
}
}
I have an entity like this and I'd like to be able to do for example
$orderRepository->findOneByTotalCost('999')
$orderRepository->findBy(['totalCost' => '400']);
Is this possible in Doctrine2? Or would I go about it differently?
Like I said in my comments, it's likely you're wrestling with an issue that shouldn't have occurred in the first place. Still, having a SUM value mapped to a property is possible using doctrine, in a variety of ways: Check the aggregate field docs to find out which would solve your problem best.
To my eyes, is that you're using entities as more than what they really are: Entities represent records in a database, Doctrine is a DBAL. Searching data using entities (or repositories) is querying the database. You could solve the problem by adding custom methods to your entity manager or a custom repository class that'll query all of the data required to compute the totalCost value for all entities, and return only those you need. Alternatively, use the connection from your DBAL to query for the id's you're after (for example), then use those values to get to the actual entities. Or, like I said before: use aggregate fields.
The problems you have with the findOneByTotalCost and findBy examples you show is that the first requires you to write a method Called findOneByTotalCost yourself. The problem with your use of findBy is simply that your argument is malformed: the array should be associative: use the mapped column names as keys, and the values are what you want to query for:
$repo->findBy(
['totalCost' => 400]
);
is what you're looking for, not ['totalCost', 400]. As for the entity itself, you'll need to add an annotation:
Yes it is, judging by your use of #ORM\Entity annotations in the doc-blocks, this ought to do it:
/**
* #ORM\Column(type="string", length=255)
*/
protected $regioun = 'Spain';
The update the table, and you'll be able to:
$entities = $repo->findBy(
['region' => 'Spain']
);
Don't forget that this code represents a table in a DB: you can search on any of the fields, but use indexes, which you can do by adding annotations at the top of your class definition:
/**
* #ORM\Table(name="tblname", indexes={
* #ORM\Index(name="region", columns={"region"})
* })
*/
class Foo
{}
As ever: in DB's, indexes matter
You should write a method findOneByTotalCost on your entity repository, something like:
public function findOneByTotalCost ($queryParams){
$query = 'select o
from <yourEntity> o
where o.numberOfUnits * o.costPerUnit = :myParam';
$dql = $this->getEntityManager()->createQuery($query);
$dql->setParameter('myParam', $queryParams);
return $dql ->execute();
}
Them, $orderRepository->findOneByTotalCost('999') should work.

Polymorphic relationships with Doctrine2

How do I create traditional polymorphic relationships with Doctrine 2?
I have read a lot of answers that suggest using Single Table Inheritance but I can't see how this would help in my situation. Here's what I'm trying to do:
I have some utility entities, like an Address, an Email and a PhoneNumber.
I have some 'contactable' entities, like a Customer, Employer, Business. Each of these should contain a OneToMany relationship with the above utility entities.
Ideally, I'd like to create an abstract base class called 'ContactableEntity' that contains these relationships, but I know it is not possible to put OneToMany relationships in mapped superclasses with doctrine-- that's fine.
However, I am still at a loss at how I can relate these without massive redundancy in code. Do I make Address an STI type, with a 'CustomerAddress' subclass that contains the relationship directly to a Customer? Is there no way to reduce the amount of repetition?
Why not just make your base ContactableEntity concrete?
EDIT:
Just did a few experiments in a project I've done that uses CTI. I don't see any reason that the same strategy wouldn't work with STI.
Basically, I have something like:
/**
* Base class for orders. Actual orders are some subclass of order.
*
* #Entity
* #Table(name="OOrder")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"CAOrder" = "CAOrder", "AmazonOrder" = "AmazonOrder"})
*/
abstract class Order {
/**
* CSRs can add notes to orders of any type
* #OneToMany(targetEntity = "OrderNote", mappedBy = "order", cascade={"all"})
* #OrderBy({"created" = "ASC"})
*/
protected $notes;
// ...
}
/**
* #Entity
*/
class AmazonOrder extends Order {
/**
* #Column(type="string", length="20")
*/
protected $amazonOrderId;
// ...
}
/**
* #Entity
*/
class OrderNote {
// ...
/**
* #ManyToOne(targetEntity="Order", inversedBy="notes")
*/
protected $order;
// ...
}
And it seems to work exactly as expected. I can get an OrderNote, and it's $order property will contain some subclass of Order.
Is there some restriction on using STI that makes this not possible for you? If so, I'd suggest moving to CTI. But I can't imagine why this wouldn't work with STI.
If the contactable entity shall be abstract (#MappedSuperclass) you'll need to use the ResolveTargetEntityListener provided by Doctrine 2.2+.
It basically allows you to define a relationship by specifying an interface instead of a concrete entity. (Maybe you want to define/inherit several interfaces as you speak of multiple "contactables"). For instance you then can implement the interface in your abstract class or concrete class. Finally you'll need to define/associate the concrete class (entity) to the related interface within the config.yml
An example can be found in the Symfony docs: http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html

Categories