Generate unique id - doctrine - symfony2 - symfony

I want to generate a unique ticket ID for my tickets. But how to let doctrine generate a unique id?
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
little more explain:
id must be 6 charters like: 678915
id must be unique

As of version 2.3, you can just add the following annotations to your property:
/**
* #ORM\Column(type="guid")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
protected $id;

Use custom GeneratedValue strategy:
1. In your Entity class:
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="AppBundle\Doctrine\RandomIdGenerator")
*/
protected $id;
2. Then create file AppBundle/Doctrine/RandomIdGenerator.php with content
namespace AppBundle\Doctrine;
use Doctrine\ORM\Id\AbstractIdGenerator;
class RandomIdGenerator extends AbstractIdGenerator
{
public function generate(\Doctrine\ORM\EntityManager $em, $entity)
{
$entity_name = $em->getClassMetadata(get_class($entity))->getName();
// Id must be 6 digits length, so range is 100000 - 999999
$min_value = 100000;
$max_value = 999999;
$max_attempts = $min_value - $max_value;
$attempt = 0;
while (true) {
$id = mt_rand($min_value, $max_value);
$item = $em->find($entity_name, $id);
// Look in scheduled entity insertions (persisted queue list), too
if (!$item) {
$persisted = $em->getUnitOfWork()->getScheduledEntityInsertions();
$ids = array_map(function ($o) { return $o->getId(); }, $persisted);
$item = array_search($id, $ids);
}
if (!$item) {
return $id;
}
// Should we stop?
$attempt++;
if ($attempt > $max_attempts) {
throw new \Exception('RandomIdGenerator worked hardly, but failed to generate unique ID :(');
}
}
}
}

You can use the PrePersist annotation, like this:
/**
* #ORM\PrePersist()
*/
public function preSave() {
$this->id = uniqid();
}
As the annotation name suggest, it will be run before object persistence into database.
For unique id, I simply use a native php uniqid() function http://php.net/manual/en/function.uniqid.php which will return 13 characters. To get only 6 characters, refer to this PHP Ticket ID Generation
In the $id property, I think you also need to remove this line to prevent auto generated value of it:
#ORM\GeneratedValue(strategy="AUTO")

