Symfony2 which doctrine relationship for blog between posts and comments - symfony

I am building a blog with symfony2 and I am wondering which doctrine relation mapping should be used for the comments. The thing is that I would like to use the comments in different parts, like commenting on pictures (which are not posts) or commenting on comments. So I need my comment entity to be independent of the post.
I wanted to try the OneToMany unidirectionnal but it forces a unique key on the post which is not great.
Thanks for your help

Simple way
The simplest way would be to create a OneToMany relationship for each linked entity. It's quite ugly and maybe is not effective when searching entities, but it works.
The mapping would be similar to this:
class Comment
{
/**
* #ORM\ManyToOne(targetEntity="Post")
* #ORM\JoinColumn(nullable=true)
**/
protected $post;
/**
* #ORM\ManyToOne(targetEntity="Picture")
* #ORM\JoinColumn(nullable=true)
**/
protected $picture;
/**
* #ORM\ManyToOne(targetEntity="Comment")
* #ORM\JoinColumn(nullable=true)
**/
protected $comment;
}
You'll have to handle security by yourself to make sure the comment has at least ONE linked element, and searching might be harder, but it's a basic way to do it.
Complex way
The most effective way to do it (but maybe the most complex) would be to create a "discriminant" property and a "element-to-be-commented" property, coupled with a Custom Doctrine hydrator to retrieve all objects at once, but each one being the correct entity.
The "element-to-be-commented" property would then be either a Comment, Picture or Post, and the discriminant would be here to tell which class is linked.
In SQL terms, it means no foreign key between tables, and that the element_id is dependent of the discriminant.

You may want to take a look at Single Table Inheritance. Disclaimer: I don't have direct experience with it, but it's been suggested often as an answer to similar questions.

Related

Getting a list of all properties in a group in API Platform

Using API Platform 1.2.
I've simplified my setup for the purpose of this question. Please excuse lack of following standards.
I have 2 entities: Book and Category. Book properties:
/**
* #Groups({book:read})
*/
$name;
/**
* #Groups({book:read})
*/
$summary;
/**
* #Groups({book:read})
*/
$category;
The $category property is mapped to a Category entity. Category has a $categoryName property. This is also attached to the book:read group.
In the GET API call this the output contains all the Book properties plus the $categoryName property. This is great 👍
What I want to know is: Using API Platform, how would I go about getting all properties for a serialization group such as above?
I have found that I could tap into the \ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface to get the property info but this requires knowing what classes and properties to check. Looping through all entities with a metadata lookup, then looping through all properties, performing another metadata lookup seems wasteful and slow.
Is there a better way to acheieve this? I basically want the same output as what the API produces by feeding am entity name and group name.

Polymorphic attachments using a subset of the polymorphic relationship in doctrine 2

I need some help with doctrine 2 that uses "polymorphic associations". Let me clarify myself. Entitys can support file attachments using a subset of the polymorphic relationship. the File entity is used to safekeep this relationship where reference to the files are stored as records in the files table and have a polymorphic relation to the parent model. I want to create the same functionality as https://octobercms.com/docs/database/attachments
But do not know how to make the relationship, and how, for example, put the attachment_type dynamic like attachment_id;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToOne(targetEntity="App\Domain\FileAttachment\Entity\FileAttachment", attachment_type="news_thumbnail")
*/
private $thumbnail;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToOne(targetEntity="App\Domain\FileAttachment\Entity\FileAttachment", attachment_type="news_image")
*/
private $image;
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Domain\FileAttachment\Entity\FileAttachment", attachment_type="news_files")
*/
private $files;
An example of the files table.
I have some experience in trying to make polymorphism work (including polymorphic files) in symfony and by this time I think I can share a few of my insights with you in hopes that they would provide you with some useful information about this subject.
Firstly, I would suggest reading up on inheritance mapping in doctrine link. With doctrine inheritance mapping you would simply create one main File class and then make every other attachment extend it. Then, say you want to add a picture attachment to the user. You would simply create a oneToOne relationship between the user and the main File class. If the attachment you persist would be an instance of one of the attachment classes, Doctrine is smart enough to return you an object of that class, not the main File class.
So to answer you question, I will give you a specific example. Case:
ImageAttachment extends FileAttachment
User has a property called photo
Property photo is a OneToOne relationship to the FileAttachment entity
Code:
$image = new ImageAttachment();
$user->setPhoto($image);
$em->persist($user);
$em->flush();
Result:
Now in the database in the User table, in a column called something like photo_id the referenced ID would be the one in the FileAttachment table. When you would do $user->getPhoto(); it would return an object of class ImageAttachment since doctrine knows that you have persisted an ImageAttachment, not just a FileAttachment.
When it comes to collections, things would also be pretty simple. In this case, you would probably need to create an ManyToMany relationship between the file and the entity that you want to relate to the file. Say that a user can have many different types of attachments saved in the database. If you want to use this filesystem application wide it would probably make no sense for a file to know about the user it belongs to, because soon file would have to hold information on all different types of the relationships and that is just not a smart architecture choice if you want to have any type of modular system in place. Thats why my suggestion is to use ManyToMany relationships between some entity and the attachments. This way only user would know about the files in the database and filesystem would be agnostic and decoupled.
A third important point to be made when talking about polymorphism in doctrine is symfony support for this feature. Generally polymorphism is considered to be somewhat of a bad practice in certain cases, and especially in data persistence does not have much support in the community. So an important thing to consider is that symfony CollectionType HAS NO SUPPORT FOR POLYMORPHISM what so ever. Basically you will have to write your own Type if you were planning on using polymorphic form collections. But if you don't mind using a bit of ajax, this is not really a problem, you can simply avoid using SF forms for this purpose alone.

