Skip to content

Commit

Permalink
Merge remote-tracking branch 'dunglas/fix-null-classname' into feat-i…
Browse files Browse the repository at this point in the history
…nput-output
  • Loading branch information
soyuka committed Oct 4, 2018
2 parents 44170be + b08731f commit b43fc65
Show file tree
Hide file tree
Showing 18 changed files with 265 additions and 38 deletions.
3 changes: 0 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 0 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 0 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
29 changes: 29 additions & 0 deletions features/doctrine/search_filter.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 52 additions & 6 deletions features/hal/max_depth.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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:
Expand Down
28 changes: 14 additions & 14 deletions features/jsonld/max_depth.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
Expand All @@ -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"
}
Expand All @@ -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"
}
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<php>
<ini name="error_reporting" value="-1" />
<ini name="memory_limit" value="-1" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak_vendors" />
<server name="KERNEL_DIR" value="tests/Fixtures/app/" />
<server name="KERNEL_CLASS" value="AppKernel" />
<server name="APP_ENV" value="test" />
Expand Down
18 changes: 17 additions & 1 deletion src/Bridge/Doctrine/Common/DataPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
}
4 changes: 4 additions & 0 deletions src/Bridge/Symfony/Routing/IriConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/DataPersister/ChainDataPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function remove($data)
{
foreach ($this->persisters as $persister) {
if ($persister->supports($data)) {
$persister->remove($data);
return $persister->remove($data);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/GraphQl/Type/SchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() ?? []) {
Expand Down
6 changes: 4 additions & 2 deletions src/Hal/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -26,6 +27,7 @@
final class ItemNormalizer extends AbstractItemNormalizer
{
use ContextTrait;
use ClassInfoTrait;

const FORMAT = 'jsonhal';

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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] :
Expand Down
Loading

0 comments on commit b43fc65

Please sign in to comment.