Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Double call Zenstruck\Foundry\Proxy->object() generates RuntimeException('Cannot auto refresh "Entity" as there are unsaved changes. Be sure to call ->save() or disable auto refreshing') #564

Open
OleksandrConstell opened this issue Feb 23, 2024 · 0 comments

Comments

@OleksandrConstell
Copy link

OleksandrConstell commented Feb 23, 2024

Hello everyone.

I found an interesting bug with Zenstruck\Foundry\ModelFactory and Zenstruck\Foundry\Proxy.
Use:

  • zenstruck/foundry: "1.36.1"
  • "ramsey/uuid": "4.7.5",
  • "ramsey/uuid-doctrine": "2.0.0",
  • "doctrine/doctrine-bundle": "2.11.3",
  • "symfony/framework-bundle": "6.3.12"

Entity Quote and QuoteFactor

use Ramsey\Uuid\Doctrine\UuidV7Generator;
class Quote 
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['event', 'quote'])]
    private ?int $id = null;

    #[ORM\Column(type: 'uuid', unique: true, nullable: false)]
    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
    #[ORM\CustomIdGenerator(class: UuidV7Generator::class)]
    private ?string $uuid = null;

    public function getUuid(): ?string
    {
        return $this->uuid;
    }

    public function setUuid(?string $uuid): void
    {
        $this->uuid = $uuid;
    }
}

use Ramsey\Uuid\Uuid;
class QuoteFactory extends ModelFactory
{
    protected function getDefaults(): array
    {
        return ['uuid' => Uuid::uuid7()->toString()];
    }

    protected static function getClass(): string
    {
        return Quote::class;
    }
}

Unit test

class SomeTest extends BaseTestCase
{
    public function testSomething(): void
    {
        $quote = QuoteFactory::createOne();
        //to be sure that everything is up to date
        $quote->save();

        //First call - no exception
        $quote->object();

        //The second call calls the Exception
        $quote->object();
        /**
         * class Zenstruck\Foundry\Proxy::130
         * RuntimeException(Cannot auto refresh "Quote" as there are unsaved changes. 
         * Be sure to call ->save() or disable auto refreshing
         * (see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh for details)
         */
    }
}

Method $this->objectManager()->getUnitOfWork()->computeChangeSet($om->getClassMetadata($this->class), $this->object) RETURNS:

array(1) {
  ["uuid"]=>
  array(2) {
    [0]=>
    object(Ramsey\Uuid\Lazy\LazyUuidFromString)#12759 (2) {
      ["unwrapped":"Ramsey\Uuid\Lazy\LazyUuidFromString":private]=>
      NULL
      ["uuid":"Ramsey\Uuid\Lazy\LazyUuidFromString":private]=>
      string(36) "018dd661-d095-70f3-8551-7ac7bb17af35"
    }
    [1]=>
    string(36) "018dd661-d095-70f3-8551-7ac7bb17af35"
  }
}

I figured out why. Looks like after these steps the \RuntimeException appears:
FILE: https://github.com/zenstruck/foundry/blob/v1.36.1/src/Proxy.php

  1. call Proxy->object()

  2. Method "->object()" calls "->computeChangeSet()"(LINE 125) -> no changes -> CALL "$this->refresh()" (LINE 134);

  3. Method "->refresh()" calls "$this->objectManager()->refresh($this->object);" (LINE 176)

  4. Second call Proxy->object() calls "->computeChangeSet()"(LINE 125) -> some changes -> CALL "throw new \RuntimeException()" (LINE 130);

Looks like "refreshing entity" and calling "computeChangeSet" generates unsaved data and as a result exception.

Please take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant