From 8e9fc9b96897ae57a183c7fed9373494b204a3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 19 Sep 2018 21:55:12 +0200 Subject: [PATCH 1/6] Simplify env var config (#2210) * Simplify env var config * Another try --- .circleci/config.yml | 3 --- .travis.yml | 5 ----- appveyor.yml | 4 ---- phpunit.xml.dist | 1 + 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b1473f5812e..a9a4fa9e9c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -164,9 +164,6 @@ jobs: behat-coverage: docker: - image: circleci/php:7.2-node-browsers - environment: - SYMFONY_DEPRECATIONS_HELPER: weak_vendors - APP_ENV: test parallelism: 2 working_directory: ~/api-platform/core steps: diff --git a/.travis.yml b/.travis.yml index 5fec099af5b..4c0e4a4d17c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,6 @@ cache: - $HOME/.composer/cache - $HOME/.npm -env: - global: - - SYMFONY_DEPRECATIONS_HELPER=weak_vendors - - APP_ENV=test - jobs: include: - php: '7.1' diff --git a/appveyor.yml b/appveyor.yml index f5fa51aef3f..90f689d38a4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,10 +5,6 @@ clone_folder: c:\projects\api-platform\core cache: - '%LOCALAPPDATA%\Composer\files' -environment: - SYMFONY_DEPRECATIONS_HELPER: weak_vendors - APP_ENV: test - install: - ps: Set-Service wuauserv -StartupType Manual - cinst -y php composer diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ceed9ab000d..10f0d89101d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,6 +10,7 @@ + From 7f6daca98a1c4caed8f389791aae9ac534380c98 Mon Sep 17 00:00:00 2001 From: Anthony GRASSIOT Date: Thu, 27 Sep 2018 16:01:33 +0200 Subject: [PATCH 2/6] Throw an InvalidArgument when trying to get Item from a collection route --- features/doctrine/search_filter.feature | 29 +++++++++++++++++++ src/Bridge/Symfony/Routing/IriConverter.php | 4 +++ .../Symfony/Routing/IriConverterTest.php | 15 ++++++++++ 3 files changed, 48 insertions(+) diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature index 6f16a04e17a..8d34d20c48b 100644 --- a/features/doctrine/search_filter.feature +++ b/features/doctrine/search_filter.feature @@ -374,6 +374,35 @@ Feature: Search filter on collections } """ + @sqlite + Scenario: Search for entities with an existing collection route name + When I send a "GET" request to "/dummies?relatedDummies=dummy_cars" + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be valid according to this schema: + """ + { + "type": "object", + "properties": { + "@context": {"pattern": "^/contexts/Dummy$"}, + "@id": {"pattern": "^/dummies$"}, + "@type": {"pattern": "^hydra:Collection$"}, + "hydra:member": { + "type": "array", + "maxItems": 0 + }, + "hydra:view": { + "type": "object", + "properties": { + "@id": {"pattern": "^/dummies\\?relatedDummies=dummy_cars"}, + "@type": {"pattern": "^hydra:PartialCollectionView$"} + } + } + } + } + """ + @createSchema Scenario: Search related collection by name Given there are 3 dummy objects having each 3 relatedDummies diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index d328256198f..4852ce49045 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -79,6 +79,10 @@ public function getItemFromIri(string $iri, array $context = []) throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); } + if (isset($parameters['_api_collection_operation_name'])) { + throw new InvalidArgumentException(sprintf('The iri "%s" references a collection not an item.', $iri)); + } + $attributes = AttributesExtractor::extractAttributes($parameters); try { diff --git a/tests/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Bridge/Symfony/Routing/IriConverterTest.php index b524814d64c..b734601d311 100644 --- a/tests/Bridge/Symfony/Routing/IriConverterTest.php +++ b/tests/Bridge/Symfony/Routing/IriConverterTest.php @@ -62,6 +62,21 @@ public function testGetItemFromIriNoResourceException() $converter->getItemFromIri('/users/3'); } + public function testGetItemFromIriCollectionRouteException() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The iri "/users" references a collection not an item.'); + + $routerProphecy = $this->prophesize(RouterInterface::class); + $routerProphecy->match('/users')->willReturn([ + '_api_resource_class' => Dummy::class, + '_api_collection_operation_name' => 'get', + ])->shouldBeCalledTimes(1); + + $converter = $this->getIriConverter($routerProphecy); + $converter->getItemFromIri('/users'); + } + public function testGetItemFromIriItemNotFoundException() { $this->expectException(ItemNotFoundException::class); From 5db15220a0f50b0382e04a79bee4f36b6d8075b5 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Mon, 1 Oct 2018 10:29:07 +0200 Subject: [PATCH 3/6] Fix: max depth not working when fetch eager is disabled (#2215) * Add failing test * Fix HAL normalizer --- features/bootstrap/FeatureContext.php | 19 ++++++ features/hal/max_depth.feature | 58 +++++++++++++++++-- features/jsonld/max_depth.feature | 28 ++++----- src/Hal/Serializer/ItemNormalizer.php | 6 +- .../TestBundle/Entity/MaxDepthDummy.php | 2 + .../TestBundle/Entity/MaxDepthEagerDummy.php | 57 ++++++++++++++++++ 6 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Entity/MaxDepthEagerDummy.php diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 2629e7f8f94..d6afde8132f 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -35,6 +35,7 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FooDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FourthLevel; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet; @@ -1024,4 +1025,22 @@ public function thereIsAPersonWithAGreeting(string $name, string $message) $this->manager->flush(); $this->manager->clear(); } + + /** + * @Given there is a max depth dummy with :level level of descendants + */ + public function thereIsAMaxDepthDummyWithLevelOfDescendants(int $level) + { + $maxDepthDummy = new MaxDepthDummy(); + $maxDepthDummy->name = "level $level"; + $this->manager->persist($maxDepthDummy); + + for ($i = 1; $i <= $level; ++$i) { + $maxDepthDummy = $maxDepthDummy->child = new MaxDepthDummy(); + $maxDepthDummy->name = 'level '.($i + 1); + $this->manager->persist($maxDepthDummy); + } + + $this->manager->flush(); + } } diff --git a/features/hal/max_depth.feature b/features/hal/max_depth.feature index 65f4e8ed021..4ade6116abc 100644 --- a/features/hal/max_depth.feature +++ b/features/hal/max_depth.feature @@ -7,7 +7,7 @@ Feature: Max depth handling Scenario: Create a resource with 1 level of descendants When I add "Accept" header equal to "application/hal+json" And I add "Content-Type" header equal to "application/json" - And I send a "POST" request to "/max_depth_dummies" with body: + And I send a "POST" request to "/max_depth_eager_dummies" with body: """ { "name": "level 1", @@ -24,17 +24,17 @@ Feature: Max depth handling { "_links": { "self": { - "href": "\/max_depth_dummies\/1" + "href": "\/max_depth_eager_dummies\/1" }, "child": { - "href": "\/max_depth_dummies\/2" + "href": "\/max_depth_eager_dummies\/2" } }, "_embedded": { "child": { "_links": { "self": { - "href": "\/max_depth_dummies\/2" + "href": "\/max_depth_eager_dummies\/2" } }, "id": 2, @@ -46,8 +46,54 @@ Feature: Max depth handling } """ - @dropSchema Scenario: Add a 2nd level of descendants + When I add "Accept" header equal to "application/hal+json" + And I add "Content-Type" header equal to "application/json" + And I send a "PUT" request to "max_depth_eager_dummies/1" with body: + """ + { + "id": "/max_depth_eager_dummies/1", + "child": { + "id": "/max_depth_eager_dummies/2", + "child": { + "name": "level 3" + } + } + } + """ + And the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "_links": { + "self": { + "href": "\/max_depth_eager_dummies\/1" + }, + "child": { + "href": "\/max_depth_eager_dummies\/2" + } + }, + "_embedded": { + "child": { + "_links": { + "self": { + "href": "\/max_depth_eager_dummies\/2" + } + }, + "id": 2, + "name": "level 2" + } + }, + "id": 1, + "name": "level 1" + } + """ + + @dropSchema + Scenario: Add a 2nd level of descendants when eager fetching is disabled + Given there is a max depth dummy with 1 level of descendants When I add "Accept" header equal to "application/hal+json" And I add "Content-Type" header equal to "application/json" And I send a "PUT" request to "max_depth_dummies/1" with body: @@ -62,7 +108,7 @@ Feature: Max depth handling } } """ - And the response status code should be 200 + Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8" And the JSON should be equal to: diff --git a/features/jsonld/max_depth.feature b/features/jsonld/max_depth.feature index 99469b5f554..af039168199 100644 --- a/features/jsonld/max_depth.feature +++ b/features/jsonld/max_depth.feature @@ -6,7 +6,7 @@ Feature: Max depth handling @createSchema Scenario: Create a resource with 1 level of descendants When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/max_depth_dummies" with body: + And I send a "POST" request to "/max_depth_eager_dummies" with body: """ { "name": "level 1", @@ -21,14 +21,14 @@ Feature: Max depth handling And the JSON should be equal to: """ { - "@context": "/contexts/MaxDepthDummy", - "@id": "/max_depth_dummies/1", - "@type": "MaxDepthDummy", + "@context": "/contexts/MaxDepthEagerDummy", + "@id": "/max_depth_eager_dummies/1", + "@type": "MaxDepthEagerDummy", "id": 1, "name": "level 1", "child": { - "@id": "/max_depth_dummies/2", - "@type": "MaxDepthDummy", + "@id": "/max_depth_eager_dummies/2", + "@type": "MaxDepthEagerDummy", "id": 2, "name": "level 2" } @@ -38,12 +38,12 @@ Feature: Max depth handling @dropSchema Scenario: Add a 2nd level of descendants When I add "Content-Type" header equal to "application/ld+json" - And I send a "PUT" request to "max_depth_dummies/1" with body: + And I send a "PUT" request to "max_depth_eager_dummies/1" with body: """ { - "@id": "/max_depth_dummies/1", + "@id": "/max_depth_eager_dummies/1", "child": { - "@id": "/max_depth_dummies/2", + "@id": "/max_depth_eager_dummies/2", "child": { "name": "level 3" } @@ -56,14 +56,14 @@ Feature: Max depth handling And the JSON should be equal to: """ { - "@context": "/contexts/MaxDepthDummy", - "@id": "/max_depth_dummies/1", - "@type": "MaxDepthDummy", + "@context": "/contexts/MaxDepthEagerDummy", + "@id": "/max_depth_eager_dummies/1", + "@type": "MaxDepthEagerDummy", "id": 1, "name": "level 1", "child": { - "@id": "/max_depth_dummies/2", - "@type": "MaxDepthDummy", + "@id": "/max_depth_eager_dummies/2", + "@type": "MaxDepthEagerDummy", "id": 2, "name": "level 2" } diff --git a/src/Hal/Serializer/ItemNormalizer.php b/src/Hal/Serializer/ItemNormalizer.php index d7e176921ed..9ae08bf58ac 100644 --- a/src/Hal/Serializer/ItemNormalizer.php +++ b/src/Hal/Serializer/ItemNormalizer.php @@ -16,6 +16,7 @@ use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Serializer\AbstractItemNormalizer; use ApiPlatform\Core\Serializer\ContextTrait; +use ApiPlatform\Core\Util\ClassInfoTrait; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; /** @@ -26,6 +27,7 @@ final class ItemNormalizer extends AbstractItemNormalizer { use ContextTrait; + use ClassInfoTrait; const FORMAT = 'jsonhal'; @@ -102,7 +104,7 @@ protected function getAttributes($object, $format = null, array $context) */ private function getComponents($object, string $format = null, array $context) { - $cacheKey = \get_class($object).'-'.$context['cache_key']; + $cacheKey = $this->getObjectClass($object).'-'.$context['cache_key']; if (isset($this->componentsCache[$cacheKey])) { return $this->componentsCache[$cacheKey]; @@ -160,7 +162,7 @@ private function getComponents($object, string $format = null, array $context) */ private function populateRelation(array $data, $object, string $format = null, array $context, array $components, string $type): array { - $class = \get_class($object); + $class = $this->getObjectClass($object); $attributesMetadata = \array_key_exists($class, $this->attributesMetadataCache) ? $this->attributesMetadataCache[$class] : diff --git a/tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php b/tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php index 8b3f85db57d..f5d8781280a 100644 --- a/tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php +++ b/tests/Fixtures/TestBundle/Entity/MaxDepthDummy.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity; +use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; @@ -45,6 +46,7 @@ class MaxDepthDummy /** * @ORM\ManyToOne(targetEntity="MaxDepthDummy", cascade={"persist"}) + * @ApiProperty(attributes={"fetch_eager"=false}) * @Groups({"default"}) * @MaxDepth(1) */ diff --git a/tests/Fixtures/TestBundle/Entity/MaxDepthEagerDummy.php b/tests/Fixtures/TestBundle/Entity/MaxDepthEagerDummy.php new file mode 100644 index 00000000000..4c0b9915710 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/MaxDepthEagerDummy.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Core\Annotation\ApiResource; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\MaxDepth; + +/** * + * @ApiResource(attributes={ + * "normalization_context"={"groups"={"default"}, "enable_max_depth"=true}, + * "denormalization_context"={"groups"={"default"}, "enable_max_depth"=true} + * }) + * @ORM\Entity + * + * @author Brian Fox + */ +class MaxDepthEagerDummy +{ + /** + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + * @Groups({"default"}) + */ + private $id; + + /** + * @ORM\Column(name="name", type="string", length=30) + * @Groups({"default"}) + */ + public $name; + + /** + * @ORM\ManyToOne(targetEntity="MaxDepthEagerDummy", cascade={"persist"}) + * @Groups({"default"}) + * @MaxDepth(1) + */ + public $child; + + public function getId() + { + return $this->id; + } +} From 8a8eab4bf1dfcc77bbb5ddce8ebedf7b42d872fd Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 1 Oct 2018 09:11:08 +0200 Subject: [PATCH 4/6] Fix data persister remove stops after 1st match --- src/DataPersister/ChainDataPersister.php | 2 +- tests/DataPersister/ChainDataPersisterTest.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/DataPersister/ChainDataPersister.php b/src/DataPersister/ChainDataPersister.php index 6abf03987a6..2883aa1d63b 100644 --- a/src/DataPersister/ChainDataPersister.php +++ b/src/DataPersister/ChainDataPersister.php @@ -63,7 +63,7 @@ public function remove($data) { foreach ($this->persisters as $persister) { if ($persister->supports($data)) { - $persister->remove($data); + return $persister->remove($data); } } } diff --git a/tests/DataPersister/ChainDataPersisterTest.php b/tests/DataPersister/ChainDataPersisterTest.php index 82d82631bed..c68b8e8b47f 100644 --- a/tests/DataPersister/ChainDataPersisterTest.php +++ b/tests/DataPersister/ChainDataPersisterTest.php @@ -60,7 +60,11 @@ public function testPersist() $barPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled(); $barPersisterProphecy->persist($dummy)->shouldBeCalled(); - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal()]))->persist($dummy); + $foobarPersisterProphecy = $this->prophesize(DataPersisterInterface::class); + $foobarPersisterProphecy->supports($dummy)->shouldNotBeCalled(); + $foobarPersisterProphecy->persist($dummy)->shouldNotBeCalled(); + + (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->persist($dummy); } public function testRemove() @@ -75,6 +79,10 @@ public function testRemove() $barPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled(); $barPersisterProphecy->remove($dummy)->shouldBeCalled(); - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal()]))->remove($dummy); + $foobarPersisterProphecy = $this->prophesize(DataPersisterInterface::class); + $foobarPersisterProphecy->supports($dummy)->shouldNotBeCalled(); + $foobarPersisterProphecy->remove($dummy)->shouldNotBeCalled(); + + (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->remove($dummy); } } From 9823253373a76835c543701ab88fa9fca21494c3 Mon Sep 17 00:00:00 2001 From: Cyril PASCAL Date: Tue, 2 Oct 2018 18:25:44 +0200 Subject: [PATCH 5/6] Persist doctrine entities with DEFERRED_EXPLICIT change tracking policy --- src/Bridge/Doctrine/Common/DataPersister.php | 18 +++++++++- .../Doctrine/Common/DataPersisterTest.php | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Bridge/Doctrine/Common/DataPersister.php b/src/Bridge/Doctrine/Common/DataPersister.php index 1d19496047e..b064dca12c6 100644 --- a/src/Bridge/Doctrine/Common/DataPersister.php +++ b/src/Bridge/Doctrine/Common/DataPersister.php @@ -17,6 +17,7 @@ use ApiPlatform\Core\Util\ClassInfoTrait; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager as DoctrineObjectManager; +use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * Data persister for Doctrine. @@ -51,7 +52,7 @@ public function persist($data) return $data; } - if (!$manager->contains($data)) { + if (!$manager->contains($data) || $this->isDeferredExplicit($manager, $data)) { $manager->persist($data); } @@ -84,4 +85,19 @@ private function getManager($data) { return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null; } + + /** + * Checks if doctrine does not manage data automatically. + * + * @return bool + */ + private function isDeferredExplicit(DoctrineObjectManager $manager, $data) + { + $classMetadata = $manager->getClassMetadata($this->getObjectClass($data)); + if ($classMetadata instanceof ClassMetadataInfo && \method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) { + return $classMetadata->isChangeTrackingDeferredExplicit(); + } + + return false; + } } diff --git a/tests/Bridge/Doctrine/Common/DataPersisterTest.php b/tests/Bridge/Doctrine/Common/DataPersisterTest.php index 04551e8e81b..3d5b7ff124f 100644 --- a/tests/Bridge/Doctrine/Common/DataPersisterTest.php +++ b/tests/Bridge/Doctrine/Common/DataPersisterTest.php @@ -18,7 +18,10 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use PHPUnit\Framework\TestCase; +use Prophecy\Prediction\CallPrediction; +use Prophecy\Prediction\NoCallsPrediction; /** * @author Baptiste Meyer @@ -69,6 +72,7 @@ public function testPersistIfEntityAlreadyManaged() $objectManagerProphecy->persist($dummy)->shouldNotBeCalled(); $objectManagerProphecy->flush()->shouldBeCalled(); $objectManagerProphecy->refresh($dummy)->shouldBeCalled(); + $objectManagerProphecy->getClassMetadata(Dummy::class)->willReturn(null)->shouldBeCalled(); $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); @@ -109,4 +113,36 @@ public function testRemoveWithNullManager() (new DataPersister($managerRegistryProphecy->reveal()))->remove(new Dummy()); } + + public function getTrackingPolicyParameters() + { + return [ + 'deferred explicit' => [true, true], + 'deferred implicit' => [false, false], + ]; + } + + /** + * @dataProvider getTrackingPolicyParameters + */ + public function testTrackingPolicy($deferredExplicit, $persisted) + { + $dummy = new Dummy(); + + $classMetadataInfo = $this->prophesize(ClassMetadataInfo::class); + $classMetadataInfo->isChangeTrackingDeferredExplicit()->willReturn($deferredExplicit)->shouldBeCalled(); + + $objectManagerProphecy = $this->prophesize(ObjectManager::class); + $objectManagerProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataInfo)->shouldBeCalled(); + $objectManagerProphecy->contains($dummy)->willReturn(true); + $objectManagerProphecy->persist($dummy)->should($persisted ? new CallPrediction() : new NoCallsPrediction()); + $objectManagerProphecy->flush()->shouldBeCalled(); + $objectManagerProphecy->refresh($dummy)->shouldBeCalled(); + + $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); + $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy)->shouldBeCalled(); + + $result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy); + $this->assertSame($dummy, $result); + } } From b08731f637a898704a8445913ba24a7baaab2383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 4 Oct 2018 19:22:59 +0200 Subject: [PATCH 6/6] GraphQL: fix a bug when the class name isn't provided --- src/GraphQl/Type/SchemaBuilder.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 69455f8b2a2..63de7b4c9ba 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -366,6 +366,10 @@ private function convertType(Type $type, bool $input = false, string $mutationNa } $resourceClass = $this->isCollection($type) ? $type->getCollectionValueType()->getClassName() : $type->getClassName(); + if (null === $resourceClass) { + return null; + } + try { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); if ([] === $resourceMetadata->getGraphql() ?? []) {