Keep my symfony2 bundles decoupled

I have two different bundle:
first bundle, OrderBundle , has core logic and functionalities and contains the Order entity.
second bundle, CustomerBundle depends on OrderBundle and contains the Customer entity.
I need to create a oneToMany relation between Customers and Orders (obviously one Customer do many Orders) but I need to keep the first bundle OrderBundle decoupled from the second, because OrderBundle is intended to be reused for other stuff.
I think the correct way could be something like this http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html but I can't figure out how to have a concrete implementation.
How to implement the relation between Order and Customer, if I can't specifically use Customer like targetEntity in the ManyToOne doctrine mapping?
Many thanks, in advance.
UPDATE
I write down the involved code, for better explanation.
\\ Order\Bundle\Entity\Order.php
class Order {
/**
* #ORM\ManyToOne(targetEntity="Order\Bundle\Model\OrderSubjectInterface", inversedBy="orders")
* #var SourceSubjectInterface
*/
protected $subject; // How to define getter ans setter for $subject ? Do I
have to use php app/console doctrine:generate:entities command?
...
\\ Customer\Bundle\Entity\Customer.php
use Order\Bundle\Model\OrderSubjectInterface;
class Customer implements OrderSubjectInterface{
/**
* #ORM\OneToMany(targetEntity="Order\Bundle\Entity\Order", mappedBy="subject")
*/
private $orders;
How to define getters, setters and the interface?
Yes, this is the right way.
As shown in the documentation you mentioned, you can specify something like OrderSubjectInterface as a targetEntity in the ManyToOne mapping.
This way, you know that your Order is related to subjects. Those subjects are, in your case, the Customers, as defined in app/config/config.yml.
It's quite hard to entirely decouple the bundles if you have entity definitions all over the place. The problem is, the Doctrine doesn't allow by default for a related entity to be missing. Let's start from the beginning.
If you want to decouple only Order entity, what you have to do is create interfaces for all its related entities (they need to implement them) and then use the ResolveTargetEntity. So instead of referencing the full entities you reference the interfaces (when you define the relations in your entities). Lastly, you can then set which interface maps to which entity in the configuration.
What this does is it allows you to pick up the Order bundle and put it in an environment which has entirely different entity structure. The important thing is that the entities related to Order must not be missing in the new environment (They can be entirely different, but they must implement the same interfaces as the originals). Then you change the settings so that the interfaces point to the entities from the new environment.
So as you see, this is not "entirely decoupled" code. I can't help you much more without some details. Is the relation bidirectional or unidirectional? What do you exactly mean by "reused for other stuff", can you be more detailed?

Multilingual entities with inheritance?

I need some multilingual entities in our application and I want to know what are the best practice for that.
Currently it solved with an 1:n relation, because there general information and language specific fields.
Is it possible and a good idea to use entity inheritance for that? So I can change always the general part on an object?
Example (pseudo code)
class Product {
public $id;
public $status;
}
class ProductDetails extends Product {
public $language;
public $name;
public $description;
}
I hope my explanation helps a bit to make clear what I need.
I think it's not a good idae, because inheritance is meant to be static. As soon as you want a new locale, you will have to add a new inheritance element in your map. As a contrary, a 1:n relation is good, as it grows without any modification. Moreover, adding a field with a single table inheritance means ading a field for every entity in this table.
Better to use oneToMany :)

create a new entity based on the creation of another (postPersist) Doctrine2

I'm trying to create a new entity based on the creation of another. Example: I have a relationship
Sale <-- 1:M --> Payment
Now, when I persist a Sale, then you must create an initial Payment but i dont know exactly how do it.
i've try:
usage #ORM\HasLifecycleCallbacks(), #ORM\prePersist or #ORM\postPersist, but these methods does not get arguments and i can't persist the Entity Payment. I've even tried to relate Sale with Payment (in prePersist method $payment->setSale($this)) hoping EntityManager to persist Payment for me. info from here
I tried to create a listener (guided from here), but it just does not work, at no time the listener runs
Do it in my SaleController::createAction(), this way is obviously simple and it works, but this is nothing elegant and also goes against the good design of my application, this operation is part of the business logic and repeated in various parts
Out of the 3 solutions you listed 3 is still the least wrong in my opinion. It's simple, not overly complicated and easy to refactor later.
But if you're looking for a clean solution, I think what you need is a form handler or a similar service.
Take a look at FOSUserBundle one.
Basically you will create a PaymentManager class & after handling all the Sales form stuff, pass all the gathered info to PaymentManager and let it handle all the create/persist logic of Payment entity.
I'd suggest that a PaymentManager as suggested by #Inori is the best way to go, it is DRY and also a central point where entities are created. It allows you to marshal all the user input in the controller and then pass it onto the manager to build up the Sale object properly.
If you DO wish to go with the 1st option and use a lifecycle callback I assume you are getting an exception that says an unmanaged entity was found on another entity - or something to that effect. To get around this you can cascade persist on your mapping which means that you don't need to call persist for the Payment:
/**
* One-to-Many via Join Table (aka One-To-Many Unidirectional).
* #ORM\ManyToMany(targetEntity="Payment", cascade={"persist"})
* #ORM\JoinTable(
* inverseJoinColumns={
* #ORM\JoinColumn(unique=true, onDelete="CASCADE")
* }
* )
*/
You can read more about the One-To-One, Unidirectional if it confuses you.
Also you should read about cascade persist Transitive persistence / Cascade Operations.

Resources