I have decided to create a 'MappedSuperclass' which will be extended by all other entities to easily share between them two common fields, it looks like this:
/**
* #ORM\MappedSuperclass
*/
abstract class EntityBase {
/**
* #ORM\Column(type="datetime")
*/
protected $created_at;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $updated_at;
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function updateTimestamps() {
$this->updated_at = new \DateTime('now');
if(is_null($this->created_at)) {
$this->created_at = new \DateTime('now');
}
}
}
My problem is that, after using generate:entities command, script copies to all child classes both fields (as private) as well as updateTimestamps function (which is empty).
Is there any possibility to prevent this behavior? Now i have only 5 entities so its not a big deal to manually delete unneeded code, but it may be pain when project grows up to 20 or more entities.
Or maybe it not the right way to achieve my needs?
If you define a class as abstract it will be interpreted as 'to be implemented'. Thats why you get dummy functions to implement the feature in the actual class. You should just extent a regular class to inherit the fields and the function to update the timestamps.
If you inherit entity classes, you must set the properties of the parent class as private. Child classes may only access them via the getter/setter. I had the same problem a while ago, making properties private works like a charm.
Related
I like to either type-hint or starting in PHP7 actually show the return value of a getter function. But with One-To-Many relationships in Doctrine / Symfony, I'm still stuck and am not sure what to add to the #var tag.
[...]
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $features;
/**
* What goes into var here?
*
* One Product has Many Features.
* #ORM\OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
public function __construct()
{
$this->features = new ArrayCollection();
$this->name = 'New Product Name';
}
/**
* #return Collection
*/
public function getFeatures(): Collection
{
return $this->features;
}
[...]
Currently I’m using #var Collection and can then use the Collection functions. But what would be the »proper« thing to return? Is it indeed Collection? Or is it ArrayCollection? I’m tempted to use Features[] in order to use the functions of Feature, if I need to (instead of typehinting), but it doesn’t feel right.
What would be the »cleanest« / stable way to do this?
If you want to keep the docblock I would use the union type | to both specify the Collection and the list of values it contains like:
/**
* #var Collection|Feature[]
*/
With this your IDE should both find the methods from Collection as well as the Feature-type hints when you get a single object from the collection, e.g. in a foreach.
As to the question of ArrayCollection vs. Collection, it is usually recommended to type hint for the interface (Collection in this case). ArrayCollection offers a few more methods, but unless you really need them I would not bother with the type hint just to get them.
What I tend to do in projects is keep the Collection inside the entity and only pass out an array in the getter like this:
public function getFeatures(): array
{
return $this->features->toArray();
}
public function setFeatures(array $features): void
{
$this->features = new ArrayCollection($features);
}
Be careful, the void return type is not supported in PHP 7.0 yet. The benefit of returning an array is that in your code you don't have to worry about what kind of Collection Doctrine uses. That class is mainly used to maintain reference between objects inside Doctrine's Unit Of Work, so it should not really be part of your concern.
I need to cache some info about a user who is logged in (such as security groups, name, username, etc.)
Currently I have a separate class to achieve just this, lets call it CurrentUserHelper. Given a user object, it will cache the appropriate data and save store info in the $_SESSION variable.
The issue is I'm finding a bunch of classes relying just on CurrentUserHelper because they only need a couple of common fields. In fact, most of the functions have the same name as my User class. There's a couple of functions in CurrentUserHelper, such as getSecurityGroupsNames(), that contains a cache of all security group names, but there is no reason this function name could not be in the user class also.
Instead, should I create a CachedUser class and pass this around? This class can extend User, but then I can override getName(), getSecurityGroups(), etc, and returned the cached data, and not preform db requests to get the data.
The downside of passing around a CachedUser object is that this kind of hides the fact the data isn't really up to date if a constructor/function is accepting a type User. I also need to find way to handle merging the entity with Doctrine 2, and making sure entities associating themselves with a CachedUser won't break. If I decide to cache some temporary data (such as # of page views since logged in), this property shouldn't be part of the User class, it's more about the current user's session.
If I continue using the CurrentUserHelper class, maybe I should create an interface and have both CurrentUserHelper and User for the common functionality the two classes would share?
Preface: Extension isn't the best way for these sorts of things.. I'm sure you've heard composition over inheritance shouted at you over and over again. In fact, you can even gain inheritance without using extends!
This sounds like a good use-case for the decorator pattern. Basically, you wrap your existing object with another one that implements the same interface, so it has the same methods as the inner object, but this object's method adds the extra stuff around the method call to the inner object, like caching, for example.
Imagine you have a UserRepository:
/**
* Represents an object capable of providing a user from persistence
*/
interface UserProvider
{
/**
* #param int $id
*
* #return User
*/
public function findUser($id);
}
/**
* Our object that already does the stuff we want to do, without caching
*/
class UserRepository implements UserProvider
{
/**
* #var DbAbstraction
*/
protected $db;
/**
* #var UserMapper
*/
protected $mapper;
/**
* #param DbAbstraction $db
* #param UserMapper $mapper
*/
public function __construct(DbAbstraction $db, UserMapper $mapper)
{
$this->db = $db;
$this->mapper = $mapper;
}
/**
* {#inheritDoc}
*/
public function findUser($id)
{
$data = $this->db->find(['id' => $id]);
/** Data mapper pattern - map data to the User object **/
$user = $this->mapper->map($data);
return $user;
}
}
The above is a really simple example. It'll retrieve the user data from it's persistence (a database, filesystem, whatever), map that data to an actual User object, then return it.
Great, so now we want to add caching. Where should we do this, within the UserRepository? No, because it's not the responsibility of the repository to perform caching, even if you Dependency Inject a caching object (or even a logger!).. this is where the decorator pattern would come in useful.
/**
* Decorates the UserRepository to provide caching
*/
class CachedUserRepository implements UserProvider
{
/**
* #var UserRepository
*/
protected $repo;
/**
* #var CachingImpl
*/
protected $cache;
/**
* #param UserRepository $repo
*/
public function __construct(UserRepository $repo, CachingImpl $cache)
{
$this->repo = $repo;
$this->cache = $cache;
}
/**
* {#inheritDoc}
*
* So, because this class also implements UserProvider, it has to
* have the same method in the interface. We FORWARD the call to
* the ACTUAL user provider, but put caching AROUND it...
*/
public function findUser($id)
{
/** Has this been cached? **/
if ($this->cache->hasKey($id))
{
/**
* Returns your user object, or maps data or whatever
*/
return $this->cache->get($id);
}
/** Hasn't been cached, forward the call to our user repository **/
$user = $this->repo->findUser($id);
/** Store the user in the cache for next time **/
$this->cache->add($id, $user);
return $user;
}
}
Very simply, we've wrapped the original object and method call with some additional caching functionality. The cool thing about this is that, not only can you switch out this cached version for the non-cached version at any time (because they both rely on the same interface), but you can remove the caching completely as a result, just by changing how you instantiate this object (you could take a look at the factory pattern for that, and even decide which factory (abstract factory?) depending on a configuration variable).
I have four types of products which I would like to map to a Doctrine ORM structure on a MySQL RDBMS. The products are PrepaidProduct, PostpaidProduct, MobilePrepaidProduct, MobilePostpaidProduct with the following structure:
abstract class Product {
/**
* #ORM\Column(type="integer")
*/
private $price;
...
}
class PrepaidProduct extends Product {
/**
* #ORM\Column(type="integer")
*/
private $credit;
/**
* #ORM\OneToMany(targetEntity="PrepaidDiscount")
*/
private $prepaidDiscounts;
}
class PostpaidProduct extends Product {
/**
* #ORM\OneToMany(targetEntity="BundleMapping")
*/
private $bundleMappings;
}
class MobilePrepaidProduct extends PrepaidProduct {
/**
* #ORM\ManyToOne(targetEntity="Device")
*/
private $device;
}
class MobilePostpaidProduct extends PostpaidProduct {
/**
* #ORM\ManyToOne(targetEntity="Device")
*/
private $device;
}
The main idea is that I would like to use a service (factory) that will use the basic class structure of the PostpaidProduct class to create a structure of the corresponding bundle mapping, so I think I would need this as a mapped super class.
In my opinion the way to go would be to have two separate tables, one for PostpaidProduct and one for PrepaidProduct, and have a Single Table Inheritance on those for MobilePostpaidProduct/PostpaidProduct and MobilePrepaidProduct/PrepaidProduct.
What do you guys think? Any thoughts on best way to model this?
If you are using a RDBMS layer [MySQL, Postgre] as I think, your proposal is the best choice in my opinion.
If subclasses have *little more attributes, possibly relationships most of all, you are actually promoting composition and your data representation won't be sparse [i.e. you won't have many empty fields in the main table].
On the other hand, if you want to stick with composition only [which is more advisable in most cases] - is $device the only additional relationship in Mobile classes? If so, you could write a findAllMobileProducts method in your repository class that would return every product where device is not null, and so on.
I currently have a model structure as follows:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="related_type", type="string")
* #ORM\DiscriminatorMap({"type_one"="TypeOne", "type_two"="TypeTwo"})
*/
abstract class BaseEntity {
... (all the usual stuff, IDs, etc)
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="baseEntity")
*/
private $comments;
}
/**
* #ORM\Entity
*/
class TypeOne extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeTwo extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class Comment {
... (all the usual stuff, IDs, etc)
/**
* #ORM\ManyToOne(targetEntity="BaseEntity", inversedBy="comments")
*/
private $baseEntity;
}
The idea here is to be able to tie a comment to any of the other tables. This all seems to be working ok so far (granted, I'm still exploring design options so there could be a better way to do this...), but the one thing I've noticed is that the subclasses have some common fields that I'd like to move into a common parent class. I don't want to move them up into the BaseEntity as there will be other objects that are children of BaseEntity, but that won't have those fields.
I've considered creating a MappedSuperclass parent class in the middle, like so:
/**
* #ORM\MappedSuperclass
*/
abstract class Common extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeOne extends Common {}
/**
* #ORM\Entity
*/
class TypeTwo extends Common {}
I figured this would work, but the doctrine database schema generator is complaining that I can't have a OneToMany mapping on a MappedSuperclass. I didn't expect this to be a problem as the OneToMany mapping is still between the root BaseEntity and the Comment table. Is there a different structure I should be using, or other way to make these fields common without adding them on the BaseEntity?
From the Docs:
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that is
common to multiple entity classes.
That said, how can you associate one entity with one that is not?
More from the docs:
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
assocations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For
further support of inheritance, the single or joined table inheritance
features have to be used.
Source: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
Update
Because your MappedSuperClass extends BaseEntity it also inherits the BaseEntity's associations, as if it were its own. So you effectively DO have a OneToMany on a MappedSuperClass.
To get around it, well, you'd need to modify/extend doctrine to work the way you want.
As far as native functionality goes you have two options:
Class Table Inheritance
You Common class and the resulting DB representation would have the common fields and child classes will now only have the fields specific to themselves. Unfortunately this may be a misrepresentation of your data if you are simply trying to group common fields for the sake of grouping them.
Make Common an Entity
It appears that all a Mapped Super Class is is an Entity that isn't represented in the DB. So, make common a Entity instead. The downside is that you'll end up with a DB table, but you could just delete that.
I recommend that you take a second look at your data and ensure that you are only grouping fields if they are common in both name and purpose. For example, a ComputerBox, a ShoeBox, a Man, and a Woman may all have the "height" property but in that case I wouldn't suggest have a Common class with a "height" property that they all inherit from. Instead, I would have a Box with fields common to ComputerBox and ShoeBox and I'd have a Person with fields common to Man and Woman. In that situation Class Table Inheritance or single table if you prefer would work perfectly.
If your data follows that example go with Single Table or Class Table Inheritance. If not, I might advise not grouping the fields.
I have this situation:
Abstract Class:
abstract class AbstractBase
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var integer
*/
protected $id;
/**
* #ORM\Column(type="datetime", name="updated_at")
* #var \DateTime $updatedAt
*/
protected $updatedAt;
/**
* #ORM\PreUpdate
*/
public function setUpdatedAt()
{
die('THIS POINT IS NEVER REACHED');
$this->updatedAt = new \DateTime();
}
}
Concrete Class:
/**
* #ORM\Entity(repositoryClass="Entity\Repository\UserRepository")
* #ORM\Table(name="users")
* #ORM\HasLifecycleCallbacks
*/
class User extends AbstractBase
{
// some fields, relations and setters/getters defined here, these all work as expected.
}
Then i call it in my controller like this:
$user = $this->em->find('Entity\User', 1);
// i call some setters here like $user->setName('asd');
$this->em->flush();
die('end');
Everything works as expected, so the id field from the abstract class gets created for the User entity, i can access it etc.
The problem is, that the line "die('THIS POINT IS NEVER REACHED')" is never reached. (Note the #ORM\PreUpdate) This means that lifecycleCallbacks are not called on
inherited objects. Is this a bug, or is there a reason for this?
Your abstract base class has to be anotated as Mapped Superclasses and include the HasLifecycleCallbacks-Annotation.
Further Information: Inheritance Mapping in the Doctrine Documentation.
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks
*/
abstract class AbstractBase
{
[...]
/**
* #ORM\PreUpdate
*/
public function setUpdatedAt()
{
$this->updatedAt = new \DateTime();
}
}
/**
* #ORM\Entity(repositoryClass="Entity\Repository\UserRepository")
* #ORM\Table(name="users")
*/
class User extends AbstractBase
{
// some fields, relations and setters/getters defined here, these all work as expected.
}
You have to annotate the base class with #ORM\HasLifecycleCallbacks, and the function with #ORM\preUpdate
You have a typo (PreUpdate should be preUpdate), also preUpdate isn't called on creation (only on update). So if you want it also be triggered on creation, you should add #ORM\prePersist.
While the accepted reply is correct for the general case, in this particular case (timestamp) you actually want to use the doctrine extension Timestampable as explained for example here Lifecycle Callback Issue When Extending FOSUserBundle User Entity
It is important that the MappedSuperclass with HasLifecycleCallbacks is in the same namespace or directory as their child Entities.
I had problems with life cycle callbacks when the MappedSuperclass was in one directory (Model) while the Entities were in another (Entity). Putting the MappedSuperclass in the same directory as the Entities (Entity) solved the issue.
Maybe i'm wrong but I don't think preUpdate isn't triggered when you persist an entity. You should have a #prePersist.
http://www.doctrine-project.org/docs/orm/2.0/en/reference/events.html
But still then i'm not sure this is going to work but you could try that. Else a workaround would be to overwrite the setUpdatedAt function and just call his parent one but that's a bit ugly.
Hope the #prePersist helps for you.
Perhaps you could this issue report as a reference how to setup your annotations? The testcase seems to be valid and matches your use case.
I think you have to annotate the base class with #ORM\HasLifecycleCallbacks
docs