37

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
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Mitchel Verschoof
  • 1,543
  • 4
  • 20
  • 38

5 Answers5

73

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;
Jonathan
  • 13,947
  • 17
  • 94
  • 123
40

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 :(');
            }  
        }

    }
}
Fabian Schmick
  • 1,616
  • 3
  • 23
  • 32
psylosss
  • 3,469
  • 3
  • 16
  • 26
  • 2
    How to deal with collisions? in a web application, before the generated id is flushed in the db, it is possible that another request has generated and flushed the very same id. Is there a more robust solution? – Vincent Pazeller Oct 22 '15 at 08:36
  • @VincentPazeller, I think there is no way to handle this type of collision in absolute. You can only decrease probablity of collision. For example, extending ID range or adding something like 2 digits milliseconds of current timestamp in the beginning of ID (so collision probablity equals probablity of generation two same ID in your range by requests came in one millisecond in condition that time between generation ID and flushing it is less than 99 milliseconds) or adding some request-specified marker. Any other ideas? – psylosss Oct 22 '15 at 09:05
  • Instead of raising an exception, why don't you loop until you get a non existing ID? – Nicolas Oct 22 '16 at 15:52
  • @Nicolas, there is a logical bug in this code. Exception never be raised because cycle is infinite ;) You are free to write your own random ID generator, it's just an example, not best of the world. For example, to make this cycle not infinite you can add increment counter and break the loop when it reach maximum value. If "Id must be 6 charters like: 678915" we cannot loop more than 999999 times. – psylosss Oct 22 '16 at 20:02
  • 1
    I corrected code example, so it consider maximum attempts to generate unique ID. – psylosss Oct 22 '16 at 20:14
3

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")
Community
  • 1
  • 1
ihsan
  • 2,279
  • 20
  • 36
  • This seems to work outside of the `@Id` annotation as the `@Id` is generated after the `PrePersist` event, so I don't think it can be substituted to the original `@Id`. – Aerendir Nov 05 '16 at 18:52
2

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
Bram Gerritsen
  • 7,178
  • 4
  • 35
  • 45
  • Sorry, I forgot to tell that I want to have the id like: 986016 (6numbers) and the id must be unique. – Mitchel Verschoof Feb 23 '13 at 11:04
  • How about `ALTER TABLE mytable SET AUTO_INCREMENT = 100000`, Or take care about the ID generation in your application code. Why do you want 6 numbers? You can also zero fill to 6 chars (`str_pad`) when you are presenting the number and just use a simple auto_increment for your id in the database. – Bram Gerritsen Feb 23 '13 at 11:12
  • no sorry, I want unique and diffrent numbers, so not 100001, 100002, 100003. – Mitchel Verschoof Feb 23 '13 at 11:40
  • Create a seperate table containing all possible ticket ID's ie 100000-999999 and a flag to indicate if it is used or not. Before persisting your ticket select a random ticket id from the table and set `used` to true. Remove the `@GeneratedValue` annotation. – Bram Gerritsen Feb 23 '13 at 11:58
1

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.

Massimiliano Arione
  • 2,422
  • 19
  • 40