Symfony2 & PHP Traits with entities relation embedded - php

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.

Related

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.

Symfony: Convert two entities that extend the same class

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.

Doctrine association mapping between entities in different bundle or relate entities from two different bundles?

Region:
namespace Acme\RegionBundle\Entity;
class Region
{
private $id;
/**
* #ORM\OneToMany(targetEntity="User")
* #ORM\JoinColumn(name="region_id", referencedColumnName="id")
*/
private $users;
}
User:
namespace Acme\UserBundle\Entity;
class User
{
private $id;
private $region_id;
}
How to relate entities from different bundle without mentioning fully specified entity path i.e. hard coding dependency.
Is there any better approach ?
Can Resolve Target Entity Listener be a solution. I couldn't understand how it could be applied here ?
The resolve target entity listener allows you to re-define associations at runtime. It allows you basically to map something like following:
#ORM\OneToMany(targetEntity="My\Namespace\UserInterface")
As you can see, mapping an interface as target entity does not make much sense. It becomes really useful when you tell that every My\Namespace\UserInterface has to be replaced with a Other\Namespace\User reference.

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