Maybe there are already answers regarding this topic but I cannot find any information. If there are answers already, just post any links.
My question is:
I work with Symfony 2.5 and doctrine 2
A common problem I have is that I have an entity that I want to persist to different tables in the database. For this I usually create a superclass and extend it in two concrete entities that I persist to the database.
Now I have two entities deriving from the same base class. Is it possible to convert from one to another. Of course I have to persist them to the database separately.
One example. I have two classes: "quote" and "order" which are basically the same and extend the superclass "project"
/**
* #ORM\MappedSuperclass
*/
abstract class project {
/**
* #ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="Bundle\Customer")
*/
private $customer;
/**
* #ORM\ManyToOne(targetEntity="Bundle\CustomerContact")
*/
private $customercontact;
... (getters/setters etc.)
}
class quote extends project { ... }
class order extends project { ... }
Now once a quote is confirmed I want to "convert" it to an order and keep all the base data (stored in the "project" part). Also I want to keep the quote itself so I kind of want to create a new order from the quote instead of using the "project" directly and just set a status there.
Of course I could write a constructor or something but I am searching for a way to clone that part of an object.
Maybe there is an alternative way where the class is not extended but the "project"-part is used within the class itself. Not sure but it seems to me like a lot of people could have run into such a problem already and there might be a smart solution for this kind of situation.
I'm happy with any links to docs too if applicable.
Related
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.
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
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.
Suppose I'm writing a blog app: My Posts Entity should have a createddate, modifieddate, name, and slug property (the slug would be generated when I call setName() for the Entity). I want to reuse this code, but I also want to reuse it on-demand. I may, for instance, have a Log Entity which wants to use createddate, but not modifieddate or slug functionality.
Class inheritance, whether via traits (mixins) or abstract classes, seems to be an insufficient, or at least improper, approach to reusing this functionality, as it doesn't pass the is-a test. After all, the Entity has-a createddate, but isn't a createddate itself.. so we should use Composition rather than Inheritance, right?. However, observer doesn't seem to work here, since while I want to use this functionality on-demand, Doctrine's use of annotation and object properties seem to make horizontal injection difficult (and expensive?) without use of Reflection.
To show some code and simplify the question a bit: can I at once inject the following definition and functionality into an Entity without breaking DRY or good OOP (proper use of composition / inheritance) practice?
/**
* #var DateTime $createddate
*
* #ORM\Column(type="datetime")
*/
private $createddate;
/**
* #ORM\PrePersist
*/
public function createddatePrePersist() {
$this->createddate = new \DateTime('now');
}
My personal opinion is that you're trying to find the nail for your hammer. You don't have to use composition, inheritance, traits, or any OOP pattern for every entity.
I tend to consider that the creationDate is an inherent property of my entity, and any entity that requires a creation date has this property. One may argue that's repeating myself, but in my opinion that makes the whole entity much easier to read.
Oh, and if I may, I wouldn't use an ORM-specific method, which is not part of my domain model, to initialise the creation date. This would be done in my constructor:
/**
* #var DateTime $createddate
*
* #ORM\Column(type="datetime")
*/
private $createddate;
public function __construct()
{
$this->createddate = new \DateTime();
// ... and other business rules that need to be enforced in the constructor
}
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