Define Doctrine entity relationships (OneToMany, ManyToMany, ManyToOne); configure fetch modes, cascade operations, and orphan removal; prevent N+1 queries
/plugin marketplace add MakFly/superpowers-symfony/plugin install makfly-superpowers-symfony@MakFly/superpowers-symfonyThis skill inherits all available tools. When active, it can use any tool Claude has access to.
The most common relationship. ManyToOne is always the owning side.
<?php
// src/Entity/Post.php
#[ORM\Entity(repositoryClass: PostRepository::class)]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'posts')]
#[ORM\JoinColumn(nullable: false)]
private User $author;
// Getter and setter
public function getAuthor(): User
{
return $this->author;
}
public function setAuthor(User $author): self
{
$this->author = $author;
return $this;
}
}
// src/Entity/User.php
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
/** @var Collection<int, Post> */
#[ORM\OneToMany(targetEntity: Post::class, mappedBy: 'author', orphanRemoval: true)]
private Collection $posts;
public function __construct()
{
$this->posts = new ArrayCollection();
}
/** @return Collection<int, Post> */
public function getPosts(): Collection
{
return $this->posts;
}
public function addPost(Post $post): self
{
if (!$this->posts->contains($post)) {
$this->posts->add($post);
$post->setAuthor($this);
}
return $this;
}
public function removePost(Post $post): self
{
if ($this->posts->removeElement($post)) {
// orphanRemoval will delete the post
}
return $this;
}
}
<?php
// src/Entity/Post.php
#[ORM\Entity]
class Post
{
/** @var Collection<int, Tag> */
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'posts')]
#[ORM\JoinTable(name: 'post_tags')]
private Collection $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function addTag(Tag $tag): self
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
$tag->addPost($this); // Sync inverse side
}
return $this;
}
public function removeTag(Tag $tag): self
{
if ($this->tags->removeElement($tag)) {
$tag->removePost($this); // Sync inverse side
}
return $this;
}
}
// src/Entity/Tag.php
#[ORM\Entity]
class Tag
{
/** @var Collection<int, Post> */
#[ORM\ManyToMany(targetEntity: Post::class, mappedBy: 'posts')]
private Collection $posts;
public function __construct()
{
$this->posts = new ArrayCollection();
}
public function addPost(Post $post): self
{
if (!$this->posts->contains($post)) {
$this->posts->add($post);
}
return $this;
}
public function removePost(Post $post): self
{
$this->posts->removeElement($post);
return $this;
}
}
<?php
// src/Entity/User.php
#[ORM\Entity]
class User
{
#[ORM\OneToOne(targetEntity: Profile::class, cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true)]
private ?Profile $profile = null;
public function getProfile(): ?Profile
{
return $this->profile;
}
public function setProfile(?Profile $profile): self
{
$this->profile = $profile;
return $this;
}
}
// LAZY (default) - loads on access, may cause N+1
#[ORM\ManyToOne(fetch: 'LAZY')]
// EAGER - always loads with parent (use sparingly)
#[ORM\ManyToOne(fetch: 'EAGER')]
// EXTRA_LAZY - for large collections (count without loading)
#[ORM\OneToMany(fetch: 'EXTRA_LAZY')]
// Allows: $collection->count(), contains(), slice() without full load
#[ORM\OneToMany(
targetEntity: Comment::class,
mappedBy: 'post',
cascade: ['persist', 'remove'], // Cascade persist and remove
orphanRemoval: true // Delete orphaned entities
)]
private Collection $comments;
Cascade options:
persist: Persist child when parent is persistedremove: Remove child when parent is removedmerge, detach, refresh: Less commonly usedWarning: Avoid cascade: ['all'] - be explicit about what you cascade.
// This causes N+1 queries!
$posts = $postRepository->findAll();
foreach ($posts as $post) {
echo $post->getAuthor()->getName(); // Each iteration = 1 query
}
<?php
// src/Repository/PostRepository.php
public function findAllWithAuthors(): array
{
return $this->createQueryBuilder('p')
->addSelect('a') // Select author too
->leftJoin('p.author', 'a')
->getQuery()
->getResult();
}
// Multiple relations
public function findAllWithRelations(): array
{
return $this->createQueryBuilder('p')
->addSelect('a', 't', 'c')
->leftJoin('p.author', 'a')
->leftJoin('p.tags', 't')
->leftJoin('p.comments', 'c')
->getQuery()
->getResult();
}
$query = $em->createQuery('SELECT p FROM Post p');
$query->setFetchMode(Post::class, 'author', ClassMetadata::FETCH_EAGER);
<?php
// src/Entity/Category.php
#[ORM\Entity]
class Category
{
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
private ?Category $parent = null;
#[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')]
private Collection $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
#[ORM\Entity]
#[ORM\Index(columns: ['author_id'], name: 'idx_post_author')]
class Post
{
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'author_id', nullable: false)]
private User $author;
}
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.