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

[2.0] Problem with inversed OneToOne relationships #655

Closed
simondaigre opened this issue Jun 27, 2024 · 15 comments · Fixed by #659 or #726
Closed

[2.0] Problem with inversed OneToOne relationships #655

simondaigre opened this issue Jun 27, 2024 · 15 comments · Fixed by #659 or #726
Labels
bug Something isn't working

Comments

@simondaigre
Copy link
Contributor

simondaigre commented Jun 27, 2024

Hello there,

I'm upgrading a project from 1.38 to 2.0. With the Rector rule, the upgrade is really smooth, thanks @nikophil !

I just have a minor issue, in one Factory, in the defaults() method, I'm doing something like this :

protected function defaults(): array
{
    return [
        'customer' => CustomerFactory::new()->withUser(),
    ];
}

And in Customer factory :

public function withUser(): self
{
    return $this->with(static fn (): array => ['user' => UserFactory::new()]);
}

With Foundry 1.38, my Customer was containing an User, since 2.0 User is always null.
Is it something deprecated or a bug ?

@simondaigre simondaigre changed the title [2.0] Can't use Reusable Factory "States" (->with()) in defaults() method [2.0] Can't use Reusable Factory "States" (->with()) in defaults() method Jun 27, 2024
@nikophil
Copy link
Member

Hi @simondaigre

With the Rector rule, the upgrade is really smooth

nice to hear that! 😊

You problem definitively sounds like a bug. I'm trying to reproduce it.

@nikophil
Copy link
Member

nikophil commented Jun 27, 2024

hmm I kinda made the same thing, but everything is hydrated as expected 🤔

// Object1Factory
    protected function defaults(): array|callable
    {
        return [
            'object2' => Object2Factory::new()->withSomeObject(),
        ];
    }

// Object2Factory
    public function withSomeObject(): static
    {
        return $this->with(static fn (): array => ['object3' => Object3Factory::new(['prop1' => 'toto'])]);
    }

any chance you create a public reproducer please?

@simondaigre
Copy link
Contributor Author

@nikophil
Copy link
Member

ok, I confirm that there is a bug: CustomerFactory::new()->withUser()->create(); this creates two "customers"

@nikophil nikophil changed the title [2.0] Can't use Reusable Factory "States" (->with()) in defaults() method [2.0] Problem with inversed OneToOne relationships Jun 28, 2024
@mmarton
Copy link

mmarton commented Sep 27, 2024

Any update on this one? I have the same issue.

@nikophil nikophil added the bug Something isn't working label Oct 24, 2024
@nikophil
Copy link
Member

hey @mmarton @simondaigre the bug is fied in #659
would you mind to test the PR before it gets merged?

@simondaigre
Copy link
Contributor Author

Hi @nikophil, I confirm the issue is fixed with your patch, thanks !

@mmarton
Copy link

mmarton commented Nov 22, 2024

Hi!

it created me a lot more errors than I originally had :/ I will investigate more and come back with the results

with 2.2.2: Tests: 58, Assertions: 215, Errors: 3
with dev-fix/inversed-one-to-one: Tests: 58, Assertions: 67, Errors: 34.

@nikophil
Copy link
Member

Which error does it give you?

@mmarton
Copy link

mmarton commented Nov 22, 2024

Only checked the first issue, but it looks like it broke every 1:1 relations i have:

<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
class Event
{
    #[ORM\Column(type: Types::INTEGER)]
    #[ORM\Id]
    #[ORM\GeneratedValue]
    private ?int $id = null;

    #[ORM\OneToOne(mappedBy: 'event', cascade: ['persist', 'remove'])]
    #[Assert\Valid]
    private EventLimit $eventLimit;

    public function __construct()
    {
        $this->setEventLimit(new EventLimit());
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEventLimit(): EventLimit
    {
        return $this->eventLimit;
    }

    public function setEventLimit(EventLimit $eventLimit): static
    {
        // set the owning side of the relation if necessary
        if ($eventLimit->getEvent() !== $this) {
            $eventLimit->setEvent($this);
        }

        $this->eventLimit = $eventLimit;

        return $this;
    }
}
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class EventLimit
{
    #[ORM\Column(type: Types::INTEGER)]
    #[ORM\Id]
    #[ORM\GeneratedValue]
    private ?int $id = null;

    #[ORM\OneToOne(inversedBy: 'eventLimit', cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: false)]
    private ?Event $event = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEvent(): ?Event
    {
        return $this->event;
    }

    public function setEvent(Event $event): static
    {
        $this->event = $event;

        return $this;
    }
}
<?php

declare(strict_types=1);

namespace Tests\Factory;

use App\Entity\Event;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;

/**
 * @extends PersistentProxyObjectFactory<Event>
 */
final class EventFactory extends PersistentProxyObjectFactory
{
    public static function class(): string
    {
        return Event::class;
    }

    protected function defaults(): array
    {
        return [
            'eventLimit' => EventLimitFactory::new(),
        ];
    }
}
<?php

declare(strict_types=1);

namespace Tests\Event;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Tests\Factory\EventFactory;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;

class EventdTest extends KernelTestCase
{
    use Factories;
    use ResetDatabase;

    public function testIssue(): void
    {
        $event = EventFactory::createOne();
        self::assertTrue(true);
    }
}
There was 1 error:

1) Tests\Event\EventTest
Symfony\Component\PropertyAccess\Exception\InvalidTypeException: Expected argument of type "App\Entity\EventLimit", "null" given at property path "eventLimit".

/var/www/html/vendor/symfony/property-access/PropertyAccessor.php:198
/var/www/html/vendor/symfony/property-access/PropertyAccessor.php:124
/var/www/html/vendor/zenstruck/foundry/src/Object/Hydrator.php:68
/var/www/html/vendor/zenstruck/foundry/src/Object/Instantiator.php:48
/var/www/html/vendor/zenstruck/foundry/src/ObjectFactory.php:59
/var/www/html/vendor/zenstruck/foundry/src/Persistence/PersistentObjectFactory.php:195
/var/www/html/vendor/zenstruck/foundry/src/Persistence/PersistentProxyObjectFactory.php:41
/var/www/html/vendor/zenstruck/foundry/src/Factory.php:63
/var/www/html/vendor/zenstruck/foundry/src/Persistence/PersistentProxyObjectFactory.php:50
/var/www/html/tests/Event/EventTest.php:27

Caused by
TypeError: App\Entity\Event::setEventLimit(): Argument #1 ($eventLimit) must be of type App\Entity\EventLimit, null given, called in /var/www/html/vendor/symfony/property-access/PropertyAccessor.php on line 532

@nikophil
Copy link
Member

yes thanks for this, I do see where it comes from, and I currently have no solution, have to dig a little bit more
#659 (comment)

@nikophil nikophil reopened this Nov 23, 2024
@nikophil
Copy link
Member

nikophil commented Nov 29, 2024

ok, this problem is really hard to fix... I've found a solution, that MIGHT work, but I'm not so proud of it 😅

Could you test this PR #726 🙏 @simondaigre @mmarton

(I've not managed to reproduce your problem with our fixtures, I'm gonna give another try)

thanks!

@mmarton
Copy link

mmarton commented Nov 30, 2024

Hi!

Thank you for your work.
I'm pretty busy atm, I will test it and get back with the results in a couple days and create a reproducer demo if necessary.

@simondaigre
Copy link
Contributor Author

@nikophil Still OK for me 👍

@nikophil
Copy link
Member

nikophil commented Dec 1, 2024

I've managed to reproduce your problem in the tests! I think you won't need to create any reproducer app 🤞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
3 participants