Bi-directional OneToOne not loaded when using custom DBAL types #5959

Closed
opened 2026-01-22 15:23:11 +01:00 by admin · 5 comments
Owner

Originally created by @dekker-m on GitHub (May 1, 2018).

Originally assigned to: @Ocramius on GitHub.

Some days ago we mapped some legacy database to Doctrine. It went well, however, we ran into an issue today with a bi-directional one-to-one association. The association uses custom DBAL types as join columns. This creates an issue when loading the entity. From what we found, the following is happening:

  1. Entity A is loaded from the database, this entity is loaded correctly. The primary key from A is a custom DBAL type (a UUID stored as an BINARY(16), let's call the DBAL type "binary_uuid"). In the DBAL SQL Logger we see the query is executed as expected, the parameter for the query is a UUID in the form "00000000-0000-0000-0000-000000000000". The type of the parameter is given as "binary_uuid".
  2. Entity B, the owning side of the association, is loaded automatically by the method BasicEntityPersister::loadOneToOneEntity(). Using the DBAL SQL Logger we see that the correct SQL is being executed to load the entity. However, the parameter and/or the type of the parameters are wrong. The parameter is again an UUID in the above mention form, however the type of the parameter is not detected at all. Because an UUID in the above mentioned form does not match the BINARY(16) column in the database, no record is returned.

I'm not sure if this is a scenario which is supported by Doctrine and if this would be an improvement or a bug, but from what I can see by looking at the BasicEntityPersister, it would be possible to load those associations correctly. The reason why BasicEntityPersister::getTypes() is not recognizing the type of the fields correctly is because loadOneToOneEntity() passes an SQL column to the load() method. getTypes() looks at the mapping for object properties instead of SQL columns.

I'm not that familiar with the doctrine codebase, but from what I can see there could be two possible fixes to support loading entities with custom types as join column (at least for one-to-one associations).

  1. In loadOneToOneEntity() the methods Type::getType() and PersisterHelper::getTypeOfColumn() can be used to get the type of the column and convert the value to a database value using the type's method convertToDatabaseValue. This is a solution I tested locally and this seems to work. However, I can understand this might not be the best solution, because the executeQuery method on the connection has a $types parameter for a reason. If for some reason the type of the parameter is recognized after all, this would cause the conversion to happen twice.
  2. loadOneToOneEntity() is now passing the identifiers to load() using SQL columns. However, load() already seems to take care of the conversion from property names/association names to SQL column names. Instead of using SQL columns in the identifier, why not use the association name of the target class? For example:
$identifier = [$owningAssoc['fieldName'] => $sourceEntity];

This seems to load the entities as well, and lets the load() method take care of the property to SQL column mapping. I tested this solution as well and it seems to work. I can't really think of a downside of this solution, nor of any side effects.

I tested this against the following packages:

doctrine/annotations                     v1.6.0 
doctrine/cache                           v1.7.1
doctrine/collections                     v1.5.0
doctrine/common                          v2.8.1
doctrine/data-fixtures                   v1.3.0
doctrine/dbal                            v2.6.3
doctrine/inflector                       v1.3.0
doctrine/instantiator                    1.1.0
doctrine/lexer                           v1.0.1
doctrine/orm                             v2.5.14

Could the second solution work without introducing any side effects? If so, I might be able to write a pull request.

Originally created by @dekker-m on GitHub (May 1, 2018). Originally assigned to: @Ocramius on GitHub. Some days ago we mapped some legacy database to Doctrine. It went well, however, we ran into an issue today with a bi-directional one-to-one association. The association uses custom DBAL types as join columns. This creates an issue when loading the entity. From what we found, the following is happening: 1. Entity A is loaded from the database, this entity is loaded correctly. The primary key from A is a custom DBAL type (a UUID stored as an BINARY(16), let's call the DBAL type "binary_uuid"). In the DBAL SQL Logger we see the query is executed as expected, the parameter for the query is a UUID in the form "00000000-0000-0000-0000-000000000000". The type of the parameter is given as "binary_uuid". 2. Entity B, the owning side of the association, is loaded automatically by the method `BasicEntityPersister::loadOneToOneEntity()`. Using the DBAL SQL Logger we see that the correct SQL is being executed to load the entity. However, the parameter and/or the type of the parameters are wrong. The parameter is again an UUID in the above mention form, however the type of the parameter is not detected at all. Because an UUID in the above mentioned form does not match the BINARY(16) column in the database, no record is returned. I'm not sure if this is a scenario which is supported by Doctrine and if this would be an improvement or a bug, but from what I can see by looking at the `BasicEntityPersister`, it would be possible to load those associations correctly. The reason why `BasicEntityPersister::getTypes()` is not recognizing the type of the fields correctly is because `loadOneToOneEntity()` passes an SQL column to the `load()` method. `getTypes()` looks at the mapping for object properties instead of SQL columns. I'm not that familiar with the doctrine codebase, but from what I can see there could be two possible fixes to support loading entities with custom types as join column (at least for one-to-one associations). 1. In `loadOneToOneEntity()` the methods `Type::getType()` and `PersisterHelper::getTypeOfColumn()` can be used to get the type of the column and convert the value to a database value using the type's method `convertToDatabaseValue`. This is a solution I tested locally and this seems to work. However, I can understand this might not be the best solution, because the `executeQuery` method on the connection has a `$types` parameter for a reason. If for some reason the type of the parameter is recognized after all, this would cause the conversion to happen twice. 2. `loadOneToOneEntity()` is now passing the identifiers to `load()` using SQL columns. However, `load()` already seems to take care of the conversion from property names/association names to SQL column names. Instead of using SQL columns in the identifier, why not use the association name of the target class? For example: ```php $identifier = [$owningAssoc['fieldName'] => $sourceEntity]; ``` This seems to load the entities as well, and lets the `load()` method take care of the property to SQL column mapping. I tested this solution as well and it seems to work. I can't really think of a downside of this solution, nor of any side effects. I tested this against the following packages: ``` doctrine/annotations v1.6.0 doctrine/cache v1.7.1 doctrine/collections v1.5.0 doctrine/common v2.8.1 doctrine/data-fixtures v1.3.0 doctrine/dbal v2.6.3 doctrine/inflector v1.3.0 doctrine/instantiator 1.1.0 doctrine/lexer v1.0.1 doctrine/orm v2.5.14 ``` Could the second solution work without introducing any side effects? If so, I might be able to write a pull request.
admin added the BugInvalidMissing Tests labels 2026-01-22 15:23:11 +01:00
admin closed this issue 2026-01-22 15:23:11 +01:00
Author
Owner

@oleg-andreyev commented on GitHub (Oct 5, 2018):

We've faced same issue with custom types (uuid), we've figure out how to handle it by adding

case strpos($field, '.') !== false && !count($types):
               $columnName = substr($field, strpos($field, '.') + 1);
               $types[] = PersisterHelper::getTypeOfColumn($columnName, $this->class, $this->em);
               break;

to \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getTypes, but:

  • we cannot alter getTypes because it's private
  • we cannot replace BasicEntityPersister with own implementation, because it's initialized in directly in UnitOfWork (and is based of other persistent)
  • we cannot replace UnitOfWork with own implementation, because it's initialized directly in EntityManager
@oleg-andreyev commented on GitHub (Oct 5, 2018): We've faced same issue with custom types (uuid), we've figure out how to handle it by adding ``` case strpos($field, '.') !== false && !count($types): $columnName = substr($field, strpos($field, '.') + 1); $types[] = PersisterHelper::getTypeOfColumn($columnName, $this->class, $this->em); break; ``` to `\Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getTypes`, but: - we cannot alter `getTypes` because it's private - we cannot replace `BasicEntityPersister` with own implementation, because it's initialized in directly in `UnitOfWork` (and is based of other persistent) - we cannot replace `UnitOfWork` with own implementation, because it's initialized directly in `EntityManager`
Author
Owner

@Ocramius commented on GitHub (Oct 5, 2018):

Replacing/customising is not really what should happen here, @oleg-andreyev: the proper type conversions should happen anyway, and ORM 2.6 includes a lot of fixes to achieve that.

What needs to be done here?

  • test case needs to be written
  • technical solution (in this repository) can then be written
@Ocramius commented on GitHub (Oct 5, 2018): Replacing/customising is not really what should happen here, @oleg-andreyev: the proper type conversions should happen anyway, and ORM 2.6 includes a lot of fixes to achieve that. What needs to be done here? * test case needs to be written * technical solution (in this repository) can then be written
Author
Owner

@oleg-andreyev commented on GitHub (Oct 5, 2018):

@Ocramius thanks for responding. I'll try to prepare test case as soon as possible.

@oleg-andreyev commented on GitHub (Oct 5, 2018): @Ocramius thanks for responding. I'll try to prepare test case as soon as possible.
Author
Owner

@oleg-andreyev commented on GitHub (Oct 5, 2018):

@dekker-m @Ocramius after preparing test case, I've found out that this issue is no longer relevant (at least my case OneToOne to JoinedTable entity) in >= v2.6.0

@oleg-andreyev commented on GitHub (Oct 5, 2018): @dekker-m @Ocramius after preparing test case, I've found out that this issue is no longer relevant (at least my case OneToOne to JoinedTable entity) in >= v2.6.0
Author
Owner

@Ocramius commented on GitHub (Oct 6, 2018):

Closing as per https://github.com/doctrine/doctrine2/issues/7209#issuecomment-427474070

@dekker-m please submit a test scenario if you manage to reproduce this in 2.6 or newer

@Ocramius commented on GitHub (Oct 6, 2018): Closing as per https://github.com/doctrine/doctrine2/issues/7209#issuecomment-427474070 @dekker-m please submit a test scenario if you manage to reproduce this in 2.6 or newer
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5959