I started using symfony not long ago and at the moment I'm struggling with this problem:
I decided to have "who" information at entity level so I have defined these additional 4 prameters for every entity:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
*/
private $createdBy;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
private $updatedAt;
My problem is now where and how I should populate createdBy and updatedBy. ATM I do that in my controller before persisting to the database. Thou I encountered a problem when a entity is a property of another entity and lets say I have an entity called Post that has a property images of type Document the entities Post and Document both have "who" information on them and images property inside Post is defined as follows:
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Nisand\DocumentsBundle\Entity\Document", cascade={"persist"})
* #ORM\JoinTable(name="blog_documents",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="document_id", referencedColumnName="id")}
* )
*/
private $images;
For Post suppose I set createdBy in the controller before persisting but on Document how should that work cause that will be persisted by the cascade rule?
How do you guys handle in your applications the "who" columns?
Try this bundle: StofDoctrineExtensionsBundle and use Blameable extension.
You will need set current user with BlameableListener. And it will cover your use case.
Documentation for Blameable is here: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
Related
I have this entity
/**
* #ApiResource()
* #ORM\Entity(repositoryClass="App\Repository\FeedRepository")
*/
class Feed implements AuthoredEntityInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="myFeeds")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=2083, unique=true)
*/
private $url;
// various getters and setters
}
using the AuthoredEntityInterface I made I can automatically set the user to the logged user.
I'd need to know how to set the collectionOperations so when I am logged in as the user with id = 1, when I call /api/feeds I will only retrieve items with user = 1. If this is possible I would like to do this with an annotation, otherwise any other method is ok.
thanks.
If it is just for connected user, what you need is a current user extension (doctrine extension). Else, you need to create a "subresource' link.
Link to Extension, and to Subresource.
Enjoy :) (and thank you timisorean for the review)
In a previous question i wanted to know how to prevent a user to edit a form if another user was already using it.
Since i'm using SF 2.8, i can't use the lock component (> SF3.4) so i was thinking about doing it manually, with an entity managing the locks.
for my entity, i need :
user_id (the user that edit the form, create the lock)
entity_id (the id of the edited entity)
entity_class (FQCN of the entityType)
createdAt (date of the lock)
moreover, i need a UniqueEntity constraint on (user_id, entity_id and entity_class)
This is where i have a problem of mapping : the entity (id) can be of different type (i have Profession, Module, Institution, User...)
So from a Doctrine point of view, i don"t see how i can do it.
maybe i can use the entity id, but loosing the very power of docrine/symfony relationships.
/**
* Lockit.
*
* #ORM\Table(name="lockit")
*
* #UniqueEntity(
* fields={"entityClass", "entityId", "user"}
* )
*/
class Lockit
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* FQCN of the entity associated with the form to be locked.
*
* #var string
* #ORM\Column(name="entity_class", type="string")
*/
private $entityClass;
/**
* Entity id associated with the form to be locked.
* #ORM\Column(name="entity_id", type="integer")
*/
private $entityId;
/**
* #var \Simusante\SimustoryBundle\Entity\User
*
* #ORM\ManyToOne(targetEntity="Simusante\SimustoryUserBundle\Entity\User")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="userId", referencedColumnName="id", nullable=true)
* })
*/
private $user;
/**
* Date of the lock creation.
*
* #var \DateTime
* #ORM\Column(name="createdAt", type="datetime", nullable=true)
* #Assert\Date()
*/
private $createdAt;
Another solution would be to create as many lockEntities as i can lock entity with.
i would create a base Lock, and then a ProfessionLock, a InstitutionLock... where i could use the "correct" mapping.
/**
* #ORM\ManyToOne(targetEntity="Institution")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="institutionId", referencedColumnName="id", nullable=true)
* })
*/
private $user;
it would work, but this doesn't feel as "optimized" as it could.
maybe there's another way to to it, where i don't have to create as many entities as i have form type to lock.
Thank you in advance
This is where i have a problem of mapping : the entity (id) can be of different type (i have Profession, Module, Institution, User...)
As I can see, just mapping the entityId field as a text field instead of integer should solve your issue.
Your UniqueEntity constraint would still be relevant, and you would still be able to recover any locked entity Lockit instance via a simple entity repository method or whatever query method you'd like.
I have many ads entities (MotorAds, RealestateAds, ElectronicsAds, ...) that share some attributes like title and description. In order to avoid redefining these attributes for each Ads entity, one can use the mapped superclass methods as follows:
<?php
/** #MappedSuperclass */
class MappedSuperclassAds{
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, nullable=false)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="description", type="text", nullable=false)
*/
private $description;
}
Then, the inheritance will do the job.
Now, what is the problem? The problem is that each Ads entity is related to its entity that defines the list of users that added the ads to their favorites. To do that (the MotorsAds entity for example),
1.linking the MotorsAds entity to its MotorsFavorite entity through that code:
/**
* #ORM\OneToMany(targetEntity="Minn\AdsBundle\Entity\MotorsFavorite",
* mappedBy="motors",cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $favorites;
2.Defining the MotorsFavorite entity as fellows:
<?php
namespace Minn\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* MotorsFavorite
*
* #ORM\Table(
* uniqueConstraints={#ORM\UniqueConstraint(name="unique_fav_motors",
* columns={"user_id", "motors_id"})})
* #ORM\Entity(repositoryClass="Minn\AdsBundle\Entity\MotorsFavoriteRepository")
* #ORM\HasLifecycleCallbacks()
*/
class MotorsFavorite {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Minn\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="Minn\AdsBundle\Entity\MotorsAds", inversedBy="favorites")
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
private $motors;
//...
}
As you can see, the linkage between the MotorAds and MotorFavorite is a hard linkage, which means that I have to create a Favorite entity for each Ads entity I create (FavoriteMotors, FavoriteRealestate, FavoriteElectronics, ...). This is a long and repetitive work.
So my question is:
1.Creating a super mapped class called SuperMappedFavorite which will only include the $id and $user attributes will reduce the repetitive work. But what about the the attribute $motors? $motors is hardly linked to the entity MotorsAds as you see here:#ORM\ManyToOne(targetEntity="Minn\AdsBundle\Entity\MotorsAds", inversedBy="favorites"). All the burden of the work is in the setters and getters of $motors.
2.Is it possible to make the target entity an interface like this:
<?php
// SuperMappedFavorite.php
// ...
#ORM\ManyToOne(targetEntity="Minn\AdsBundle\Favorite\FavoriteAwareInterface", inversedBy="favorites")
private $object;
// ...
and the MotorsAds entity will be implementing in this the FavoriteAwareInterface
If anyone has a good link/article regarding this kind of issue, I will be happy to have it.
Thanks.
Yes, you can set an interface as target entity, as described in the Symfony documentation.
The process is basically:
defining the interface (your Minn\AdsBundle\Favorite\FavoriteAwareInterface),
setting the interface in the parent entity (as you already did),
implementing the interface in a different entity (would be class MotorsFavorite implements FavoriteAwareInterface) – and yes, it can also be derived from a mapped superclass,
and then telling Doctrine to use your implementation through the doctrine.orm.resolve_target_entities config parameter.
See the documentation for details and a code example.
I can't figure it out..
Why I haven't access to Country table?
countryName should show Great Britain but it doesn't.
This is my dump($User):
My my piece of code of User entity:
/**
*
* #ORM\ManyToOne(targetEntity="Dashboard\MainBundle\Entity\Country", cascade={"persist"})
* #ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=true)
*
*/
private $countryId;
And my piece of code of Country Entity:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
Depending on how you get the user maybe it is a lazy load that you are using which will get the country only if you call the getter explicitly, to always get a country with the user try :
/**
*
* #ORM\ManyToOne(targetEntity="Dashboard\MainBundle\Entity\Country", cascade={"persist"}, fetch="EAGER")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id", nullable=true)
*
*/
private $countryId;
But still we need to know how you are getting the user the lazy load may override the fetch eager.
Your Country object is now only a Proxy object - dump function don't call a Doctrine to get a related object. Try before dump get your object for example:
dump($User->getCountry()):
dump($User);
OR try left join you Country in QueryBuilder
OR find a information about lazy load in Doctrine2 here
I have several bundles in my app and I would like to have relations between tables.
One is my User(StoreOwner) which is in UserBundle, and the second is Store in StoreBundle.
The relation between them is OneToMany (User -> is owner of -> Store).
Store
/**
* Description of Store
*
* #ORM\Table(name="Store")
* #ORM\Entity(repositoryClass="Traffic\StoreBundle\Repository\StoreRepository")
* #author bart
*/
class Store extends StoreModel {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $name
*
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(
* message="Please provide your shop name"
* )
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\StoreOwner", inversedBy="stores")
*
*/
protected $owner;
}
StoreOwner
/**
* #ORM\Entity
*
*/
class StoreOwner extends User implements StoreOwnerInterface {
/**
* #var type ArrayCollection()
*
* #ORM\OneToMany(targetEntity="Traffic\StoreBundle\Entity\Store", mappedBy="owner", cascade={"persist"})
*/
protected $stores;
}
My question is:
Is there any solution to avoid dependency between StoreBundle and UserBundle and keep relations between Entities in Doctrine?
This is a valid concern in my opinion. Two-way dependencies between bundles are a smell.
One way of solving the dependency issue is moving your entities out of the bundles into a more general namespace. This way both bundles will depend on the same "library" but won't depend on each other directly.
I recently wrote a blog post on how to do it: How to store Doctrine entities outside of a Symfony bundle?