From aacb2536d13e73f13fa675d0fab42351013b708e Mon Sep 17 00:00:00 2001 From: Tristan LEMARTINEL Date: Sat, 3 Jun 2023 17:26:24 +0200 Subject: [PATCH 1/2] Add cascade annotation option --- src/Doctrine/BaseRelation.php | 6 ++ src/Util/ClassSourceManipulator.php | 8 +++ tests/Util/ClassSourceManipulatorTest.php | 51 ++++++++++++++++ .../User_simple_cascade_persist.php | 53 +++++++++++++++++ .../User_simple_cascade_persist.php | 34 +++++++++++ .../User_simple_cascade_persist.php | 59 +++++++++++++++++++ 6 files changed, 211 insertions(+) create mode 100644 tests/Util/fixtures/add_many_to_many_relation/User_simple_cascade_persist.php create mode 100644 tests/Util/fixtures/add_many_to_one_relation/User_simple_cascade_persist.php create mode 100644 tests/Util/fixtures/add_one_to_many_relation/User_simple_cascade_persist.php diff --git a/src/Doctrine/BaseRelation.php b/src/Doctrine/BaseRelation.php index 5ab9a513f..6bc1e0482 100644 --- a/src/Doctrine/BaseRelation.php +++ b/src/Doctrine/BaseRelation.php @@ -28,6 +28,7 @@ public function __construct( private bool $isOwning = false, private bool $orphanRemoval = false, private bool $isNullable = false, + private array $cascade = [] ) { } @@ -85,4 +86,9 @@ public function isNullable(): bool { return $this->isNullable; } + + public function getCascade(): array + { + return $this->cascade; + } } diff --git a/src/Util/ClassSourceManipulator.php b/src/Util/ClassSourceManipulator.php index 4db3191ca..48d3376db 100644 --- a/src/Util/ClassSourceManipulator.php +++ b/src/Util/ClassSourceManipulator.php @@ -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']; } @@ -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, diff --git a/tests/Util/ClassSourceManipulatorTest.php b/tests/Util/ClassSourceManipulatorTest.php index a32d40b38..2eb030904 100644 --- a/tests/Util/ClassSourceManipulatorTest.php +++ b/tests/Util/ClassSourceManipulatorTest.php @@ -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'], + ), + ]; } /** @@ -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 } @@ -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'], + ), + ]; } /** @@ -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'], + ), + ]; } public function testAddInterface(): void diff --git a/tests/Util/fixtures/add_many_to_many_relation/User_simple_cascade_persist.php b/tests/Util/fixtures/add_many_to_many_relation/User_simple_cascade_persist.php new file mode 100644 index 000000000..c42f5c187 --- /dev/null +++ b/tests/Util/fixtures/add_many_to_many_relation/User_simple_cascade_persist.php @@ -0,0 +1,53 @@ +recipes = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @return Collection + */ + 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; + } +} diff --git a/tests/Util/fixtures/add_many_to_one_relation/User_simple_cascade_persist.php b/tests/Util/fixtures/add_many_to_one_relation/User_simple_cascade_persist.php new file mode 100644 index 000000000..7d90ec140 --- /dev/null +++ b/tests/Util/fixtures/add_many_to_one_relation/User_simple_cascade_persist.php @@ -0,0 +1,34 @@ +id; + } + + public function getCategory(): ?Category + { + return $this->category; + } + + public function setCategory(?Category $category): static + { + $this->category = $category; + + return $this; + } +} diff --git a/tests/Util/fixtures/add_one_to_many_relation/User_simple_cascade_persist.php b/tests/Util/fixtures/add_one_to_many_relation/User_simple_cascade_persist.php new file mode 100644 index 000000000..5751135b3 --- /dev/null +++ b/tests/Util/fixtures/add_one_to_many_relation/User_simple_cascade_persist.php @@ -0,0 +1,59 @@ +avatarPhotos = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + /** + * @return Collection + */ + 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; + } +} From b3b1437cedba24135738b4e5a963dea4c039c31c Mon Sep 17 00:00:00 2001 From: Tristan LEMARTINEL Date: Mon, 5 Jun 2023 13:32:27 +0200 Subject: [PATCH 2/2] tests --- composer.json | 5 ++++- phpunit.xml.dist | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 709ade911..41cb669af 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "minimum-stability": "dev", "require": { "php": ">=8.0", + "doctrine/annotations": "2.0.x-dev", "doctrine/inflector": "^2.0", "nikic/php-parser": "^4.11", "symfony/config": "^5.4.7|^6.0", @@ -24,10 +25,12 @@ "symfony/finder": "^5.4.3|^6.0", "symfony/framework-bundle": "^5.4.7|^6.0", "symfony/http-kernel": "^5.4.7|^6.0", - "symfony/process": "^5.4.7|^6.0" + "symfony/process": "^5.4.7|^6.0", + "symfony/security-bundle": "^5.4.7|^6.0" }, "require-dev": { "composer/semver": "^3.0", + "dbrekelmans/bdi": "dev-main", "doctrine/doctrine-bundle": "^2.4", "doctrine/orm": "^2.10.0", "symfony/http-client": "^5.4.7|^6.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3f9307b89..3cbf72338 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ > - +