[Help] How to create rich domain entities?
Hello,
that's not an issue, but more a request for help.
I have some "rich" domain entities (opposite of anemic entities) and I struggle to create them with foundry. Here is a simple example:
// Post.php
#[ORM\Entity]
class Post
{
#[ORM\Column]
private string $title;
#[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'post')]
private iterable $comments;
public function __construct(string $title);
public function addComment(Comment $comment): void;
public function getComments(): iterable<Comments>
}
#[ORM\Entity]
class Comment
{
#[ORM\ManyToOne(targetEntity: Event::class, inversedBy: 'slots'), ORM\JoinColumn(nullable: false)]
private Post $post;
#[ORM\Column]
private string $body;
public function __construct(Post $post, string $body);
}
Comment::$post property can never be modified in the app, thus I have zero need to add a setter on this property.
Is there a way to create a post with some comments with foundry without hacking the code?
thanks for your answer
Hey!
Rich entities is something I've been trying to build myself. I don't like entities that are constructed in an invalid state and with unnecessary setters/getters.
With your example above, my first thought is using Instantiator::alwaysForceProperties()->withoutConstructor(). Have you looked at that?
I always use this in my apps. I tend to like the idea of creating fixtures based on their database values rather than having them perfectly created via constructor/methods. I know this perhaps is controversial but this is how doctrine works when it hyrdates objects from the db.
thanks for you answer!
I think this may be the way to go, but... Comment::$post is mandatory is the db... so how could I tell foundry to hydrate it somehow?
To clarify, you are tying to do the following?
PostFactory::createOne([
'title' => 'my title',
'comments' => CommentFactory::new()->many(5),
]);
And in your CommentFactory::getDefaults() you return ['post' => PostFactory::new()]?
I've confirmed this is, I think, a bug.
I've created a reproducer to demonstrate (commit).
Basically, you cannot currently use always_force_properties with doctrine collections (it tries to set as an array). I'm thinking a fix would be having FactoryCollection return an ArrayCollection. This would be a BC break so we'd have to create a deprecation layer.
But @nikophil, can you confirm we're on the same page?
Hello @kbond
sorry, I have not seen the notification.
This is indeed my problem. But how could we be sure the original Post entity created will be the one used by CommentFactory::getDefaults()?
In your reproducer you're calling a method Comment::setPost() which does not exist
https://github.com/kbond/symfony-reproducer/commit/715ae2ee9d3e79dc4675e486d6523b10b6b160c0#diff-c1d37559aa251754682b2e972c2eef2018b93e96aa35730a6a2e73363b210fc4R52
But how could we be sure the original Post entity created will be the one used by CommentFactory::getDefaults()?
I think that might work currently but I need to confirm.
In your reproducer you're calling a method Comment::setPost() which does not exist
Ah yes, in your example in the pr description above, how does your addComment method work?
something simple such as
public function addComment(Comment $comment): self
{
if (!$this->comments->contains($comment)) {
$this->comments->add($comment);
}
return $this;
}
Ah ok, got ya
@nikophil, do you also have a removeComment() method on your Post?
This could happen, indeed
I discovered that your example in this issue description should work as expected now under the following conditions:
Post::removeComment()exists (the property accessor component requires this method to use adders)- Not using cascade: persist on
Post::$comments