cascade refresh on entity that is managed but not saved yet #7102

Open
opened 2026-01-22 15:44:42 +01:00 by admin · 1 comment
Owner

Originally created by @oleg-andreyev on GitHub (Feb 6, 2023).

Bug Report

Q A
BC Break no
Version 2.12.2

Summary

  1. Having Cart and CartItem entity
  2. Using refresh to restore state of Cart/CartItem if validation failed.
  3. CartItem can be managed, but not saved into database thus does not have ID (MySQL AI) and entityIdentifiers does not have record for it,

Current behavior

TypeError
array_combine(): Argument #2 ($values) must be of type array, null given

image

How to reproduce

  1. Create Entity-A that will hold collection of Entity-B (with cascade refresh)
  2. Save A
  3. Add B to A, call persist, but not flush
  4. Call EntityManager::refresh

Expected behavior

If entity is scheduled for insert do nothing in doRefresh

Originally created by @oleg-andreyev on GitHub (Feb 6, 2023). ### Bug Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | no | Version | 2.12.2 #### Summary 1. Having Cart and CartItem entity 2. Using refresh to restore state of Cart/CartItem if validation failed. 3. CartItem can be managed, but not saved into database thus does not have ID (MySQL AI) and `entityIdentifiers` does not have record for it, #### Current behavior ``` TypeError array_combine(): Argument #2 ($values) must be of type array, null given ``` ![image](https://user-images.githubusercontent.com/1244112/216971539-0b1d6083-903c-49df-9b0e-13c08f2ee508.png) #### How to reproduce 1. Create Entity-A that will hold collection of Entity-B (with cascade refresh) 2. Save A 3. Add B to A, call persist, but not flush 4. Call EntityManager::refresh #### Expected behavior If entity is scheduled for insert do nothing in doRefresh
Author
Owner

@giosh94mhz commented on GitHub (Jan 14, 2025):

Bug report

I can reproduce this same issue.

Can be reproduced also by doing this on step 3: add B to A, don't call persist

In this case the error became the following (trace edited):

16:09:00 ERROR     [app] Import failed with exception: Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist
[
  "message" => "Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist",
  "exception" => Doctrine\ORM\ORMInvalidArgumentException^ {
    #message: "Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist"
    #code: 0
    #file: "/var/www/app/vendor/doctrine/orm/src/ORMInvalidArgumentException.php"
    #line: 0
    trace: {
      /var/www/app/vendor/doctrine/orm/src/ORMInvalidArgumentException.php:0 { …}
      /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2473 { …}
      /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2515 { …}
      /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2476 { …}
      /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2439 { …}
      /var/www/app/vendor/doctrine/orm/src/EntityManager.php:0 { …}
      /var/www/app/var/cache/prod/Container2FskmRl/proxy-classes.php:464 {
        Container2FskmRl\EntityManager_9a5be93->refresh($entity, ?int $lockMode = null)^
        ›     $this->initializer05636 && ($this->initializer05636->__invoke($valueHolder8c896, $this, 'refresh', array('entity' => $entity, 'lockMode' => $lockMode), $this->initializer05636) || 1) && $this->valueHolder8c896 = $valueHolder8c896;
        ›     return $this->valueHolder8c896->refresh($entity, $lockMode);
        › }
        arguments: {
          $entity: App\User {#1 …}
          $lockMode: null
        }
      }
      /var/www/app/src/Command/ImportCommand.php:158 { …}
      /var/www/app/vendor/symfony/console/Command/Command.php:298 { …}
      /var/www/app/vendor/symfony/console/Application.php:1058 { …}
      /var/www/app/vendor/symfony/framework-bundle/Console/Application.php:96 { …}
      /var/www/app/vendor/symfony/console/Application.php:301 { …}
      /var/www/app/vendor/symfony/framework-bundle/Console/Application.php:82 { …}
      /var/www/app/vendor/symfony/console/Application.php:171 { …}
      /var/www/app/bin/console:0 { …}
    }
  }
]

Hint for a solution (?)