Doctrine will treat this field as your primary key (because of the #Id annotation), so this field is already unique. If you have the #GeneratedValue annotation on AUTO strategy Doctrine will figure out which strategy to use dependend on the db platform. It will default to IDENTITY on MySql and the field will be a auto_increment then.
You can write the id annotation without the brackets as follows.
ORM\Id

While I'm seconding the UUID approach suggested by Jonhathan, you could prefer a shorter, more readable, identifier. In this case you can use ShortId Doctrine bundle.

Related

Symfony 3.4 entity extra columns in controller response

I have this entity:
<?php
//namespace
//use ...
class Guide
{
private $id;
//private ...
//getters
//setters
}
?>
In a controller I use the entity manager to retrieve the data of this entity.
$guides= $em->getRepository('AppBundle:Guide')
->findAll();
My entity has 4 parameters: id, name, pages, author.
Is there any way to add two extra parameters, that aren´t in the class declaration and I don´t want in the database, if the entity manager returns for example 3 rows, I want o add two extra values to each row and return the data, for example add two boolean values: ok => true, warning => false.
I have tried this:
foreach($guides as $guide){
$guide->ok=true;
$guide->warning=false;
}
If I dump $guides, I see the two parameters like this:
-id:1
-name:'Guide 1'
-pages:12
-author:'John'
+"ok":true
+"warning":false
But when I use this to send a response:
return new Response($serializer->serialize($guides, 'json'));
The two extra parameters aren´t in the response.
You could add a property to entity and do not tag it as a ORM\Column eg:
<?php
//...
/**
* #ORM\Entity
* #ORM\Table(name="guides")
*/
class Guide
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(name="title", type="string")
*/
private $name;
public $myAdditionalProperty;
//...
And then set it in your controller:
foreach($guides as $guide){
$guide->myAdditionalProperty = "my amazing value";
}
Then you can serialize your data without having additional column in your table

sort objects with several parameters - symfony2

I want to display a list of objects ordered by a parameter that depends of several fields of the entity.
I have created an entity, let's call it Object for instance, which has an id and 2 integers as fields. I work with Symfony 2 and Doctrine as ORM.
`/**
* Object
*
* #ORM\Table(name="object")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ObjectRepository")
*/
class Object
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="field1", type="int")
*/
private $field1;
/**
* #var int
*
* #ORM\Column(name="field2", type="int")
*/
private $field2;
}
Now I would like to get the top-5 instances of Object from my database ordered by, for example, the average between field1 and field2, to display them in a twig template.
I know I can use this:
$list = $this->getDoctrine()->getRepository('AppBundle:Object')->findAll();
to get a list of all existing instances of Object in the DB, but although it might be a relatively simple question to experimented Symfony programmers, I have no idea about how to order it by avg(field1,field2).
You could create a method in your repository that builds a query containing a special ORDER BY clause and that limits the number of results to 5. It could look like this:
class ObjectRepository extends \Doctrine\ORM\EntityRepository
{
/* ... other methods ... */
public function retrieveTopObjects($limit = 5)
{
$qb = $this->createQueryBuilder('o')->orderBy('(o.field1 + o.field2) / 2', 'DESC');
$qb->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
}
If you would like to have the score used to sort entities along with your objects, you would need to add $qb->addSelect('(o.field1 + o.field2) / 2 AS score') and then use $qb->orderBy('score', 'DESC') instead of the expression shown above.
In such a case, you would end up with a mixed result as explained in the documentation.

Doctrine inserts twice when adding one related entity and using $em->clear()

When I try to add new info item it either inserts two of them or none. In the state of code below it inserts two. If i comment something it inserts none. When I comment the line with $em->clear() it inserts exactly one, as I need it. What I don't understand and what I'm doing wrong?
$limit = 10;
$offset = 0;
do {
$products = $selectedModelQuery->getQuery()
->setFirstResult($limit * $offset)
->setMaxResults($limit)->getResult();
$offset++;
$count = count($products);
/** #var \Nti\ApiBundle\Entity\Product $product */
foreach ($selectedModelQuery->getQuery()->getResult() as $product) {
if (!$product->getCollections()->contains($productCollection)) {
$product->addCollection($productCollection);
$productInfo = new ProductInfo;
$productInfo->setProduct($product);
$productInfo->setData($productCollection->getMessage());
$productInfo->setInfoType(ProductInfoInterface::TYPE_CUSTOM);
//$em->merge($product);
//$em->persist($productInfo);
}
}
$em->flush();
$em->clear();
} while ($count);
Main Entity:
class Product {
/**
* #ORM\OneToMany(targetEntity="ProductInfo", mappedBy="product", cascade={"persist", "remove"})
* #var ProductInfoInterface[]|ArrayCollection
*/
protected $infoList;
....
}
Related Entity:
class ProductInfo {
/**
* #ORM\ManyToOne(targetEntity="\Nti\ApiBundle\Entity\Product", inversedBy="infoList")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
protected $product;
...
}
Doctrine is based on Identity Map that keeps reference to all handled object (ie.: if you try to query two times for the same record by id, first time you hit the db but the second not and object is loaded from Identity Map).
When you use $em->clear() you are "discarding" all objects into Identity Map so, next iteration of do ... while will result in a brand new object (from ORM point of view) that will be flagged for writing operation.

Cascade remove working fined but not when more than one entry... why?

I am building an app where USERS can give a SCORE to some ANSWERS left by the community.
here the schema/relations of the entities:
ANSWER (one to many with) SCORE (many to one with) USER
I want that when i remove an ANSWER, the SCORES related to that ANSWER are also deleted...
BUT when I have more than one SCORE, then symfony triggers this exception:
"SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`symfony`.`Score`, CONSTRAINT `FK_6F8F552A4AB7A507` FOREIGN KEY (`answer_id`) REFERENCES `Answer` (`id`))
I must say that it works fine if there is only one SCORE related to that answer. (strange?)
Here my entities:
you will see that I do have the cascade={"remove"}, I emphasis the fact that SCORE id is build on ANSWER_ID and USER_ID. (because I want that a SCORE can only be delivered once for an answer by a user).
class=Answer
{
/**
* #ORM\OneToMany(targetEntity="XX\BlogBundle\Entity\Score", mappedBy="answer", cascade={"persist", "remove"})
**/
private $scores;
/**
* #param \XX\BlogBundle\Entity\Score $scores
*/
public function removeScore(\XX\BlogBundle\Entity\Score $score)
{
$this->scores->removeElement($score);
}
// OTHER ATTRIBUTES ETC
}
.
class=Score
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="XX\BlogBundle\Entity\Anwer", inversedBy="scores")
*/
private $answer;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="XX\BlogBundle\Entity\User", cascade={"persist"})
*/
private $user;
/**
* #var smallint
* #ORM\Column(name="valeur", type="smallint", length=10, nullable=true)
*/
private $value;
// SETTER AND GETTER
}
.
class User
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=10)
*/
private $name;
}
I would remove them manually instead of any cascade operations.. Something like this:
public function removeAnswerAction($id)
{
...
$answer = /* ... */ ->find($id);
foreach ($answer->getScores as $score) {
$answer->removeScore($score);
$score->setAnswer(null); // it requires setAnswer(Answer $answer = null), if you used a type hint in getter
$em->remove($score); // you can remove it if you want..
}
$em->remove($answer);
$em->flush();
...
}
I don't know how cascade=remove works with collections (I've never been interested in using cascade operations), but in your case doctrine is saying that it cant remove parent (Answer object) without deleting all its collection (related Score objects)..
Probleme solved (see below my exlanation) but not sure if it is the best way. (If anyone has a better solution?)
Actually the probleme doesn't come from the entities or the cascade remove (all that part is ok, maybe it can be optimised)
The issue come from the fact that I used an $answer object on which I had removed some of its scores (throught removeScore(score) in the controller before doing $em->remove($answer).
Also when symfony/doctrine tried to remove in cascade the scores associated to that $answer it couldn't find all of them and potentially would have create some orphin entries in the database, which triggered that exception.
So because I trully need to remove some scores from the $answer object, my solution is to store all thoses scores that I remove in an array so that I can add them to the $answer object before removing it.
$answer = $em->getRepository('XXBlogBundle:Answer')->find(1);
foreach($answer->getScores() as $score)
{
// I REMOVE SOME SCORES HERE FROM THE ANSWER OBJECT
if($score->getValue() == 0)
{
$array_temp_save_score[] = $score;
$answer->removeScore($score);
}
}
// SOME OF MY CODE WHERE I USE $ANSWER WITHOUT ALL SCORE
// ...
// END OF THAT PART
foreach($array_temp_save_score as $v)
{
$answer->addScore($v);
}
$em->remove($answer);
$em->flush();

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

Resources