From c50c60a3301e2d82dcfce895e4d289b8a51e2045 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 26 Jun 2020 10:55:22 +0200 Subject: [PATCH] Improve the way we append parent object if any --- UPGRADE-3.x.md | 15 + src/Admin/AbstractAdmin.php | 56 ++-- src/Admin/AdminHelper.php | 66 ++--- src/Form/Type/AdminType.php | 22 +- src/Manipulator/ObjectManipulator.php | 145 ++++++++++ tests/Admin/AdminHelperTest.php | 44 ++- tests/Admin/AdminTest.php | 161 ++++++----- tests/Fixtures/Admin/PostCategoryAdmin.php | 28 ++ tests/Fixtures/Admin/TagAdmin.php | 2 +- tests/Fixtures/Bundle/Entity/Post.php | 38 ++- tests/Fixtures/Bundle/Entity/PostCategory.php | 47 ++++ tests/Fixtures/Bundle/Entity/Tag.php | 27 +- tests/Fixtures/Entity/Foo.php | 1 + tests/Form/Type/AdminTypeTest.php | 24 +- tests/Form/Type/LegacyAdminTypeTest.php | 262 ------------------ tests/Manipulator/ObjectManipulatorTest.php | 107 +++++++ 16 files changed, 563 insertions(+), 482 deletions(-) create mode 100644 src/Manipulator/ObjectManipulator.php create mode 100644 tests/Fixtures/Admin/PostCategoryAdmin.php create mode 100644 tests/Fixtures/Bundle/Entity/PostCategory.php delete mode 100644 tests/Form/Type/LegacyAdminTypeTest.php create mode 100644 tests/Manipulator/ObjectManipulatorTest.php diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index 81c854e1e1..21a47747d7 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -1,6 +1,21 @@ UPGRADE 3.x =========== +## Deprecated `SonataAdminBundle\Admin\AdminHelper::addNewInstance()` + +Use +``` +$instance = $fieldDescription->getAssociationAdmin()->getNewInstance(); +SonataAdminBundle\Admin\AdminHelper::addInstance($object, $fieldDescription, $instance); +``` +Instead of +``` +$this->adminHelper->addNewInstance($object, $fieldDescription); +``` + +The static method `addInstance()` avoids the need to inject the admin helper dependency, +and adds more flexibility with the instance you're adding to the object. + UPGRADE FROM 3.68 to 3.69 ========================= diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index aadd6f2f35..173098d142 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -29,6 +29,7 @@ use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Form\Type\ModelHiddenType; +use Sonata\AdminBundle\Manipulator\ObjectManipulator; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\AdminBundle\Object\Metadata; use Sonata\AdminBundle\Route\RouteCollection; @@ -1365,6 +1366,9 @@ public function getTemplate($name) public function getNewInstance() { $object = $this->getModelManager()->getModelInstance($this->getClass()); + + $this->appendParentObject($object); + foreach ($this->getExtensions() as $extension) { $extension->alterNewInstance($this, $object); } @@ -3382,26 +3386,6 @@ protected function buildForm() $this->loaded['form'] = true; - // append parent object if any - // todo : clean the way the Admin class can retrieve set the object - if ($this->isChild() && $this->getParentAssociationMapping()) { - $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter())); - - $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor(); - $propertyPath = new PropertyPath($this->getParentAssociationMapping()); - - $object = $this->getSubject(); - - $value = $propertyAccessor->getValue($object, $propertyPath); - - if (\is_array($value) || $value instanceof \ArrayAccess) { - $value[] = $parent; - $propertyAccessor->setValue($object, $propertyPath, $value); - } else { - $propertyAccessor->setValue($object, $propertyPath, $parent); - } - } - $formBuilder = $this->getFormBuilder(); $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { $this->preValidate($event->getData()); @@ -3531,6 +3515,38 @@ protected function configureDefaultSortValues(array &$sortValues) { } + /** + * Set the parent object, if any, to the provided object. + */ + final protected function appendParentObject(object $object): void + { + if ($this->isChild() && $this->getParentAssociationMapping()) { + $parentAdmin = $this->getParent(); + $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter())); + + if (null !== $parentObject) { + $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor(); + $propertyPath = new PropertyPath($this->getParentAssociationMapping()); + + $value = $propertyAccessor->getValue($object, $propertyPath); + + if (\is_array($value) || $value instanceof \ArrayAccess) { + $value[] = $parentObject; + $propertyAccessor->setValue($object, $propertyPath, $value); + } else { + $propertyAccessor->setValue($object, $propertyPath, $parentObject); + } + } + } elseif ($this->hasParentFieldDescription()) { + $parentAdmin = $this->getParentFieldDescription()->getAdmin(); + $parentObject = $parentAdmin->getObject($this->request->get($parentAdmin->getIdParameter())); + + if (null !== $parentObject) { + ObjectManipulator::setObject($object, $parentObject, $this->getParentFieldDescription()); + } + } + } + /** * Build all the related urls to the current admin. */ diff --git a/src/Admin/AdminHelper.php b/src/Admin/AdminHelper.php index d711af5f8d..fbd56e57aa 100644 --- a/src/Admin/AdminHelper.php +++ b/src/Admin/AdminHelper.php @@ -14,12 +14,12 @@ namespace Sonata\AdminBundle\Admin; use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Util\ClassUtils; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\InflectorFactory; use Doctrine\ODM\MongoDB\PersistentCollection; use Doctrine\ORM\PersistentCollection as DoctrinePersistentCollection; use Sonata\AdminBundle\Exception\NoValueException; +use Sonata\AdminBundle\Manipulator\ObjectManipulator; use Sonata\AdminBundle\Util\FormBuilderIterator; use Sonata\AdminBundle\Util\FormViewIterator; use Symfony\Component\Form\FormBuilderInterface; @@ -180,16 +180,17 @@ public function appendFormFieldElement(AdminInterface $admin, $subject, $element $objectCount = null === $value ? 0 : \count($value); $postCount = \count($data[$childFormBuilder->getName()]); + $associationAdmin = $fieldDescription->getAssociationAdmin(); + // add new elements to the subject while ($objectCount < $postCount) { // append a new instance into the object - $this->addNewInstance($form->getData(), $fieldDescription); + ObjectManipulator::addInstance($form->getData(), $associationAdmin->getNewInstance(), $fieldDescription); ++$objectCount; } - $newInstance = $this->addNewInstance($form->getData(), $fieldDescription); + $newInstance = ObjectManipulator::addInstance($form->getData(), $associationAdmin->getNewInstance(), $fieldDescription); - $associationAdmin = $fieldDescription->getAssociationAdmin(); $associationAdmin->setSubject($newInstance); } @@ -214,6 +215,10 @@ public function appendFormFieldElement(AdminInterface $admin, $subject, $element } /** + * NEXT_MAJOR: remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.x, use to be removed with 4.0. + * * Add a new instance to the related FieldDescriptionInterface value. * * @param object $object @@ -224,53 +229,16 @@ public function appendFormFieldElement(AdminInterface $admin, $subject, $element */ public function addNewInstance($object, FieldDescriptionInterface $fieldDescription) { - $instance = $fieldDescription->getAssociationAdmin()->getNewInstance(); - $mapping = $fieldDescription->getAssociationMapping(); - $parentMappings = $fieldDescription->getParentAssociationMappings(); - - $inflector = InflectorFactory::create()->build(); - - foreach ($parentMappings as $parentMapping) { - $method = sprintf('get%s', $inflector->classify($parentMapping['fieldName'])); - - if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { - /* - * NEXT_MAJOR: Use BadMethodCallException instead - */ - throw new \RuntimeException(sprintf( - 'Method %s::%s() does not exist.', - ClassUtils::getClass($object), - $method - )); - } - - $object = $object->$method(); - } - - $method = sprintf('add%s', $inflector->classify($mapping['fieldName'])); - - if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { - $method = rtrim($method, 's'); - - if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { - $method = sprintf('add%s', $inflector->classify($inflector->singularize($mapping['fieldName']))); - - if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { - /* - * NEXT_MAJOR: Use BadMethodCallException instead - */ - throw new \RuntimeException(sprintf( - 'Method %s::%s() does not exist.', - ClassUtils::getClass($object), - $method - )); - } - } - } + @trigger_error(sprintf( + 'Method %s() is deprecated since sonata-project/admin-bundle 3.x. It will be removed in version 4.0.' + .' Use %s::addInstance() instead.', + __METHOD__, + ObjectManipulator::class + ), E_USER_DEPRECATED); - $object->$method($instance); + $instance = $fieldDescription->getAssociationAdmin()->getNewInstance(); - return $instance; + return ObjectManipulator::addInstance($object, $instance, $fieldDescription); } /** diff --git a/src/Form/Type/AdminType.php b/src/Form/Type/AdminType.php index 98109d03a3..13c09daa8a 100644 --- a/src/Form/Type/AdminType.php +++ b/src/Form/Type/AdminType.php @@ -17,6 +17,7 @@ use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Form\DataTransformer\ArrayToModelTransformer; +use Sonata\AdminBundle\Manipulator\ObjectManipulator; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\FormBuilderInterface; @@ -36,22 +37,22 @@ class AdminType extends AbstractType { /** - * @var AdminHelper + * NEXT_MAJOR: Remove this property. + * + * @var AdminHelper|null */ private $adminHelper; /** - * NEXT_MAJOR: Allow only `AdminHelper` for argument 1 and remove the default null value. + * NEXT_MAJOR: Remove the __construct method. */ public function __construct(?AdminHelper $adminHelper = null) { - // NEXT_MAJOR: Remove this condition. - if (null === $adminHelper) { + if (null !== $adminHelper) { @trigger_error(sprintf( - 'Calling %s without passing an %s as argument is deprecated since sonata-project/admin-bundle 3.66' - .' and will throw an exception in 4.0.', - __METHOD__, - AdminHelper::class + 'Passing argument 1 to %s() is deprecated since sonata-project/admin-bundle 3.x' + .' and will be ignored in version 4.0.', + __METHOD__ ), E_USER_DEPRECATED); } @@ -107,10 +108,7 @@ static function (array $associationMapping): string { $subject = $p->getValue($parentSubject, $parentPath.$path); } catch (NoSuchIndexException $e) { // no object here, we create a new one - // NEXT_MAJOR: Remove the null check. - if (null !== $this->adminHelper) { - $subject = $this->adminHelper->addNewInstance($parentSubject, $parentFieldDescription); - } + $subject = ObjectManipulator::setObject($admin->getNewInstance(), $parentSubject, $parentFieldDescription); } } } diff --git a/src/Manipulator/ObjectManipulator.php b/src/Manipulator/ObjectManipulator.php new file mode 100644 index 0000000000..61fcb15541 --- /dev/null +++ b/src/Manipulator/ObjectManipulator.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Manipulator; + +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Inflector\InflectorFactory; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; + +final class ObjectManipulator +{ + /** + * Add the instance to the object provided. + * + * @phpstan-template T of object + * @phpstan-param T $instance + * @phpstan-return T + */ + public static function addInstance( + object $object, + object $instance, + FieldDescriptionInterface $parentFieldDescription + ): object { + $associationMapping = $parentFieldDescription->getAssociationMapping(); + $parentAssociationMappings = $parentFieldDescription->getParentAssociationMappings(); + + foreach ($parentAssociationMappings as $parentAssociationMapping) { + $object = self::callGetter($object, $parentAssociationMapping['fieldName']); + } + + return self::callAdder($object, $instance, $associationMapping['fieldName']); + } + + /** + * Set the object to the instance provided. + * + * @phpstan-template T of object + * @phpstan-param T $instance + * @phpstan-return T + */ + public static function setObject( + object $instance, + object $object, + FieldDescriptionInterface $parentFieldDescription + ): object { + $associationMapping = $parentFieldDescription->getAssociationMapping(); + $parentAssociationMappings = $parentFieldDescription->getParentAssociationMappings(); + + foreach ($parentAssociationMappings as $parentAssociationMapping) { + $object = self::callGetter($object, $parentAssociationMapping['fieldName']); + } + + return self::callSetter($instance, $object, $associationMapping['mappedBy']); + } + + /** + * Call $object->getXXX(). + */ + private static function callGetter(object $object, string $fieldName): object + { + $inflector = InflectorFactory::create()->build(); + $method = sprintf('get%s', $inflector->classify($fieldName)); + + if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { + /* + * NEXT_MAJOR: Use BadMethodCallException instead + */ + throw new \RuntimeException( + sprintf('Method %s::%s() does not exist.', ClassUtils::getClass($object), $method) + ); + } + + return $object->$method(); + } + + /** + * Call $instance->setXXX($object). + * + * @phpstan-template T of object + * @phpstan-param T $instance + * @phpstan-return T + */ + private static function callSetter(object $instance, object $object, string $mappedBy): object + { + $inflector = InflectorFactory::create()->build(); + $method = sprintf('set%s', $inflector->classify($mappedBy)); + + if (!(\is_callable([$instance, $method]) && method_exists($instance, $method))) { + /* + * NEXT_MAJOR: Use BadMethodCallException instead + */ + throw new \RuntimeException( + sprintf('Method %s::%s() does not exist.', ClassUtils::getClass($instance), $method) + ); + } + + $instance->$method($object); + + return $instance; + } + + /** + * Call $object->addXXX($instance). + * + * @phpstan-template T of object + * @phpstan-param T $instance + * @phpstan-return T + */ + private static function callAdder(object $object, object $instance, string $fieldName): object + { + $inflector = InflectorFactory::create()->build(); + $method = sprintf('add%s', $inflector->classify($fieldName)); + + if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { + $method = rtrim($method, 's'); + + if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { + $method = sprintf('add%s', $inflector->classify($inflector->singularize($fieldName))); + + if (!(\is_callable([$object, $method]) && method_exists($object, $method))) { + /* + * NEXT_MAJOR: Use BadMethodCallException instead + */ + throw new \RuntimeException( + sprintf('Method %s::%s() does not exist.', ClassUtils::getClass($object), $method) + ); + } + } + } + + $object->$method($instance); + + return $instance; + } +} diff --git a/tests/Admin/AdminHelperTest.php b/tests/Admin/AdminHelperTest.php index de277352da..d76253aff9 100644 --- a/tests/Admin/AdminHelperTest.php +++ b/tests/Admin/AdminHelperTest.php @@ -72,6 +72,13 @@ public function testGetChildFormView(): void $this->assertInstanceOf(FormView::class, $this->helper->getChildFormView($formView, 'test_elementId')); } + /** + * NEXT_MAJOR: Remove this test. + * + * @group legacy + * + * @expectedDeprecation Method Sonata\AdminBundle\Admin\AdminHelper::addNewInstance() is deprecated since sonata-project/admin-bundle 3.x. It will be removed in version 4.0. Use Sonata\AdminBundle\Manipulator\ObjectManipulator::addInstance() instead. + */ public function testAddNewInstance(): void { $admin = $this->createMock(AdminInterface::class); @@ -90,29 +97,13 @@ public function testAddNewInstance(): void $this->helper->addNewInstance($object, $fieldDescription); } - public function testAddNewInstanceWithParentAssociation(): void - { - $admin = $this->createMock(AdminInterface::class); - $admin->expects($this->once())->method('getNewInstance')->willReturn(new \stdClass()); - - $fieldDescription = $this->createMock(FieldDescriptionInterface::class); - $fieldDescription->expects($this->once())->method('getAssociationAdmin')->willReturn($admin); - $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['fieldName' => 'fooBar']); - $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([['fieldName' => 'parent']]); - - $object2 = $this->getMockBuilder(\stdClass::class) - ->setMethods(['addFooBar']) - ->getMock(); - $object2->expects($this->once())->method('addFooBar'); - - $object1 = $this->getMockBuilder(\stdClass::class) - ->setMethods(['getParent']) - ->getMock(); - $object1->expects($this->once())->method('getParent')->willReturn($object2); - - $this->helper->addNewInstance($object1, $fieldDescription); - } - + /** + * NEXT_MAJOR: Remove this test. + * + * @group legacy + * + * @expectedDeprecation Method Sonata\AdminBundle\Admin\AdminHelper::addNewInstance() is deprecated since sonata-project/admin-bundle 3.x. It will be removed in version 4.0. Use Sonata\AdminBundle\Manipulator\ObjectManipulator::addInstance() instead. + */ public function testAddNewInstancePlural(): void { $admin = $this->createMock(AdminInterface::class); @@ -131,6 +122,13 @@ public function testAddNewInstancePlural(): void $this->helper->addNewInstance($object, $fieldDescription); } + /** + * NEXT_MAJOR: Remove this test. + * + * @group legacy + * + * @expectedDeprecation Method Sonata\AdminBundle\Admin\AdminHelper::addNewInstance() is deprecated since sonata-project/admin-bundle 3.x. It will be removed in version 4.0. Use Sonata\AdminBundle\Manipulator\ObjectManipulator::addInstance() instead. + */ public function testAddNewInstanceInflector(): void { $admin = $this->createMock(AdminInterface::class); diff --git a/tests/Admin/AdminTest.php b/tests/Admin/AdminTest.php index ba197cded9..b66296d0ac 100644 --- a/tests/Admin/AdminTest.php +++ b/tests/Admin/AdminTest.php @@ -55,11 +55,13 @@ use Sonata\AdminBundle\Tests\Fixtures\Admin\FilteredAdmin; use Sonata\AdminBundle\Tests\Fixtures\Admin\ModelAdmin; use Sonata\AdminBundle\Tests\Fixtures\Admin\PostAdmin; +use Sonata\AdminBundle\Tests\Fixtures\Admin\PostCategoryAdmin; use Sonata\AdminBundle\Tests\Fixtures\Admin\PostWithCustomRouteAdmin; use Sonata\AdminBundle\Tests\Fixtures\Admin\TagAdmin; use Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity\BlogPost; use Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity\Comment; use Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity\Post; +use Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity\PostCategory; use Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity\Tag; use Sonata\AdminBundle\Tests\Fixtures\Entity\FooToString; use Sonata\AdminBundle\Tests\Fixtures\Entity\FooToStringNull; @@ -1576,57 +1578,116 @@ public function testGetPersistentParametersWithValidExtension(): void $this->assertSame($expected, $admin->getPersistentParameters()); } - public function testGetFormWithNonCollectionParentValue(): void + public function testGetNewInstanceForChildAdminWithParentValue(): void { $post = new Post(); - $tagAdmin = $this->createTagAdmin($post); - $tag = $tagAdmin->getSubject(); - $tag->setPosts(null); - $tagAdmin->getForm(); - $this->assertSame($post, $tag->getPosts()); + $postAdmin = $this->getMockBuilder(PostAdmin::class)->disableOriginalConstructor()->getMock(); + $postAdmin->method('getObject')->willReturn($post); + + $formBuilder = $this->createStub(FormBuilderInterface::class); + $formBuilder->method('getForm')->willReturn(null); + + $tag = new Tag(); + + $modelManager = $this->createStub(ModelManagerInterface::class); + $modelManager->method('getModelInstance')->willReturn($tag); + + $tagAdmin = new TagAdmin('admin.tag', Tag::class, 'MyBundle\MyController'); + $tagAdmin->setModelManager($modelManager); + $tagAdmin->setParent($postAdmin); + + $request = $this->createStub(Request::class); + $tagAdmin->setRequest($request); + + $configurationPool = $this->getMockBuilder(Pool::class) + ->disableOriginalConstructor() + ->getMock(); + + $configurationPool->method('getPropertyAccessor')->willReturn(PropertyAccess::createPropertyAccessor()); + + $tagAdmin->setConfigurationPool($configurationPool); + + $tag = $tagAdmin->getNewInstance(); + + $this->assertSame($post, $tag->getPost()); } - public function testGetFormWithCollectionParentValue(): void + public function testGetNewInstanceForChildAdminWithCollectionParentValue(): void { $post = new Post(); - $tagAdmin = $this->createTagAdmin($post); - $tag = $tagAdmin->getSubject(); - // Case of a doctrine collection - $this->assertInstanceOf(Collection::class, $tag->getPosts()); - $this->assertCount(0, $tag->getPosts()); + $postAdmin = $this->getMockBuilder(PostAdmin::class)->disableOriginalConstructor()->getMock(); + $postAdmin->method('getObject')->willReturn($post); + + $formBuilder = $this->createStub(FormBuilderInterface::class); + $formBuilder->method('getForm')->willReturn(null); + + $postCategory = new PostCategory(); - $tag->addPost(new Post()); + $modelManager = $this->createStub(ModelManagerInterface::class); + $modelManager->method('getModelInstance')->willReturn($postCategory); + + $postCategoryAdmin = new PostCategoryAdmin('admin.post_category', PostCategoryAdmin::class, 'MyBundle\MyController'); + $postCategoryAdmin->setModelManager($modelManager); + $postCategoryAdmin->setParent($postAdmin); - $this->assertCount(1, $tag->getPosts()); + $request = $this->createStub(Request::class); + $postCategoryAdmin->setRequest($request); - $tagAdmin->getForm(); + $configurationPool = $this->getMockBuilder(Pool::class) + ->disableOriginalConstructor() + ->getMock(); - $this->assertInstanceOf(Collection::class, $tag->getPosts()); - $this->assertCount(2, $tag->getPosts()); - $this->assertContains($post, $tag->getPosts()); + $configurationPool->method('getPropertyAccessor')->willReturn(PropertyAccess::createPropertyAccessor()); + + $postCategoryAdmin->setConfigurationPool($configurationPool); + + $postCategory = $postCategoryAdmin->getNewInstance(); + + $this->assertInstanceOf(Collection::class, $postCategory->getPosts()); + $this->assertCount(1, $postCategory->getPosts()); + $this->assertContains($post, $postCategory->getPosts()); } - public function testGetFormWithArrayParentValue(): void + public function testGetNewInstanceForEmbededAdminWithParentValue(): void { $post = new Post(); - $tagAdmin = $this->createTagAdmin($post); - $tag = $tagAdmin->getSubject(); - // Case of an array - $tag->setPosts([]); - $this->assertCount(0, $tag->getPosts()); + $postAdmin = $this->getMockBuilder(PostAdmin::class)->disableOriginalConstructor()->getMock(); + $postAdmin->method('getObject')->willReturn($post); + + $formBuilder = $this->createStub(FormBuilderInterface::class); + $formBuilder->method('getForm')->willReturn(null); + + $parentField = $this->createStub(FieldDescriptionInterface::class); + $parentField->method('getAdmin')->willReturn($postAdmin); + $parentField->method('getParentAssociationMappings')->willReturn([]); + $parentField->method('getAssociationMapping')->willReturn(['fieldName' => 'tag', 'mappedBy' => 'post']); + + $tag = new Tag(); + + $modelManager = $this->createStub(ModelManagerInterface::class); + $modelManager->method('getModelInstance')->willReturn($tag); + + $tagAdmin = new TagAdmin('admin.tag', Tag::class, 'MyBundle\MyController'); + $tagAdmin->setModelManager($modelManager); + $tagAdmin->setParentFieldDescription($parentField); + + $request = $this->createStub(Request::class); + $tagAdmin->setRequest($request); + + $configurationPool = $this->getMockBuilder(Pool::class) + ->disableOriginalConstructor() + ->getMock(); - $tag->addPost(new Post()); + $configurationPool->method('getPropertyAccessor')->willReturn(PropertyAccess::createPropertyAccessor()); - $this->assertCount(1, $tag->getPosts()); + $tagAdmin->setConfigurationPool($configurationPool); - $tagAdmin->getForm(); + $tag = $tagAdmin->getNewInstance(); - $this->assertIsArray($tag->getPosts()); - $this->assertCount(2, $tag->getPosts()); - $this->assertContains($post, $tag->getPosts()); + $this->assertSame($post, $tag->getPost()); } public function testFormAddPostSubmitEventForPreValidation(): void @@ -2620,44 +2681,4 @@ public function getDeprecatedAbstractAdminConstructorArgs(): iterable [1, 1, 1], ]; } - - private function createTagAdmin(Post $post): TagAdmin - { - $postAdmin = $this->getMockBuilder(PostAdmin::class) - ->disableOriginalConstructor() - ->getMock(); - - $postAdmin->method('getObject')->willReturn($post); - - $formBuilder = $this->createMock(FormBuilderInterface::class); - $formBuilder->method('getForm')->willReturn(null); - - $tagAdmin = $this->getMockBuilder(TagAdmin::class) - ->setConstructorArgs([ - 'admin.tag', - Tag::class, - 'MyBundle\MyController', - ]) - ->setMethods(['getFormBuilder']) - ->getMock(); - - $tagAdmin->method('getFormBuilder')->willReturn($formBuilder); - $tagAdmin->setParent($postAdmin); - - $tag = new Tag(); - $tagAdmin->setSubject($tag); - - $request = $this->createMock(Request::class); - $tagAdmin->setRequest($request); - - $configurationPool = $this->getMockBuilder(Pool::class) - ->disableOriginalConstructor() - ->getMock(); - - $configurationPool->method('getPropertyAccessor')->willReturn(PropertyAccess::createPropertyAccessor()); - - $tagAdmin->setConfigurationPool($configurationPool); - - return $tagAdmin; - } } diff --git a/tests/Fixtures/Admin/PostCategoryAdmin.php b/tests/Fixtures/Admin/PostCategoryAdmin.php new file mode 100644 index 0000000000..65da04564f --- /dev/null +++ b/tests/Fixtures/Admin/PostCategoryAdmin.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\Fixtures\Admin; + +use Sonata\AdminBundle\Admin\AbstractAdmin; + +class PostCategoryAdmin extends AbstractAdmin +{ + public function getParentAssociationMapping() + { + if ($this->getParent() instanceof PostAdmin) { + return 'posts'; + } + + return null; + } +} diff --git a/tests/Fixtures/Admin/TagAdmin.php b/tests/Fixtures/Admin/TagAdmin.php index 83465a72d1..2e3db3f577 100644 --- a/tests/Fixtures/Admin/TagAdmin.php +++ b/tests/Fixtures/Admin/TagAdmin.php @@ -20,7 +20,7 @@ class TagAdmin extends AbstractAdmin public function getParentAssociationMapping() { if ($this->getParent() instanceof PostAdmin) { - return 'posts'; + return 'post'; } return null; diff --git a/tests/Fixtures/Bundle/Entity/Post.php b/tests/Fixtures/Bundle/Entity/Post.php index f6cfc9ccc3..2ca412d564 100644 --- a/tests/Fixtures/Bundle/Entity/Post.php +++ b/tests/Fixtures/Bundle/Entity/Post.php @@ -14,35 +14,61 @@ namespace Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; class Post { private $tags; + private $postCategories; + public function __construct() { $this->tags = new ArrayCollection(); + $this->postCategories = new ArrayCollection(); } - public function setTags($tags): void + public function setTags(Collection $tags): void { $this->tags = $tags; } - public function getTags() + public function getTags(): Collection { return $this->tags; } public function addTag(Tag $tag): void { - $tag->addPost($this); - $this->tags[] = ($tag); + $tag->setPost($this); + $this->tags->add($tag); } - public function removePost(Tag $tag): void + public function removeTag(Tag $tag): void { - $tag->removePost($this); + $tag->setPost(null); $this->tags->removeElement($tag); } + + public function setPostCategories(Collection $postCategories): void + { + $this->postCategories = $postCategories; + } + + public function getPostCategories(): Collection + { + return $this->postCategories; + } + + public function addPostCategory(PostCategory $postCategory): void + { + $postCategory->addPost($this); + $this->postCategories->add($postCategory); + } + + public function removePostCategory(PostCategory $postCategory): void + { + $postCategory->removePost($this); + $this->postCategories->removeElement($postCategory); + } } diff --git a/tests/Fixtures/Bundle/Entity/PostCategory.php b/tests/Fixtures/Bundle/Entity/PostCategory.php new file mode 100644 index 0000000000..fedaa749c1 --- /dev/null +++ b/tests/Fixtures/Bundle/Entity/PostCategory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; + +class PostCategory +{ + private $posts; + + public function __construct() + { + $this->posts = new ArrayCollection(); + } + + public function setPosts(Collection $posts): void + { + $this->posts = $posts; + } + + public function addPost(Post $post): void + { + $this->posts->add($post); + } + + public function removePost(Post $post): void + { + $this->posts->removeElement($post); + } + + public function getPosts(): Collection + { + return $this->posts; + } +} diff --git a/tests/Fixtures/Bundle/Entity/Tag.php b/tests/Fixtures/Bundle/Entity/Tag.php index fe1d7e526a..2b7854f81e 100644 --- a/tests/Fixtures/Bundle/Entity/Tag.php +++ b/tests/Fixtures/Bundle/Entity/Tag.php @@ -13,34 +13,17 @@ namespace Sonata\AdminBundle\Tests\Fixtures\Bundle\Entity; -use Doctrine\Common\Collections\ArrayCollection; - class Tag { - private $posts; - - public function __construct() - { - $this->posts = new ArrayCollection(); - } - - public function setPosts($posts): void - { - $this->posts = $posts; - } - - public function getPosts() - { - return $this->posts; - } + private $post; - public function addPost(Post $post): void + public function setPost(Post $post): void { - $this->posts[] = $post; + $this->post = $post; } - public function removePost(Post $post): void + public function getPost(): ?Post { - $this->posts->removeElement($post); + return $this->post; } } diff --git a/tests/Fixtures/Entity/Foo.php b/tests/Fixtures/Entity/Foo.php index 7c3f7ef345..23ffb4d610 100644 --- a/tests/Fixtures/Entity/Foo.php +++ b/tests/Fixtures/Entity/Foo.php @@ -16,6 +16,7 @@ class Foo { public $qux; + private $bar; private $baz; diff --git a/tests/Form/Type/AdminTypeTest.php b/tests/Form/Type/AdminTypeTest.php index 16950c7581..b4623adcdc 100644 --- a/tests/Form/Type/AdminTypeTest.php +++ b/tests/Form/Type/AdminTypeTest.php @@ -16,9 +16,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Prophecy\Argument; use Prophecy\Argument\Token\AnyValueToken; -use Prophecy\Prophecy\ObjectProphecy; use Sonata\AdminBundle\Admin\AbstractAdmin; -use Sonata\AdminBundle\Admin\AdminHelper; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Form\Extension\Field\Type\FormTypeFieldExtension; @@ -27,18 +25,12 @@ use Sonata\AdminBundle\Tests\Fixtures\Entity\Foo; use Sonata\AdminBundle\Tests\Fixtures\TestExtension; use Symfony\Component\Form\FormTypeGuesserInterface; -use Symfony\Component\Form\PreloadedExtension; use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; class AdminTypeTest extends TypeTestCase { - /** - * @var AdminHelper|ObjectProphecy - */ - private $adminHelper; - /** * @var AdminType */ @@ -46,8 +38,7 @@ class AdminTypeTest extends TypeTestCase protected function setUp(): void { - $this->adminHelper = $this->prophesize(AdminHelper::class); - $this->adminType = new AdminType($this->adminHelper->reveal()); + $this->adminType = new AdminType(); parent::setUp(); } @@ -198,7 +189,7 @@ public function testArrayCollection(): void public function testArrayCollectionNotFound(): void { $parentSubject = new \stdClass(); - $parentSubject->foo = new ArrayCollection(); + $parentSubject->foo = new ArrayCollection([]); $parentAdmin = $this->prophesize(AdminInterface::class); $parentAdmin->getSubject()->shouldBeCalled()->willReturn($parentSubject); @@ -206,10 +197,12 @@ public function testArrayCollectionNotFound(): void $parentField = $this->prophesize(FieldDescriptionInterface::class); $parentField->setAssociationAdmin(Argument::type(AdminInterface::class))->shouldBeCalled(); $parentField->getAdmin()->shouldBeCalled()->willReturn($parentAdmin->reveal()); + $parentField->getParentAssociationMappings()->willReturn([]); + $parentField->getAssociationMapping()->willReturn(['fieldName' => 'foo', 'mappedBy' => 'bar']); $modelManager = $this->prophesize(ModelManagerInterface::class); - $foo = new Foo(); + $newInstance = new Foo(); $admin = $this->prophesize(AbstractAdmin::class); $admin->hasParentFieldDescription()->shouldBeCalled()->willReturn(true); @@ -217,9 +210,8 @@ public function testArrayCollectionNotFound(): void $admin->defineFormBuilder(new AnyValueToken())->shouldBeCalled(); $admin->getModelManager()->shouldBeCalled()->willReturn($modelManager); $admin->getClass()->shouldBeCalled()->willReturn(Foo::class); - $admin->setSubject($foo)->shouldBeCalled(); - - $this->adminHelper->addNewInstance($parentSubject, $parentField->reveal())->shouldBeCalled()->willReturn($foo); + $admin->setSubject($newInstance)->shouldBeCalled(); + $admin->getNewInstance()->shouldBeCalled()->willReturn($newInstance); $field = $this->prophesize(FieldDescriptionInterface::class); $field->getAssociationAdmin()->shouldBeCalled()->willReturn($admin->reveal()); @@ -249,8 +241,6 @@ protected function getExtensions() $extension->addTypeExtension(new FormTypeFieldExtension([], [])); $extensions[] = $extension; - $extensions[] = new PreloadedExtension([$this->adminType], []); - return $extensions; } } diff --git a/tests/Form/Type/LegacyAdminTypeTest.php b/tests/Form/Type/LegacyAdminTypeTest.php deleted file mode 100644 index c52a435240..0000000000 --- a/tests/Form/Type/LegacyAdminTypeTest.php +++ /dev/null @@ -1,262 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Sonata\AdminBundle\Tests\Form\Type; - -use Doctrine\Common\Collections\ArrayCollection; -use Prophecy\Argument; -use Prophecy\Argument\Token\AnyValueToken; -use Sonata\AdminBundle\Admin\AbstractAdmin; -use Sonata\AdminBundle\Admin\AdminInterface; -use Sonata\AdminBundle\Admin\FieldDescriptionInterface; -use Sonata\AdminBundle\Form\Extension\Field\Type\FormTypeFieldExtension; -use Sonata\AdminBundle\Form\Type\AdminType; -use Sonata\AdminBundle\Model\ModelManagerInterface; -use Sonata\AdminBundle\Tests\Fixtures\Entity\Foo; -use Sonata\AdminBundle\Tests\Fixtures\TestExtension; -use Symfony\Component\Form\FormTypeGuesserInterface; -use Symfony\Component\Form\Test\TypeTestCase; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; - -/** - * @group legacy - */ -class LegacyAdminTypeTest extends TypeTestCase -{ - /** - * @var AdminType - */ - private $adminType; - - protected function setUp(): void - { - $this->adminType = new AdminType(); - - parent::setUp(); - } - - /** - * @expectedDeprecation Calling Sonata\AdminBundle\Form\Type\AdminType::__construct without passing an Sonata\AdminBundle\Admin\AdminHelper as argument is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. - */ - public function testGetDefaultOptions(): void - { - $optionResolver = new OptionsResolver(); - - $this->adminType->configureOptions($optionResolver); - - $options = $optionResolver->resolve(); - - $this->assertTrue($options['delete']); - $this->assertFalse($options['auto_initialize']); - $this->assertSame('link_add', $options['btn_add']); - $this->assertSame('link_list', $options['btn_list']); - $this->assertSame('link_delete', $options['btn_delete']); - $this->assertSame('SonataAdminBundle', $options['btn_catalogue']); - } - - /** - * @expectedDeprecation Calling Sonata\AdminBundle\Form\Type\AdminType::__construct without passing an Sonata\AdminBundle\Admin\AdminHelper as argument is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. - */ - public function testSubmitValidData(): void - { - $parentAdmin = $this->prophesize(AdminInterface::class); - $parentAdmin->hasSubject()->shouldBeCalled()->willReturn(false); - $parentField = $this->prophesize(FieldDescriptionInterface::class); - $parentField->setAssociationAdmin(Argument::type(AdminInterface::class))->shouldBeCalled(); - $parentField->getAdmin()->shouldBeCalled()->willReturn($parentAdmin->reveal()); - - $modelManager = $this->prophesize(ModelManagerInterface::class); - - $foo = new Foo(); - - $admin = $this->prophesize(AbstractAdmin::class); - $admin->hasParentFieldDescription()->shouldBeCalled()->willReturn(true); - $admin->getParentFieldDescription()->shouldBeCalled()->willReturn($parentField->reveal()); - $admin->hasAccess('delete')->shouldBeCalled()->willReturn(false); - $admin->defineFormBuilder(new AnyValueToken())->shouldBeCalled(); - $admin->getModelManager()->shouldBeCalled()->willReturn($modelManager); - $admin->getClass()->shouldBeCalled()->willReturn(Foo::class); - $admin->getNewInstance()->shouldBeCalled()->willReturn($foo); - $admin->setSubject($foo)->shouldBeCalled(); - - $field = $this->prophesize(FieldDescriptionInterface::class); - $field->getAssociationAdmin()->shouldBeCalled()->willReturn($admin->reveal()); - $field->getAdmin()->shouldBeCalled(); - $field->getName()->shouldBeCalled(); - $field->getOption('edit', 'standard')->shouldBeCalled(); - $field->getOption('inline', 'natural')->shouldBeCalled(); - $field->getOption('block_name', false)->shouldBeCalled(); - $formData = []; - - $form = $this->factory->create( - AdminType::class, - null, - [ - 'sonata_field_description' => $field->reveal(), - ] - ); - $form->submit($formData); - $this->assertTrue($form->isSynchronized()); - } - - /** - * @expectedDeprecation Calling Sonata\AdminBundle\Form\Type\AdminType::__construct without passing an Sonata\AdminBundle\Admin\AdminHelper as argument is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. - */ - public function testDotFields(): void - { - $foo = new \stdClass(); - $foo->bar = 1; - - $parentSubject = new \stdClass(); - $parentSubject->foo = $foo; - - $parentAdmin = $this->prophesize(AdminInterface::class); - $parentAdmin->getSubject()->shouldBeCalled()->willReturn($parentSubject); - $parentAdmin->hasSubject()->shouldBeCalled()->willReturn(true); - $parentField = $this->prophesize(FieldDescriptionInterface::class); - $parentField->setAssociationAdmin(Argument::type(AdminInterface::class))->shouldBeCalled(); - $parentField->getAdmin()->shouldBeCalled()->willReturn($parentAdmin->reveal()); - - $modelManager = $this->prophesize(ModelManagerInterface::class); - - $admin = $this->prophesize(AbstractAdmin::class); - $admin->hasParentFieldDescription()->shouldBeCalled()->willReturn(true); - $admin->getParentFieldDescription()->shouldBeCalled()->willReturn($parentField->reveal()); - $admin->setSubject(1)->shouldBeCalled(); - $admin->defineFormBuilder(new AnyValueToken())->shouldBeCalled(); - $admin->getModelManager()->shouldBeCalled()->willReturn($modelManager); - $admin->getClass()->shouldBeCalled()->willReturn(Foo::class); - - $field = $this->prophesize(FieldDescriptionInterface::class); - $field->getAssociationAdmin()->shouldBeCalled()->willReturn($admin->reveal()); - $field->getFieldName()->shouldBeCalled()->willReturn('bar'); - $field->getParentAssociationMappings()->shouldBeCalled()->willReturn([['fieldName' => 'foo']]); - - $this->builder->add('foo.bar'); - - try { - $this->adminType->buildForm($this->builder, [ - 'sonata_field_description' => $field->reveal(), - 'delete' => false, // not needed - 'property_path' => 'bar', // actual test case - ]); - } catch (NoSuchPropertyException $exception) { - $this->fail($exception->getMessage()); - } - } - - /** - * @expectedDeprecation Calling Sonata\AdminBundle\Form\Type\AdminType::__construct without passing an Sonata\AdminBundle\Admin\AdminHelper as argument is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. - */ - public function testArrayCollection(): void - { - $foo = new Foo(); - - $parentSubject = new \stdClass(); - $parentSubject->foo = new ArrayCollection([$foo]); - - $parentAdmin = $this->prophesize(AdminInterface::class); - $parentAdmin->getSubject()->shouldBeCalled()->willReturn($parentSubject); - $parentAdmin->hasSubject()->shouldBeCalled()->willReturn(true); - $parentField = $this->prophesize(FieldDescriptionInterface::class); - $parentField->setAssociationAdmin(Argument::type(AdminInterface::class))->shouldBeCalled(); - $parentField->getAdmin()->shouldBeCalled()->willReturn($parentAdmin->reveal()); - - $modelManager = $this->prophesize(ModelManagerInterface::class); - - $admin = $this->prophesize(AbstractAdmin::class); - $admin->hasParentFieldDescription()->shouldBeCalled()->willReturn(true); - $admin->getParentFieldDescription()->shouldBeCalled()->willReturn($parentField->reveal()); - $admin->defineFormBuilder(new AnyValueToken())->shouldBeCalled(); - $admin->getModelManager()->shouldBeCalled()->willReturn($modelManager); - $admin->getClass()->shouldBeCalled()->willReturn(Foo::class); - $admin->setSubject($foo)->shouldBeCalled(); - - $field = $this->prophesize(FieldDescriptionInterface::class); - $field->getAssociationAdmin()->shouldBeCalled()->willReturn($admin->reveal()); - $field->getFieldName()->shouldBeCalled()->willReturn('foo'); - $field->getParentAssociationMappings()->shouldBeCalled()->willReturn([]); - - $this->builder->add('foo'); - - try { - $this->adminType->buildForm($this->builder, [ - 'sonata_field_description' => $field->reveal(), - 'delete' => false, // not needed - 'property_path' => '[0]', // actual test case - ]); - } catch (NoSuchPropertyException $exception) { - $this->fail($exception->getMessage()); - } - } - - /** - * @expectedDeprecation Calling Sonata\AdminBundle\Form\Type\AdminType::__construct without passing an Sonata\AdminBundle\Admin\AdminHelper as argument is deprecated since sonata-project/admin-bundle 3.66 and will throw an exception in 4.0. - */ - public function testArrayCollectionNotFound(): void - { - $parentSubject = new \stdClass(); - $parentSubject->foo = new ArrayCollection(); - - $parentAdmin = $this->prophesize(AdminInterface::class); - $parentAdmin->getSubject()->shouldBeCalled()->willReturn($parentSubject); - $parentAdmin->hasSubject()->shouldBeCalled()->willReturn(true); - $parentField = $this->prophesize(FieldDescriptionInterface::class); - $parentField->setAssociationAdmin(Argument::type(AdminInterface::class))->shouldBeCalled(); - $parentField->getAdmin()->shouldBeCalled()->willReturn($parentAdmin->reveal()); - - $modelManager = $this->prophesize(ModelManagerInterface::class); - - $foo = new Foo(); - - $admin = $this->prophesize(AbstractAdmin::class); - $admin->hasParentFieldDescription()->shouldBeCalled()->willReturn(true); - $admin->getParentFieldDescription()->shouldBeCalled()->willReturn($parentField->reveal()); - $admin->defineFormBuilder(new AnyValueToken())->shouldBeCalled(); - $admin->getModelManager()->shouldBeCalled()->willReturn($modelManager); - $admin->getClass()->shouldBeCalled()->willReturn(Foo::class); - $admin->getNewInstance()->shouldBeCalled()->willReturn($foo); - $admin->setSubject($foo)->shouldBeCalled(); - - $field = $this->prophesize(FieldDescriptionInterface::class); - $field->getAssociationAdmin()->shouldBeCalled()->willReturn($admin->reveal()); - $field->getFieldName()->shouldBeCalled()->willReturn('foo'); - $field->getParentAssociationMappings()->shouldBeCalled()->willReturn([]); - - $this->builder->add('foo'); - - try { - $this->adminType->buildForm($this->builder, [ - 'sonata_field_description' => $field->reveal(), - 'delete' => false, // not needed - 'property_path' => '[0]', // actual test case - ]); - } catch (NoSuchPropertyException $exception) { - $this->fail($exception->getMessage()); - } - } - - protected function getExtensions() - { - $extensions = parent::getExtensions(); - - $guesser = $this->prophesize(FormTypeGuesserInterface::class)->reveal(); - $extension = new TestExtension($guesser); - - $extension->addTypeExtension(new FormTypeFieldExtension([], [])); - $extensions[] = $extension; - - return $extensions; - } -} diff --git a/tests/Manipulator/ObjectManipulatorTest.php b/tests/Manipulator/ObjectManipulatorTest.php new file mode 100644 index 0000000000..14d1b394ef --- /dev/null +++ b/tests/Manipulator/ObjectManipulatorTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Tests\Manipulator; + +use PHPUnit\Framework\TestCase; +use Sonata\AdminBundle\Admin\FieldDescriptionInterface; +use Sonata\AdminBundle\Manipulator\ObjectManipulator; + +class ObjectManipulatorTest extends TestCase +{ + public function testAddInstance(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['fieldName' => 'fooBar']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([]); + + $instance = new \stdClass(); + $object = $this->getMockBuilder(\stdClass::class)->setMethods(['addFooBar'])->getMock(); + $object->expects($this->once())->method('addFooBar')->with($instance); + + ObjectManipulator::addInstance($object, $instance, $fieldDescription); + } + + public function testAddInstanceWithParentAssociation(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['fieldName' => 'fooBar']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([['fieldName' => 'parent']]); + + $instance = new \stdClass(); + + $object2 = $this->getMockBuilder(\stdClass::class)->setMethods(['addFooBar'])->getMock(); + $object2->expects($this->once())->method('addFooBar')->with($instance); + + $object1 = $this->getMockBuilder(\stdClass::class)->setMethods(['getParent'])->getMock(); + $object1->expects($this->once())->method('getParent')->willReturn($object2); + + ObjectManipulator::addInstance($object1, $instance, $fieldDescription); + } + + public function testAddInstancePlural(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['fieldName' => 'fooBars']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([]); + + $instance = new \stdClass(); + $object = $this->getMockBuilder(\stdClass::class)->setMethods(['addFooBar'])->getMock(); + $object->expects($this->once())->method('addFooBar')->with($instance); + + ObjectManipulator::addInstance($object, $instance, $fieldDescription); + } + + public function testAddInstanceInflector(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['fieldName' => 'entries']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([]); + + $instance = new \stdClass(); + $object = $this->getMockBuilder(\stdClass::class)->setMethods(['addEntry'])->getMock(); + $object->expects($this->once())->method('addEntry')->with($instance); + + ObjectManipulator::addInstance($object, $instance, $fieldDescription); + } + + public function testSetObject(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['mappedBy' => 'parent']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([]); + + $object = new \stdClass(); + $instance = $this->getMockBuilder(\stdClass::class)->setMethods(['setParent'])->getMock(); + $instance->expects($this->once())->method('setParent')->with($object); + + ObjectManipulator::setObject($instance, $object, $fieldDescription); + } + + public function testSetObjectWithParentAssociation(): void + { + $fieldDescription = $this->createMock(FieldDescriptionInterface::class); + $fieldDescription->expects($this->once())->method('getAssociationMapping')->willReturn(['mappedBy' => 'fooBar']); + $fieldDescription->expects($this->once())->method('getParentAssociationMappings')->willReturn([['fieldName' => 'parent']]); + + $object2 = new \stdClass(); + + $instance = $this->getMockBuilder(\stdClass::class)->setMethods(['setFooBar'])->getMock(); + $instance->expects($this->once())->method('setFooBar')->with($object2); + + $object1 = $this->getMockBuilder(\stdClass::class)->setMethods(['getParent'])->getMock(); + $object1->expects($this->once())->method('getParent')->willReturn($object2); + + ObjectManipulator::setObject($instance, $object1, $fieldDescription); + } +}