Scenario:
I want to have an AUTO_INCREMENT id from my DB layer, and also have a hash as Uuid.
If you have your entity like this:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Foo
{
/**
* @var int
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var Uuid
* @ORM\Column(type="uuid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
private $uuid;
// ...
}
When you try to persist a new entity:
$foo = new Foo();
$em->persist($foo);
$em->flush($foo);
Problem:
You will get an error saying:
"detail": "An exception occurred while executing 'INSERT INTO db_name.foo (id, hash) VALUES (?, ?)' with params [\"431548bf-1a89-4552-90dd-a1ee89b659f8\", \"36710515-8595-49c0-86b9-8c504b7fb243\"]:
Notice: Object of class Ramsey\Uuid\Uuid could not be converted to int",
I was wondering why this was happening so I went deeper into Doctrine, and inside UnitOfWord::persistNew($class, $entity) I found something interesting:
$idGen = $class->idGenerator; [line: 895] is a UuidGenerator!?
But shoulnd't it be the generator for the id property the @ORM\Id | @ORM\GeneratedValue(strategy="IDENTITY") auto increment by default? Why it seems to be overrite it by the new Ramsey Uuid Generator?
After a little research I came with the conclusion that Doctrine annotations -in order to determine the idGenerator- it loads by order which is the idGenerator for the entity. Which means the last one (annotation) override the previous one.
I came with this solution:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Foo
{
/**
* @var Uuid
* @ORM\Column(type="uuid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
private $uuid;
/**
* IMPORTANT! This field annotation must be the last one in order to prevent
* that Doctrine will use UuidGenerator as $`class->idGenerator`!
*
* @var int
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ...
}
Conclusion: the solution was just moving as last annotation the id property from the entity which it is actually the real id (as AUTO_INCREMENT int from the DB) and still have the Uuid from Ramsey\Uuid\Uuid.
I think we should avoid this ordering problem in our entities files and takes the idGenerator from the property which has @ORM\Id.
There's no place in the Doctrine code that will use an IdentityGenerator for a non-identity field. Uses of IdentityGenerator::generate(): here, here and here; all deal with setting the identity fields.
I agree that if this is intentional, then it's a bug that the metadata loader still looks for the generator even if the field is not part of the identity.
This isn't UUID-specific, it goes for anything field type that you might want to generate automatically. The way things currently work, if you want to automate the generation of a non-identity field then you will need to use an event listener of some sort (I'd start with prePersist)
I'll also point out that the XML, Yaml and Database drivers do seem to isolate the setting of custom generators to id elements.
My code references were for 2.5 — master seems to have some heavy changes in it, but I believe it still has the same issue here
GeneratedValue currently only works for identifier fields on non-composite identified entities.
GeneratedValue currently only works for identifier fields on non-composite identified entities.
So, is this by design or a bug?
This is currently by design but it could become supported in ORM 3.0.
Note that in ORM 3.0 the UUID generator strategy will be removed, please use constructor injection for passing entity (uu)IDs.
And what if you want UUID as Your IDENTITY __and__ have internal AUTO GENERATED int in entity? It cries:
Could not convert database value "8" to Doctrine Type uuid
And what if you want UUID as Your IDENTITY?
I'd recommend using ramsey/uuid-doctrine:
/**
* @ORM\Entity
*/
class Foo
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
private $id;
public function __construct(UuidInterface $id)
{
$this->id $id;
}
}
It also provides UuidGenerator but I'd advise against that.
@Majkl578
/**
* @ORM\Entity
*/
class Foo
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
private $id;
/**
* @ORM\GeneratedValue(strategy="SEQUENCE")
* @ORM\Column(type="integer")
*/
private $counter;
public function __construct(UuidInterface $id)
{
$this->id = $id;
}
}
But it won't work.
Only identifiers can have @GeneratedValue
Closing here. As mentioned above:
I agree that if this is intentional, then it's a bug that the metadata loader still looks for the generator even if the field is not part of the identity.
Please open a new issue with a schema validator patch (preventing usage of @GeneratedValue on non-identifiers or composite identifiers), if needed.
Only identifiers can have
@GeneratedValue
That's true, but actually, an AUTO_INCREMENT can be applied only to one key in a table, but not necessarily the PK.
Consider this example:
DROP TABLE IF EXISTS `test_auto_incremental`;
CREATE TABLE `test_auto_incremental` (
`uuid` VARCHAR(64) PRIMARY KEY NOT NULL,
`text` VARCHAR(255) DEFAULT NULL,
`counter` INT(11) AUTO_INCREMENT NOT NULL,
KEY `counter` (`counter`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO test_auto_incremental (uuid, `text`) VALUES (uuid(), 'Hello');
INSERT INTO test_auto_incremental (uuid, `text`) VALUES (uuid(), 'world');
SELECT * FROM `test_auto_incremental`;
So, technically Only identifiers can have @GeneratedValue is only for Doctrine by design, because at a lower level(SQL) we could say something like: Any Key can have a @GeneratedValue, but only one per Entity/Table.
In such case, this would allow this logic:
/** @ORM\Entity */
class Foo
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
private $id;
/**
* @ORM\GeneratedValue(strategy="SEQUENCE")
* @ORM\Column(type="integer")
*/
private $counter;
Yes, it can, but the ORM will not re-fetch data from the underlying layer after INSERT/UPDATE, so this won't ever work.
Same applies for: