Use Doctrine UUID in prePersist LifecycleCallback - symfony

Is there any way to use the previous generated UUID in entities LifecycleCallback?
/**
* #ORM\Table("user")
* #ORM\HasLifecycleCallbacks()
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="guid")
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(name="slug", type="string", length=36)
*/
private $slug;
[.. getId() / setSlug() / getSlug() ..]
/**
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->setSlug($this->getId());
}
}
My intention is to use the UUID as default slug on user creation until I got for example the users name to update the slug. Can be mysql triggers a solution?

I think you have 2 options:
1) You can manually create a UUID through this library (or another library). Then you can access them, in the prePersist event.
2) Or you use the postPersist handler, but this will create an INSERT statement and an UPDATE statement.
Use an MySQL trigger is a bad idea, because nobody can control them. It's like magic, it happens but nobody knows how. And update them is a pain.

Related

To many queries for logged in user. Partial loading doesn't work. Doctrine Symfony2

enter image description hereIn my Symfony2 project, using Doctrine2, I got a User class extending the FOSUserBundle's User class, which has a lot of associations, as an adress, images,commands, etc..
class User extends BaseUser{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
*/
protected $id;
/**
*
* #ORM\OneToMany(targetEntity="ANG\CommandBundle\Entity\Command", cascade={"persist", "remove"}, mappedBy="customer")
*#Expose
*
*/
private $commands;
/**
*
* #ORM\OneToOne(targetEntity="ANG\FileBundle\Entity\Image", cascade={"remove"})
* #ORM\JoinColumn(onDelete="SET NULL")
* #Assert\Valid()
* #MaxDepth(1)
* #Expose
*/
private $banner;
/**
*
* #ORM\OneToOne(targetEntity="ANG\FileBundle\Entity\Image", cascade={"remove"})
* #ORM\JoinColumn(onDelete="SET NULL")
* #Assert\Valid()
* #MaxDepth(2)
* #Expose
*/
private $avatar;
/**
*
* #ORM\OneToMany(targetEntity="ANG\MainBundle\Entity\Address", cascade={"persist", "remove"}, mappedBy="customer")
*#MaxDepth(1)
*#Expose
*/
private $address;
/**
*
* #ORM\OneToMany(targetEntity="ANG\MainBundle\Entity\Invitation" , mappedBy="customerSender", cascade={"persist", "remove"})
*
*/
protected $invitationsSend;
...
I realised that when I do $this->getUser() in a controller, the doctrine lazy loading executes 128 queries... (which is far too much to be acceptable)
So I created my own function in the UserRepository, using setHint(Query::HINT_FORCE_PARTIAL_LOAD, true) to avoid the lazy loading.
public function testGetUser($id){
$em=$this->getEntityManager();
$qb=$em->createQueryBuilder()
->select('cs')
->from('ANGCustomerBundle:User', 'cs')
->where('cs.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getOneOrNullResult();
}
My problem : That function does not work for the logged in user.
Example:
I'm logged in with the User having ID = 3
Working on all users except me
For every ID != 3, the abose request is working.
$user = $em->getRepository('ANGCustomerBundle:User')->testGetUser(4); returns: {id: 4, username: "coy", email: "n****#gmail.com"}
Result:
2 queries
User object without any associations
Not working on the logged in user...
But the same request on the logged in user $user = $em->getRepository('ANGCustomerBundle:User')->testGetUser(3); returns
{"id":3,"username":"bolz","email":"h******#gmail.com","images":[{"id":15,"url":"png"},{"id":20,"url":"jpeg"}],"commands":[{"id":1,"date_creation":"2016-03-25T15:52:40+0100", .....
Result:
128 queries
The entire User object, with all his associations. We can see that we have the user's command, but we also have all the command's associate entities and so on ...
It looks like the HINT_FORCE_PARTIAL_LOAD doesn't apply on the logged in user
.
Just to conclude, this problem is also present if I request on all the users together.
public function findALLs(){
$em=$this->getEntityManager()
->createQueryBuilder()
->select('cs')
->from('ANGCustomerBundle:User', 'cs')
->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
}
For all users except the logged on : username, email, id
But like for the 1st function, we have the entire structure for the logged in user, doing once again, 128queries...
I've been stuck for a week now on this and really don't understand what's happening here.
I really thank you for reading this long post, and hope someone could help me on this one.
With much thanks, Bastien.
The problem is with JMSSerializerBundle, which use \JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber in which there is a call to $object->__load(); that trigger lazy loading.
See this question where people had the same problem and managed to make it work.
Hope it helped.

Symfony2 OneToMany relationship - remove action isn't called

I'm using Symfony 2.4.6 and I'm trying to use OneToMany relationship to manage images added to a banner.
I did read a lot about the deletion of a child element (setting orphanRemoval, adding 'remove' to cascade) but none of those worked for me. What I've noticed is that remove actions isn't called at all on update.
I have 2 classes, Banner and BannerFile and using collection field type for adding images and it seems to work OK except the delete action.
class Banner
{
/.../
/**
* #ORM\OneToMany(targetEntity="BannerFile", cascade={"persist", "remove"}, mappedBy="banner", orphanRemoval=true)
*/
private $bannerFiles;
/.../
/**
* Remove bannerFiles
*
* #param BannerFile $bannerFiles
*/
public function removeBannerFile(BannerFile $bannerFiles)
{
$this->bannerFiles->removeElement($bannerFiles);
}
}
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner")
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}
My problem is that the removeBannerFile isn't called.
Thanks for any help.
This happens if you use a database table engine type that does not support foreign keys (such as MyISAM).
Change your engine type to InnoDB, then run the following code to update your database schema.
php console doctrine:schema:update
Add cascade to the ManyToOne definition:
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner", cascade={"remove"})
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}

ManyToMany relationship with extra fields in symfony2 orm doctrine

Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend

Symfony2 app/console not generating properties or schema updates for Entity Relationships/Associations

I am reading and following along in code what is written in the Symfony2 book on using Database and Doctrine (http://symfony.com/doc/2.0/book/doctrine.html). I have reached the "Entity Relationships/Associations" section but the framework does not seem to be doing what it is meant to be doing. I have added the protected $category field to the Product entity and added the $products field to the Category entity. My Product and Category entities are as below:
Product:
<?php
namespace mydomain\mywebsiteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
/**
* Set description
*
* #param string $description
* #return Product
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Category:
<?php
namespace mydomain\mywebsiteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use \Doctrine\Common\Collections\ArrayCollection;
/**
* Category
*
* #ORM\Table()
* #ORM\Entity
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/*
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
public function __construct(){
$this->products = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set description
*
* #param string $description
* #return Category
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
}
According to the documentation, if i now execute
$ php app/console doctrine:generate:entities mydomain
the framework should generate the getters/setters for the new category field in Product and for the new products field in Category.
HOWEVER when i run the command it supposedly updates the entities but it does not add the properties. I have compared with the backup(~) files and there are no differences. If i add another field (e.g. description2) and add doctrine annotations for persistence to it then it generates the properties. I ignored this at first and manually added the properties for the mapping fields and then executed:
$php app/console doctrine:schema:update --force
for it to add the new association columns.
HOWEVER once again it told me that the metadata and schema were upto date.
I have deleted the app/cache/dev folder and allowed the system to recreate it but it has made no difference.
Can anyone see why the framework is not behaving as described in the documentation??
Thanks
You have forgotten one star here:
/*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
it must be
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
UPDATE: After trying different things with absolutely no success i ended up deleting the entire bundle and associated database and starting from scratch again. Second time around things are generated correctly and the database schema is being updated correctly. Such flaky behavior is EXTREMELY POOR of the framework and as mentioned in the comment above is the reason that as a developer i am moving away from Grails. Now i find that symfony2 has the same sort of problems.
When i use a framework i should not need to always keep in the back of my mind whether something is not working because the framework is buggy. This is quite unacceptable for such a mainstream framework and it would seem i am not the only person that has come across such kind of problems. The framework developers should definitely address such issues either by (preferably) resolving them or providing some means of understanding why the framework fails on random occasions.
Based on what I found the issue is that you can't have two types of definitions..in the book the entity create comand for category also creates a yml configoration so the annotations failed. You must use either annotations or yml or xml or php. Once I removed the yml config and recreated the tables with annotations it worked..be careful and don't use the comnad for the category createion..you will still though get an error that the description is mandatory field :)
I had the exact same issue and I solved it like this:
Delete the "doctrine"-Folder containing the yml-files with the (in your case redundant!) configuration for the entities. Do this ONLY on your test-system for educational purposes.
Some background-information (maybe someone with more experience than me - probably almost everybody here ;o)) can add to this:
Doctrine preferes YML-Schema configuration over annotations in the entity-class (/** #ORM ... */)
when working through the book you might have created a blog-entity with YML-Schema configuration a few chapters in before chapter 8 - maybe you played around a little and this YML-Schema is in the same bundle than your chapter 8 exercise
consequently: Doctrine thinks you want to use YML but it finds only configuation for "anotherEntity" but not for product and category
OR: you run a few Doctrine commands for testing and choose once (by mistake?) YML and voilĂ : all further annotation chances will be ignored because i.e. a product.orm.yml exists
Hope that helped. I just started chapter 10 ;-)
When it comes to generating getters and setters Symfony is just using the ReflectionClass to look if the methods already exist.
It doesn't look what properties are written in the annotation.
Concerning the schema update problem I don't have another solution then resetting the database and creating it from scratch.
I faced this problems a few times but never really found a good solution, it seems Symfony doesn't differ between some properties, which results in not finding any updates.
I don't have a framework by hand now, to look it up. Maybe you can try to find out what schema:update does exactly thus finding the error.

An optional OneToOne relationship between entities?

I have a User Entity. This is considered the primary entity in this case and the mere fact it is being used means it is present.
The User entity, has a Store entity. But not all Users will necessarily have a Store entity.
It is worth noting that this is an existing database we are working with, and the id for the User table is the same as the id for the Store table. Name (id) and Value. It's just that in some cases, Store does not have a record for a given User id.
User:
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $store;
...
}
Store:
class Store extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
...
}
This causes problems in the controllers. If a User entity does not have a Store record, it fails with a "Entity not found" exception. This can be dealt with using a try catch easy enough (I haven't been able to find a way to check if an Entity object exists or is just a proxy). If the User does have a store record, all is fine here.
But the big issue I have is especially the Fixtures:
protected function createUser($id)
{
$user = new User();
$user->setId($id);
$user->setEmail($id.'#example.com');
$user->setUserName($id.'_name');
$user->setArea($this->manager->find('Area', 156)); // Global
$this->manager->persist($user);
return $user;
}
When I run Fixtures, this fails. Giving me the error "Integrity constraint violation: 1048 Column 'id' cannot be null". This message disappears if I remove the store entity from User. So in a nutshell, I cannot add a user if it doesn't have a store.
Anyone know what's happening? I've done some looking around and I can't find anything, including doctrine docs, on having optional relationships between Entities. Which I thought would have been a common situation.
Found the solution to this on this doc page:
http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata
In my case, rather than the User entity being associated with the Store entity using the id field, the store property in the User entity would be associated to the Store entity by user (an entity object). In return, the Store object will hold a User entity, which is annotated as the entity's id.
I'm sure that's as confusing as hell, so just look at the sample above. Below are my adjusted Entity classes:
User
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store", mappedBy="user")
*/
protected $store;
...
}
Store
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}
Now, if there is no Store record present for a given User, the store property in the User entity will be null. Fixtures runs as expected too.
In addition to the answer above, I also needed to add an inversedBy attribute. Otherwise, an invalid Entity mapping error will be thrown.
Using the entities above, the Store object would need to look like this:
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}

Resources