Symfony OneToMany Fetch: Eager with ULID primary key?
Добрый день.
Подскажите как исправить.
Есть 2 сущности
Product
<?php namespace AppEntity; use SymfonyComponentUidUlid; use SymfonyBridgeDoctrineTypesUlidType; use DoctrineORMMapping as ORM; use DoctrineCommonCollections{ ArrayCollection, Collection }; use AppRepositoryProductRepository; #[ORMEntity(repositoryClass: ProductRepository::class)] #[ORMTable(name: 'product')] class Product { #[ORMId] #[ORMColumn(type: UlidType::NAME, unique: true)] #[ORMGeneratedValue(strategy: 'CUSTOM')] #[ORMCustomIdGenerator(class: 'doctrine.ulid_generator')] private ?Ulid $id = null; #[ORMColumn] private ?string $name = null; #[ORMOneToMany(targetEntity: Image::class, mappedBy: 'product', cascade: [ 'persist', 'remove' ], fetch: 'EAGER')] #[ORMJoinColumn(name: 'product_id', referencedColumnName: 'id')] private Collection $images; public function __construct() { $this->images = new ArrayCollection(); } public function getId(): ?Ulid { return $this->id; } public function getUlid(): ?string { return $this->id->toRfc4122(); } public function getName(): ?string { return $this->name; } public function setName(?string $name): static { $this->name = $name; return $this; } public function getImages(): Collection { return $this->images; } } |
<?php namespace AppEntity; use SymfonyComponentUidUlid; use SymfonyBridgeDoctrineTypesUlidType; use DoctrineORMMapping as ORM; use DoctrineCommonCollections{ ArrayCollection, Collection }; use AppRepositoryProductRepository; #[ORMEntity(repositoryClass: ProductRepository::class)] #[ORMTable(name: 'product')] class Product { #[ORMId] #[ORMColumn(type: UlidType::NAME, unique: true)] #[ORMGeneratedValue(strategy: 'CUSTOM')] #[ORMCustomIdGenerator(class: 'doctrine.ulid_generator')] private ?Ulid $id = null; #[ORMColumn] private ?string $name = null; #[ORMOneToMany(targetEntity: Image::class, mappedBy: 'product', cascade: [ 'persist', 'remove' ], fetch: 'EAGER')] #[ORMJoinColumn(name: 'product_id', referencedColumnName: 'id')] private Collection $images; public function __construct() { $this->images = new ArrayCollection(); } public function getId(): ?Ulid { return $this->id; } public function getUlid(): ?string { return $this->id->toRfc4122(); } public function getName(): ?string { return $this->name; } public function setName(?string $name): static { $this->name = $name; return $this; } public function getImages(): Collection { return $this->images; } }
Image
<?php namespace AppEntity; use SymfonyComponentUidUlid; use SymfonyBridgeDoctrineTypesUlidType; use DoctrineORMMapping as ORM; use AppEntityProduct; use AppRepositoryImageRepository; #[ORMEntity(repositoryClass: ImageRepository::class)] #[ORMTable(name: 'image')] class Image { #[ORMId] #[ORMColumn(type: UlidType::NAME, unique: true)] #[ORMGeneratedValue(strategy: 'CUSTOM')] #[ORMCustomIdGenerator(class: 'doctrine.ulid_generator')] private ?Ulid $id = null; #[ORMManyToOne(targetEntity: Product::class, inversedBy: 'images')] private ?Product $product = null; public function getId(): ?Ulid { return $this->id; } public function getUlid(): ?string { return $this->id->toRfc4122(); } public function getProduct(): ?Product { return $this->product; } public function setProduct(?Product $product): static { $this->product = $product; return $this; } } |
<?php namespace AppEntity; use SymfonyComponentUidUlid; use SymfonyBridgeDoctrineTypesUlidType; use DoctrineORMMapping as ORM; use AppEntityProduct; use AppRepositoryImageRepository; #[ORMEntity(repositoryClass: ImageRepository::class)] #[ORMTable(name: 'image')] class Image { #[ORMId] #[ORMColumn(type: UlidType::NAME, unique: true)] #[ORMGeneratedValue(strategy: 'CUSTOM')] #[ORMCustomIdGenerator(class: 'doctrine.ulid_generator')] private ?Ulid $id = null; #[ORMManyToOne(targetEntity: Product::class, inversedBy: 'images')] private ?Product $product = null; public function getId(): ?Ulid { return $this->id; } public function getUlid(): ?string { return $this->id->toRfc4122(); } public function getProduct(): ?Product { return $this->product; } public function setProduct(?Product $product): static { $this->product = $product; return $this; } }
Стандартная выборка из репозитория
ProductController
<?php namespace AppController; use SymfonyBundleFrameworkBundleControllerAbstractController; use SymfonyComponentHttpFoundation{ Request, Response }; use SymfonyComponentRoutingAnnotationRoute; use AppRepositoryProductRepository; class ProductController extends AbstractController { #[Route('/products', name: 'products')] public function index(ProductRepository $productRepository): Response { $output = [ 'data' => [], 'included' => [] ]; $productCollection = $productRepository->findAll(); foreach ($productCollection as $entity) { $item = [ 'id' => $entity->getUlid(), 'attributes' => [ 'name' => $entity->getName() ] ]; $images = $entity->getImages(); foreach ($images as $image) { $output['included']['images'][] = [ 'id' => $image->getUlid(), 'relationships' => [ 'product' => $entity->getUlid() ] ]; } $output['data'][] = $item; } return $this->json($output); } } |
<?php namespace AppController; use SymfonyBundleFrameworkBundleControllerAbstractController; use SymfonyComponentHttpFoundation{ Request, Response }; use SymfonyComponentRoutingAnnotationRoute; use AppRepositoryProductRepository; class ProductController extends AbstractController { #[Route('/products', name: 'products')] public function index(ProductRepository $productRepository): Response { $output = [ 'data' => [], 'included' => [] ]; $productCollection = $productRepository->findAll(); foreach ($productCollection as $entity) { $item = [ 'id' => $entity->getUlid(), 'attributes' => [ 'name' => $entity->getName() ] ]; $images = $entity->getImages(); foreach ($images as $image) { $output['included']['images'][] = [ 'id' => $image->getUlid(), 'relationships' => [ 'product' => $entity->getUlid() ] ]; } $output['data'][] = $item; } return $this->json($output); } }
При использовании SymfonyComponentUidUlid вместо первичного ключа результат выборки следующий
При этом $images пустой
Если же просто изменить первичный ключ на
#[ORMId] #[ORMGeneratedValue] #[ORMColumn] private ?int $id = null; |
#[ORMId] #[ORMGeneratedValue] #[ORMColumn] private ?int $id = null;
То все работает как надо, запрос тот же самый, но вместо SymfonyComponentUidUlid в параметрах там INT, но при этом $image полноценная коллекция из которой можно получить все данные.
Composer require
"require": { "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.11", "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.1", "symfony/console": "7.0.*", "symfony/dotenv": "7.0.*", "symfony/flex": "^2", "symfony/framework-bundle": "7.0.*", "symfony/runtime": "7.0.*", "symfony/uid": "7.0.*", "symfony/yaml": "7.0.*" } |
"require": { "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.11", "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.1", "symfony/console": "7.0.*", "symfony/dotenv": "7.0.*", "symfony/flex": "^2", "symfony/framework-bundle": "7.0.*", "symfony/runtime": "7.0.*", "symfony/uid": "7.0.*", "symfony/yaml": "7.0.*" }
Дополнительно:
Ответы:
Стоило бы посмотреть как sql запрос в итоге формируется и где происходит несоответствие форматов.
При передаче в качестве параметра запроса можно явно сконвертировать Ulid в нужный формат:
$entity->getUlid()->toBase32()
- Формируется вот так
SELECT t0.id AS id_1, t0.product_id AS product_id_2 FROM image t0 WHERE t0.product_id IN ('01HS0RHTWAVDXTPBV9VZ5W09ME', '01HS0RHTWAVDXTPBV9VZ5W09MM', '01HS0RHTWAVDXTPBV9VZ5W09MV', '01HS0RHTWAVDXTPBV9VZ5W09N3', '01HS0RHTWAVDXTPBV9VZ5W09N9', '01HS0RHTWAVDXTPBV9VZ5W09NF', '01HS0RHTWAVDXTPBV9VZ5W09NN', '01HS0RHTWAVDXTPBV9VZ5W09NY', '01HS0RHTWAVDXTPBV9VZ5W09P4', '01HS0RHTWAVDXTPBV9VZ5W09PE', '01HS0RHTWAVDXTPBV9VZ5W09PP', '01HS0RHTWAVDXTPBV9VZ5W09PZ', '01HS0RHTWAVDXTPBV9VZ5W09QA', '01HS0RHTWAVDXTPBV9VZ5W09QN', '01HS0RHTWBPNVM1R8Y8DM3F12Q', '01HS0RHTWBPNVM1R8Y8DM3F12Z', '01HS0RHTWBPNVM1R8Y8DM3F135', '01HS0RHTWBPNVM1R8Y8DM3F13C', '01HS0RHTWBPNVM1R8Y8DM3F13K', '01HS0RHTWBPNVM1R8Y8DM3F13S', '01HS0RHTWBPNVM1R8Y8DM3F13Z', '01HS0RHTWBPNVM1R8Y8DM3F14A', '01HS0RHTWBPNVM1R8Y8DM3F14N', '01HS0RHTWBPNVM1R8Y8DM3F14Y', '01HS0RHTWBPNVM1R8Y8DM3F159', '01HS0RHTWBPNVM1R8Y8DM3F15M', '01HS0RHTWBPNVM1R8Y8DM3F15T', '01HS0RHTWBPNVM1R8Y8DM3F165', '01HS0RHTWBPNVM1R8Y8DM3F16G', '01HS0RHTWBPNVM1R8Y8DM3F16V');
SELECT t0.id AS id_1, t0.product_id AS product_id_2 FROM image t0 WHERE t0.product_id IN ('01HS0RHTWAVDXTPBV9VZ5W09ME', '01HS0RHTWAVDXTPBV9VZ5W09MM', '01HS0RHTWAVDXTPBV9VZ5W09MV', '01HS0RHTWAVDXTPBV9VZ5W09N3', '01HS0RHTWAVDXTPBV9VZ5W09N9', '01HS0RHTWAVDXTPBV9VZ5W09NF', '01HS0RHTWAVDXTPBV9VZ5W09NN', '01HS0RHTWAVDXTPBV9VZ5W09NY', '01HS0RHTWAVDXTPBV9VZ5W09P4', '01HS0RHTWAVDXTPBV9VZ5W09PE', '01HS0RHTWAVDXTPBV9VZ5W09PP', '01HS0RHTWAVDXTPBV9VZ5W09PZ', '01HS0RHTWAVDXTPBV9VZ5W09QA', '01HS0RHTWAVDXTPBV9VZ5W09QN', '01HS0RHTWBPNVM1R8Y8DM3F12Q', '01HS0RHTWBPNVM1R8Y8DM3F12Z', '01HS0RHTWBPNVM1R8Y8DM3F135', '01HS0RHTWBPNVM1R8Y8DM3F13C', '01HS0RHTWBPNVM1R8Y8DM3F13K', '01HS0RHTWBPNVM1R8Y8DM3F13S', '01HS0RHTWBPNVM1R8Y8DM3F13Z', '01HS0RHTWBPNVM1R8Y8DM3F14A', '01HS0RHTWBPNVM1R8Y8DM3F14N', '01HS0RHTWBPNVM1R8Y8DM3F14Y', '01HS0RHTWBPNVM1R8Y8DM3F159', '01HS0RHTWBPNVM1R8Y8DM3F15M', '01HS0RHTWBPNVM1R8Y8DM3F15T', '01HS0RHTWBPNVM1R8Y8DM3F165', '01HS0RHTWBPNVM1R8Y8DM3F16G', '01HS0RHTWBPNVM1R8Y8DM3F16V');
а надо чтобы формировался вот так
SELECT t0.id AS id_1, t0.product_id AS product_id_2 FROM image t0 WHERE t0.product_id IN (0x018E4188EB8ADB7BAB2F69DFCBC0268E, 0x018E4188EB8ADB7BAB2F69DFCBC02694, 0x018E4188EB8ADB7BAB2F69DFCBC0269B, 0x018E4188EB8ADB7BAB2F69DFCBC026A3, 0x018E4188EB8ADB7BAB2F69DFCBC026A9, 0x018E4188EB8ADB7BAB2F69DFCBC026AF, 0x018E4188EB8ADB7BAB2F69DFCBC026B5, 0x018E4188EB8ADB7BAB2F69DFCBC026BE, 0x018E4188EB8ADB7BAB2F69DFCBC026C4, 0x018E4188EB8ADB7BAB2F69DFCBC026CE, 0x018E4188EB8ADB7BAB2F69DFCBC026D6, 0x018E4188EB8ADB7BAB2F69DFCBC026DF, 0x018E4188EB8ADB7BAB2F69DFCBC026EA, 0x018E4188EB8ADB7BAB2F69DFCBC026F5, 0x018E4188EB8BB57740E11E4368378457, 0x018E4188EB8BB57740E11E436837845F, 0x018E4188EB8BB57740E11E4368378465, 0x018E4188EB8BB57740E11E436837846C, 0x018E4188EB8BB57740E11E4368378473, 0x018E4188EB8BB57740E11E4368378479, 0x018E4188EB8BB57740E11E436837847F, 0x018E4188EB8BB57740E11E436837848A, 0x018E4188EB8BB57740E11E4368378495, 0x018E4188EB8BB57740E11E436837849E, 0x018E4188EB8BB57740E11E43683784A9, 0x018E4188EB8BB57740E11E43683784B4, 0x018E4188EB8BB57740E11E43683784BA, 0x018E4188EB8BB57740E11E43683784C5, 0x018E4188EB8BB57740E11E43683784D0, 0x018E4188EB8BB57740E11E43683784DB);
SELECT t0.id AS id_1, t0.product_id AS product_id_2 FROM image t0 WHERE t0.product_id IN (0x018E4188EB8ADB7BAB2F69DFCBC0268E, 0x018E4188EB8ADB7BAB2F69DFCBC02694, 0x018E4188EB8ADB7BAB2F69DFCBC0269B, 0x018E4188EB8ADB7BAB2F69DFCBC026A3, 0x018E4188EB8ADB7BAB2F69DFCBC026A9, 0x018E4188EB8ADB7BAB2F69DFCBC026AF, 0x018E4188EB8ADB7BAB2F69DFCBC026B5, 0x018E4188EB8ADB7BAB2F69DFCBC026BE, 0x018E4188EB8ADB7BAB2F69DFCBC026C4, 0x018E4188EB8ADB7BAB2F69DFCBC026CE, 0x018E4188EB8ADB7BAB2F69DFCBC026D6, 0x018E4188EB8ADB7BAB2F69DFCBC026DF, 0x018E4188EB8ADB7BAB2F69DFCBC026EA, 0x018E4188EB8ADB7BAB2F69DFCBC026F5, 0x018E4188EB8BB57740E11E4368378457, 0x018E4188EB8BB57740E11E436837845F, 0x018E4188EB8BB57740E11E4368378465, 0x018E4188EB8BB57740E11E436837846C, 0x018E4188EB8BB57740E11E4368378473, 0x018E4188EB8BB57740E11E4368378479, 0x018E4188EB8BB57740E11E436837847F, 0x018E4188EB8BB57740E11E436837848A, 0x018E4188EB8BB57740E11E4368378495, 0x018E4188EB8BB57740E11E436837849E, 0x018E4188EB8BB57740E11E43683784A9, 0x018E4188EB8BB57740E11E43683784B4, 0x018E4188EB8BB57740E11E43683784BA, 0x018E4188EB8BB57740E11E43683784C5, 0x018E4188EB8BB57740E11E43683784D0, 0x018E4188EB8BB57740E11E43683784DB);
А все потому что видимо "по умолчанию" происходит так как я написал в ответ Виктор Кожухарь
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос

Для использования связи OneToMany с жадной загрузкой (Eager) в Symfony с ULID в качестве первичного ключа, вам нужно сделать небольшие настройки.
ULID (Universally Unique Lexicographically Sortable Identifier) - это тип уникального идентификатора, который можно использовать в качестве первичного ключа в Symfony. Однако, жадная загрузка (Eager loading) не является поддерживаемой функцией в Symfony для таких типов ключей.
Вместо этого, вам следует использовать ленивую загрузку (Lazy loading) для связи OneToMany. Для этого вам нужно убедиться, что ваша сущность настроена правильно:
```php
/**
* @ORM\Entity
*/
class ParentEntity
{
// ...
/**
* @ORM\OneToMany(targetEntity="ChildEntity", mappedBy="parent", fetch="LAZY")
*/
private $children;
// ...
}
```
Таким образом, при обращении к свойству $children сущности ParentEntity, данные будут загружаться лениво только при необходимости.
Если же вам все-таки необходимо использовать жадную загрузку с ULID, вам придется написать собственный запрос с использованием Doctrine Query Builder или DQL, чтобы явно указать условия загрузки данных. Например:
```php
$query = $entityManager->createQuery(
'SELECT p, c FROM ParentEntity p
JOIN p.children c
WHERE p.id = :parentId'
)->setParameter('parentId', $parentId);
$parent = $query->getOneOrNullResult();
```
Таким образом, вы сможете использовать жадную загрузку с ULID в Symfony, хотя это потребует дополнительных усилий по настройке запросов. Надеюсь, это поможет вам решить проблему!