In my opinion, the issue is at https://github.com/doctrine/orm/blob/3.4.x/src/UnitOfWork.php#L2042, since the code is:


    private function cascadeRefresh(object $entity, array &$visited, LockMode|int|null $lockMode = null): void
    {
        $class = $this->em->getClassMetadata($entity::class);

        $associationMappings = array_filter(
            $class->associationMappings,
            static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRefresh()
        );

        foreach ($associationMappings as $assoc) {
            $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity);

            switch (true) {
                case $relatedEntities instanceof PersistentCollection:
                    // Unwrap so that foreach() does not initialize
                    $relatedEntities = $relatedEntities->unwrap();
                    // break; is commented intentionally!

                case $relatedEntities instanceof Collection:
                case is_array($relatedEntities):
                    foreach ($relatedEntities as $relatedEntity) {
/* FIXME:               if (really a persistent entity) { */
                            $this->doRefresh($relatedEntity, $visited, $lockMode);
/* FIXME:               } else { do nothing on this entity, just refresh the PersistentCollection; } */
                    }

                    break;

                case $relatedEntities !== null:
                    $this->doRefresh($relatedEntities, $visited, $lockMode);
                    break;

                default:
                    // Do nothing
            }
        }
    }

@giosh94mhz commented on GitHub (Jan 14, 2025): ## Bug report I can reproduce this same issue. Can be reproduced also by doing this on step 3: add B to A, *don't* call persist In this case the error became the following (trace edited): ``` 16:09:00 ERROR [app] Import failed with exception: Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist [ "message" => "Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist", "exception" => Doctrine\ORM\ORMInvalidArgumentException^ { #message: "Entity App\UserEmail@3616 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist" #code: 0 #file: "/var/www/app/vendor/doctrine/orm/src/ORMInvalidArgumentException.php" #line: 0 trace: { /var/www/app/vendor/doctrine/orm/src/ORMInvalidArgumentException.php:0 { …} /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2473 { …} /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2515 { …} /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2476 { …} /var/www/app/vendor/doctrine/orm/src/UnitOfWork.php:2439 { …} /var/www/app/vendor/doctrine/orm/src/EntityManager.php:0 { …} /var/www/app/var/cache/prod/Container2FskmRl/proxy-classes.php:464 { Container2FskmRl\EntityManager_9a5be93->refresh($entity, ?int $lockMode = null)^ › $this->initializer05636 && ($this->initializer05636->__invoke($valueHolder8c896, $this, 'refresh', array('entity' => $entity, 'lockMode' => $lockMode), $this->initializer05636) || 1) && $this->valueHolder8c896 = $valueHolder8c896; › return $this->valueHolder8c896->refresh($entity, $lockMode); › } arguments: { $entity: App\User {#1 …} $lockMode: null } } /var/www/app/src/Command/ImportCommand.php:158 { …} /var/www/app/vendor/symfony/console/Command/Command.php:298 { …} /var/www/app/vendor/symfony/console/Application.php:1058 { …} /var/www/app/vendor/symfony/framework-bundle/Console/Application.php:96 { …} /var/www/app/vendor/symfony/console/Application.php:301 { …} /var/www/app/vendor/symfony/framework-bundle/Console/Application.php:82 { …} /var/www/app/vendor/symfony/console/Application.php:171 { …} /var/www/app/bin/console:0 { …} } } ] ``` ## Hint for a solution (?) In my opinion, the issue is at https://github.com/doctrine/orm/blob/3.4.x/src/UnitOfWork.php#L2042, since the code is: ```php private function cascadeRefresh(object $entity, array &$visited, LockMode|int|null $lockMode = null): void { $class = $this->em->getClassMetadata($entity::class); $associationMappings = array_filter( $class->associationMappings, static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRefresh() ); foreach ($associationMappings as $assoc) { $relatedEntities = $class->reflFields[$assoc->fieldName]->getValue($entity); switch (true) { case $relatedEntities instanceof PersistentCollection: // Unwrap so that foreach() does not initialize $relatedEntities = $relatedEntities->unwrap(); // break; is commented intentionally! case $relatedEntities instanceof Collection: case is_array($relatedEntities): foreach ($relatedEntities as $relatedEntity) { /* FIXME: if (really a persistent entity) { */ $this->doRefresh($relatedEntity, $visited, $lockMode); /* FIXME: } else { do nothing on this entity, just refresh the PersistentCollection; } */ } break; case $relatedEntities !== null: $this->doRefresh($relatedEntities, $visited, $lockMode); break; default: // Do nothing } } } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7102