mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-09 02:59:27 +03:00
Support typed embedded relation with willdurand/hateoas 3.0 (#1510)
* allow typed embedded relation with hateoas 3.0 * symfony/framework-bundle 4.2.7 is broken https://github.com/symfony/symfony/pull/31156 * internal public methods
This commit is contained in:
parent
101648bb8f
commit
eb255010a0
@ -12,10 +12,9 @@
|
|||||||
namespace Nelmio\ApiDocBundle\ModelDescriber;
|
namespace Nelmio\ApiDocBundle\ModelDescriber;
|
||||||
|
|
||||||
use EXSyst\Component\Swagger\Schema;
|
use EXSyst\Component\Swagger\Schema;
|
||||||
|
use Hateoas\Configuration\Metadata\ClassMetadata;
|
||||||
use Hateoas\Configuration\Relation;
|
use Hateoas\Configuration\Relation;
|
||||||
use Hateoas\Serializer\Metadata\RelationPropertyMetadata;
|
use Hateoas\Serializer\Metadata\RelationPropertyMetadata;
|
||||||
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
|
|
||||||
use JMS\Serializer\SerializationContext;
|
|
||||||
use Metadata\MetadataFactoryInterface;
|
use Metadata\MetadataFactoryInterface;
|
||||||
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
|
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
|
||||||
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
|
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
|
||||||
@ -48,39 +47,43 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
|||||||
{
|
{
|
||||||
$this->JMSModelDescriber->describe($model, $schema);
|
$this->JMSModelDescriber->describe($model, $schema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ClassMetadata
|
||||||
|
*/
|
||||||
$metadata = $this->getHateoasMetadata($model);
|
$metadata = $this->getHateoasMetadata($model);
|
||||||
if (null === $metadata) {
|
if (null === $metadata) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$groupsExclusion = null !== $model->getGroups() ? new GroupsExclusionStrategy($model->getGroups()) : null;
|
|
||||||
|
|
||||||
$schema->setType('object');
|
$schema->setType('object');
|
||||||
|
$context = $this->JMSModelDescriber->getSerializationContext($model);
|
||||||
|
|
||||||
foreach ($metadata->getRelations() as $relation) {
|
foreach ($metadata->getRelations() as $relation) {
|
||||||
if (!$relation->getEmbedded() && !$relation->getHref()) {
|
if (!$relation->getEmbedded() && !$relation->getHref()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$item = new RelationPropertyMetadata($relation->getExclusion(), $relation);
|
||||||
|
|
||||||
if (null !== $groupsExclusion && $relation->getExclusion()) {
|
if (null !== $context->getExclusionStrategy() && $context->getExclusionStrategy()->shouldSkipProperty($item, $context)) {
|
||||||
$item = new RelationPropertyMetadata($relation->getExclusion(), $relation);
|
continue;
|
||||||
|
|
||||||
// filter groups
|
|
||||||
if ($groupsExclusion->shouldSkipProperty($item, SerializationContext::create())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = $relation->getName();
|
$context->pushPropertyMetadata($item);
|
||||||
|
|
||||||
$relationSchema = $schema->getProperties()->get($relation->getEmbedded() ? '_embedded' : '_links');
|
$embedded = $relation->getEmbedded();
|
||||||
|
$relationSchema = $schema->getProperties()->get($embedded ? '_embedded' : '_links');
|
||||||
|
|
||||||
$properties = $relationSchema->getProperties();
|
$properties = $relationSchema->getProperties();
|
||||||
$relationSchema->setReadOnly(true);
|
$relationSchema->setReadOnly(true);
|
||||||
|
|
||||||
|
$name = $relation->getName();
|
||||||
$property = $properties->get($name);
|
$property = $properties->get($name);
|
||||||
$property->setType('object');
|
|
||||||
|
|
||||||
|
if ($embedded && method_exists($embedded, 'getType') && $embedded->getType()) {
|
||||||
|
$this->JMSModelDescriber->describeItem($embedded->getType(), $property, $context);
|
||||||
|
} else {
|
||||||
|
$property->setType('object');
|
||||||
|
}
|
||||||
if ($relation->getHref()) {
|
if ($relation->getHref()) {
|
||||||
$subProperties = $property->getProperties();
|
$subProperties = $property->getProperties();
|
||||||
|
|
||||||
@ -89,6 +92,8 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
|||||||
|
|
||||||
$this->setAttributeProperties($relation, $subProperties);
|
$this->setAttributeProperties($relation, $subProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$context->popPropertyMetadata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +124,7 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
|||||||
foreach ($relation->getAttributes() as $attribute => $value) {
|
foreach ($relation->getAttributes() as $attribute => $value) {
|
||||||
$subSubProp = $subProperties->get($attribute);
|
$subSubProp = $subProperties->get($attribute);
|
||||||
switch (gettype($value)) {
|
switch (gettype($value)) {
|
||||||
case 'integer':
|
case 'integer' :
|
||||||
$subSubProp->setType('integer');
|
$subSubProp->setType('integer');
|
||||||
$subSubProp->setDefault($value);
|
$subSubProp->setDefault($value);
|
||||||
|
|
||||||
|
@ -118,7 +118,10 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
$context->popClassMetadata();
|
$context->popClassMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSerializationContext(Model $model): SerializationContext
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function getSerializationContext(Model $model): SerializationContext
|
||||||
{
|
{
|
||||||
if (isset($this->contexts[$model->getHash()])) {
|
if (isset($this->contexts[$model->getHash()])) {
|
||||||
$context = $this->contexts[$model->getHash()];
|
$context = $this->contexts[$model->getHash()];
|
||||||
@ -178,7 +181,11 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function describeItem(array $type, $property, Context $context)
|
/**
|
||||||
|
* @internal
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function describeItem(array $type, $property, Context $context)
|
||||||
{
|
{
|
||||||
$nestedTypeInfo = $this->getNestedTypeInArray($type);
|
$nestedTypeInfo = $this->getNestedTypeInArray($type);
|
||||||
if (null !== $nestedTypeInfo) {
|
if (null !== $nestedTypeInfo) {
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Nelmio\ApiDocBundle\Tests\Functional;
|
namespace Nelmio\ApiDocBundle\Tests\Functional;
|
||||||
|
|
||||||
|
use Hateoas\Configuration\Embedded;
|
||||||
|
|
||||||
class BazingaFunctionalTest extends WebTestCase
|
class BazingaFunctionalTest extends WebTestCase
|
||||||
{
|
{
|
||||||
public function testModelComplexDocumentationBazinga()
|
public function testModelComplexDocumentationBazinga()
|
||||||
@ -57,12 +59,60 @@ class BazingaFunctionalTest extends WebTestCase
|
|||||||
'route' => [
|
'route' => [
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
],
|
],
|
||||||
|
'embed_with_group' => [
|
||||||
|
'type' => 'object',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
], $this->getModel('BazingaUser')->toArray());
|
], $this->getModel('BazingaUser')->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWithGroup()
|
||||||
|
{
|
||||||
|
$this->assertEquals([
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'_embedded' => [
|
||||||
|
'readOnly' => true,
|
||||||
|
'properties' => [
|
||||||
|
'embed_with_group' => [
|
||||||
|
'type' => 'object',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], $this->getModel('BazingaUser_grouped')->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithType()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
new \ReflectionMethod(Embedded::class, 'getType');
|
||||||
|
} catch (\ReflectionException $e) {
|
||||||
|
$this->markTestSkipped('Typed embedded properties require at least willdurand/hateoas 3.0');
|
||||||
|
}
|
||||||
|
$this->assertEquals([
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'_embedded' => [
|
||||||
|
'readOnly' => true,
|
||||||
|
'properties' => [
|
||||||
|
'typed_bazinga_users' => [
|
||||||
|
'items' => [
|
||||||
|
'$ref' => '#/definitions/BazingaUser',
|
||||||
|
],
|
||||||
|
'type' => 'array',
|
||||||
|
],
|
||||||
|
'typed_bazinga_name' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], $this->getModel('BazingaUserTyped')->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
protected static function createKernel(array $options = [])
|
protected static function createKernel(array $options = [])
|
||||||
{
|
{
|
||||||
return new TestKernel(true, true);
|
return new TestKernel(true, true);
|
||||||
|
@ -32,4 +32,16 @@ class BazingaController
|
|||||||
public function userAction()
|
public function userAction()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/bazinga_foo", methods={"GET"})
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Success",
|
||||||
|
* @Model(type=BazingaUser::class, groups={"foo"})
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function userGroupAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
35
Tests/Functional/Controller/BazingaTypedController.php
Normal file
35
Tests/Functional/Controller/BazingaTypedController.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the NelmioApiDocBundle package.
|
||||||
|
*
|
||||||
|
* (c) Nelmio
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
|
||||||
|
|
||||||
|
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||||
|
use Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded\BazingaUserTyped;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||||
|
use Swagger\Annotations as SWG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(host="api.example.com")
|
||||||
|
*/
|
||||||
|
class BazingaTypedController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/api/bazinga_typed", methods={"GET"})
|
||||||
|
* @SWG\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Success",
|
||||||
|
* @Model(type=BazingaUserTyped::class)
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function userTypedAction()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,21 @@ use Hateoas\Configuration\Annotation as Hateoas;
|
|||||||
*
|
*
|
||||||
* @Hateoas\Relation(name="example", attributes={"str_att":"bar", "float_att":5.6, "bool_att": false}, href="http://www.example.com")
|
* @Hateoas\Relation(name="example", attributes={"str_att":"bar", "float_att":5.6, "bool_att": false}, href="http://www.example.com")
|
||||||
* @Hateoas\Relation(name="route", href=@Hateoas\Route("foo"))
|
* @Hateoas\Relation(name="route", href=@Hateoas\Route("foo"))
|
||||||
* @Hateoas\Relation(name="route", attributes={"foo":"bar"}, embedded=@Hateoas\Embedded("expr(service('xx'))"))
|
* @Hateoas\Relation(
|
||||||
|
* name="route",
|
||||||
|
* attributes={"foo":"bar"},
|
||||||
|
* embedded=@Hateoas\Embedded(
|
||||||
|
* "expr(service('xx'))"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* @Hateoas\Relation(
|
||||||
|
* name="embed_with_group",
|
||||||
|
* attributes={"foo":"with_groups"},
|
||||||
|
* exclusion=@Hateoas\Exclusion(groups={"foo"}),
|
||||||
|
* embedded=@Hateoas\Embedded(
|
||||||
|
* "expr(service('xx'))"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
class BazingaUser
|
class BazingaUser
|
||||||
{
|
{
|
||||||
|
34
Tests/Functional/EntityExcluded/BazingaUserTyped.php
Normal file
34
Tests/Functional/EntityExcluded/BazingaUserTyped.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the NelmioApiDocBundle package.
|
||||||
|
*
|
||||||
|
* (c) Nelmio
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded;
|
||||||
|
|
||||||
|
use Hateoas\Configuration\Annotation as Hateoas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Hateoas\Relation(
|
||||||
|
* name="typed_bazinga_users",
|
||||||
|
* embedded=@Hateoas\Embedded(
|
||||||
|
* "expr(service('zz'))",
|
||||||
|
* type="array<Nelmio\ApiDocBundle\Tests\Functional\Entity\BazingaUser>"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* @Hateoas\Relation(
|
||||||
|
* name="typed_bazinga_name",
|
||||||
|
* embedded=@Hateoas\Embedded(
|
||||||
|
* "expr(service('yy'))",
|
||||||
|
* type="string"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class BazingaUserTyped
|
||||||
|
{
|
||||||
|
}
|
@ -54,6 +54,7 @@ class SwaggerUiTest extends WebTestCase
|
|||||||
'Dummy' => $expected['definitions']['Dummy'],
|
'Dummy' => $expected['definitions']['Dummy'],
|
||||||
'Test' => ['type' => 'string'],
|
'Test' => ['type' => 'string'],
|
||||||
'JMSPicture_mini' => ['type' => 'object'],
|
'JMSPicture_mini' => ['type' => 'object'],
|
||||||
|
'BazingaUser_grouped' => ['type' => 'object'],
|
||||||
];
|
];
|
||||||
|
|
||||||
yield ['/docs/test', 'test', $expected];
|
yield ['/docs/test', 'test', $expected];
|
||||||
|
@ -14,8 +14,10 @@ namespace Nelmio\ApiDocBundle\Tests\Functional;
|
|||||||
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
|
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
|
||||||
use Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle;
|
use Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle;
|
||||||
use FOS\RestBundle\FOSRestBundle;
|
use FOS\RestBundle\FOSRestBundle;
|
||||||
|
use Hateoas\Configuration\Embedded;
|
||||||
use JMS\SerializerBundle\JMSSerializerBundle;
|
use JMS\SerializerBundle\JMSSerializerBundle;
|
||||||
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
||||||
|
use Nelmio\ApiDocBundle\Tests\Functional\Entity\BazingaUser;
|
||||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture;
|
use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture;
|
||||||
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
|
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
||||||
@ -88,6 +90,12 @@ class TestKernel extends Kernel
|
|||||||
|
|
||||||
if ($this->useBazinga) {
|
if ($this->useBazinga) {
|
||||||
$routes->import(__DIR__.'/Controller/BazingaController.php', '/', 'annotation');
|
$routes->import(__DIR__.'/Controller/BazingaController.php', '/', 'annotation');
|
||||||
|
|
||||||
|
try {
|
||||||
|
new \ReflectionMethod(Embedded::class, 'getType');
|
||||||
|
$routes->import(__DIR__.'/Controller/BazingaTypedController.php', '/', 'annotation');
|
||||||
|
} catch (\ReflectionException $e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +180,11 @@ class TestKernel extends Kernel
|
|||||||
'type' => JMSPicture::class,
|
'type' => JMSPicture::class,
|
||||||
'groups' => ['mini'],
|
'groups' => ['mini'],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'BazingaUser_grouped',
|
||||||
|
'type' => BazingaUser::class,
|
||||||
|
'groups' => ['foo'],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -51,6 +51,9 @@
|
|||||||
"api-platform/core": "For using an API oriented framework.",
|
"api-platform/core": "For using an API oriented framework.",
|
||||||
"friendsofsymfony/rest-bundle": "For using the parameters annotations."
|
"friendsofsymfony/rest-bundle": "For using the parameters annotations."
|
||||||
},
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/framework-bundle": "4.2.7"
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Nelmio\\ApiDocBundle\\": ""
|
"Nelmio\\ApiDocBundle\\": ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user