From 90c6c5d8aa90a29ce54da50e31358a278542208c Mon Sep 17 00:00:00 2001 From: Jorrit Schippers Date: Thu, 24 Sep 2020 13:12:54 +0200 Subject: [PATCH] Prevent false deprecation error for n to m associations --- src/Controller/CRUDController.php | 33 ++++++++++++++++++++++- tests/Controller/CRUDControllerTest.php | 36 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Controller/CRUDController.php b/src/Controller/CRUDController.php index 9c0a4b2b716..b8eb17df00d 100644 --- a/src/Controller/CRUDController.php +++ b/src/Controller/CRUDController.php @@ -1554,7 +1554,13 @@ private function checkParentChildAssociation(Request $request, object $object): $propertyAccessor = PropertyAccess::createPropertyAccessor(); $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping()); - if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) { + $parentObject = $parentAdmin->getObject($parentId); + $objectParent = $propertyAccessor->getValue($object, $propertyPath); + + // $objectParent may be an array or a Collection when the parent association is many to many. + $parentObjectMatches = self::equalsOrInList($parentObject, $objectParent); + + if (!$parentObjectMatches) { // NEXT_MAJOR: make this exception @trigger_error( 'Accessing a child that isn\'t connected to a given parent is deprecated since sonata-project/admin-bundle 3.34 and won\'t be allowed in 4.0.', @@ -1563,6 +1569,31 @@ private function checkParentChildAssociation(Request $request, object $object): } } + /** + * Checks whether $parentObject is equal to $childParentValue or part of it. + * + * @param object $needle Object to compare with $haystack + * @param object|iterable $haystack Object to compare with $needle + * + * @return bool true when $haystack equals $needle or $haystack is iterable and contains $needle + */ + private static function equalsOrInList(object $needle, $haystack): bool + { + if ($needle === $haystack) { + return true; + } + + if (is_iterable($haystack)) { + foreach ($haystack as $haystackItem) { + if ($haystackItem === $needle) { + return true; + } + } + } + + return false; + } + /** * Sets the admin form theme to form view. Used for compatibility between Symfony versions. */ diff --git a/tests/Controller/CRUDControllerTest.php b/tests/Controller/CRUDControllerTest.php index b53fe3125b8..784b52eac36 100644 --- a/tests/Controller/CRUDControllerTest.php +++ b/tests/Controller/CRUDControllerTest.php @@ -1361,6 +1361,42 @@ public function testDeleteActionInvalidCsrfToken(): void } } + public function testDeleteActionChildManyToMany(): void + { + $parent = new \stdClass(); + + $child = new \stdClass(); + $child->parents = [$parent]; + + $parentAdmin = $this->createMock(PostAdmin::class); + $parentAdmin->method('getIdParameter')->willReturn('parent_id'); + + $childAdmin = $this->admin; + $childAdmin->method('getIdParameter')->willReturn('parent_id'); + + $parentAdmin->expects($this->once()) + ->method('getObject') + ->willReturn($parent); + + $childAdmin->expects($this->once()) + ->method('getObject') + ->willReturn($child); + + $childAdmin->expects($this->once()) + ->method('isChild') + ->willReturn(true); + + $childAdmin->expects($this->once()) + ->method('getParent') + ->willReturn($parentAdmin); + + $childAdmin->expects($this->exactly(2)) + ->method('getParentAssociationMapping') + ->willReturn('parents'); + + $this->controller->deleteAction(1); + } + public function testEditActionNotFoundException(): void { $this->expectException(NotFoundHttpException::class);