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

Add cascade annotation option support in ClassSourceManipulator #1323

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Doctrine/BaseRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function __construct(
private bool $isOwning = false,
private bool $orphanRemoval = false,
private bool $isNullable = false,
private array $cascade = []
) {
}

Expand Down Expand Up @@ -85,4 +86,9 @@ public function isNullable(): bool
{
return $this->isNullable;
}

public function getCascade(): array
{
return $this->cascade;
}
}
8 changes: 8 additions & 0 deletions src/Util/ClassSourceManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ private function addSingularRelation(BaseRelation $relation): void
$annotationOptions['targetEntity'] = new ClassNameValue($typeHint, $relation->getTargetClassName());
}

if ($cascade = $relation->getCascade()) {
$annotationOptions['cascade'] = $cascade;
}

if ($relation instanceof RelationOneToOne) {
$annotationOptions['cascade'] = ['persist', 'remove'];
}
Expand Down Expand Up @@ -550,6 +554,10 @@ private function addCollectionRelation(BaseCollectionRelation $relation): void
$annotationOptions['orphanRemoval'] = true;
}

if ($cascade = $relation->getCascade()) {
$annotationOptions['cascade'] = $cascade;
}

$attributes = [
$this->buildAttributeNode(
$relation instanceof RelationManyToMany ? ManyToMany::class : OneToMany::class,
Expand Down
51 changes: 51 additions & 0 deletions tests/Util/ClassSourceManipulatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,20 @@ public function getAddManyToOneRelationTests(): \Generator
isNullable: true,
),
];

yield 'many_to_one_cascade_persist' => [
'User_simple.php',
'User_simple_cascade_persist.php',
new RelationManyToOne(
propertyName: 'category',
targetClassName: \App\Entity\Category::class,
targetPropertyName: 'foods',
mapInverseRelation: false,
isOwning: true,
isNullable: true,
cascade: ['persist'],
),
];
}

/**
Expand Down Expand Up @@ -464,6 +478,17 @@ public function getAddOneToManyRelationTests(): \Generator
),
];

yield 'one_to_many_cascade_persist' => [
'User_simple.php',
'User_simple_cascade_persist.php',
new RelationOneToMany(
propertyName: 'avatarPhotos',
targetClassName: \App\Entity\UserAvatarPhoto::class,
targetPropertyName: 'user',
cascade: ['persist'],
),
];

// todo test existing constructor
}

Expand Down Expand Up @@ -524,6 +549,19 @@ public function getAddManyToManyRelationTests(): \Generator
isOwning: true,
),
];

yield 'many_to_many_cascade_persist' => [
'User_simple.php',
'User_simple_cascade_persist.php',
new RelationManyToMany(
propertyName: 'recipes',
targetClassName: \App\Entity\Recipe::class,
targetPropertyName: 'foods',
mapInverseRelation: false,
isOwning: true,
cascade: ['persist'],
),
];
}

/**
Expand Down Expand Up @@ -643,6 +681,19 @@ public function getAddOneToOneRelationTests(): \Generator
isNullable: true,
),
];

yield 'one_to_one_cascade_persist_only' => [
'User_simple.php',
'User_simple_owning.php', // Same because ['persist', 'remove'] is forced
new RelationOneToOne(
propertyName: 'userProfile',
targetClassName: \App\Entity\UserProfile::class,
targetPropertyName: 'user',
isOwning: true,
isNullable: true,
cascade: ['persist'],
),
];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the many_to_many and one_to_one test cases. It's enough to make sure that a "singular" and "collection" relationship are tested.

}

public function testAddInterface(): void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private ?int $id = null;

#[ORM\ManyToMany(targetEntity: Recipe::class, cascade: ['persist'])]
private Collection $recipes;

public function __construct()
{
$this->recipes = new ArrayCollection();
}

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

/**
* @return Collection<int, Recipe>
*/
public function getRecipes(): Collection
{
return $this->recipes;
}

public function addRecipe(Recipe $recipe): static
{
if (!$this->recipes->contains($recipe)) {
$this->recipes->add($recipe);
}

return $this;
}

public function removeRecipe(Recipe $recipe): static
{
$this->recipes->removeElement($recipe);

return $this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private ?int $id = null;

#[ORM\ManyToOne(cascade: ['persist'])]
private ?Category $category = null;

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

public function getCategory(): ?Category
{
return $this->category;
}

public function setCategory(?Category $category): static
{
$this->category = $category;

return $this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column()]
private ?int $id = null;

#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserAvatarPhoto::class, cascade: ['persist'])]
private Collection $avatarPhotos;

public function __construct()
{
$this->avatarPhotos = new ArrayCollection();
}

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

/**
* @return Collection<int, UserAvatarPhoto>
*/
public function getAvatarPhotos(): Collection
{
return $this->avatarPhotos;
}

public function addAvatarPhoto(UserAvatarPhoto $avatarPhoto): static
{
if (!$this->avatarPhotos->contains($avatarPhoto)) {
$this->avatarPhotos->add($avatarPhoto);
$avatarPhoto->setUser($this);
}

return $this;
}

public function removeAvatarPhoto(UserAvatarPhoto $avatarPhoto): static
{
if ($this->avatarPhotos->removeElement($avatarPhoto)) {
// set the owning side to null (unless already changed)
if ($avatarPhoto->getUser() === $this) {
$avatarPhoto->setUser(null);
}
}

return $this;
}
}