mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-02 23:59:26 +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;
|
||||
|
||||
use EXSyst\Component\Swagger\Schema;
|
||||
use Hateoas\Configuration\Metadata\ClassMetadata;
|
||||
use Hateoas\Configuration\Relation;
|
||||
use Hateoas\Serializer\Metadata\RelationPropertyMetadata;
|
||||
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
|
||||
use JMS\Serializer\SerializationContext;
|
||||
use Metadata\MetadataFactoryInterface;
|
||||
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
|
||||
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
|
||||
@ -48,39 +47,43 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
||||
{
|
||||
$this->JMSModelDescriber->describe($model, $schema);
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
$metadata = $this->getHateoasMetadata($model);
|
||||
if (null === $metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
$groupsExclusion = null !== $model->getGroups() ? new GroupsExclusionStrategy($model->getGroups()) : null;
|
||||
|
||||
$schema->setType('object');
|
||||
$context = $this->JMSModelDescriber->getSerializationContext($model);
|
||||
|
||||
foreach ($metadata->getRelations() as $relation) {
|
||||
if (!$relation->getEmbedded() && !$relation->getHref()) {
|
||||
continue;
|
||||
}
|
||||
$item = new RelationPropertyMetadata($relation->getExclusion(), $relation);
|
||||
|
||||
if (null !== $groupsExclusion && $relation->getExclusion()) {
|
||||
$item = new RelationPropertyMetadata($relation->getExclusion(), $relation);
|
||||
|
||||
// filter groups
|
||||
if ($groupsExclusion->shouldSkipProperty($item, SerializationContext::create())) {
|
||||
continue;
|
||||
}
|
||||
if (null !== $context->getExclusionStrategy() && $context->getExclusionStrategy()->shouldSkipProperty($item, $context)) {
|
||||
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();
|
||||
$relationSchema->setReadOnly(true);
|
||||
|
||||
$name = $relation->getName();
|
||||
$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()) {
|
||||
$subProperties = $property->getProperties();
|
||||
|
||||
@ -89,6 +92,8 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
||||
|
||||
$this->setAttributeProperties($relation, $subProperties);
|
||||
}
|
||||
|
||||
$context->popPropertyMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +124,7 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi
|
||||
foreach ($relation->getAttributes() as $attribute => $value) {
|
||||
$subSubProp = $subProperties->get($attribute);
|
||||
switch (gettype($value)) {
|
||||
case 'integer':
|
||||
case 'integer' :
|
||||
$subSubProp->setType('integer');
|
||||
$subSubProp->setDefault($value);
|
||||
|
||||
|
@ -118,7 +118,10 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
||||
$context->popClassMetadata();
|
||||
}
|
||||
|
||||
private function getSerializationContext(Model $model): SerializationContext
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getSerializationContext(Model $model): SerializationContext
|
||||
{
|
||||
if (isset($this->contexts[$model->getHash()])) {
|
||||
$context = $this->contexts[$model->getHash()];
|
||||
@ -178,7 +181,11 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
|
||||
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);
|
||||
if (null !== $nestedTypeInfo) {
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Functional;
|
||||
|
||||
use Hateoas\Configuration\Embedded;
|
||||
|
||||
class BazingaFunctionalTest extends WebTestCase
|
||||
{
|
||||
public function testModelComplexDocumentationBazinga()
|
||||
@ -57,12 +59,60 @@ class BazingaFunctionalTest extends WebTestCase
|
||||
'route' => [
|
||||
'type' => 'object',
|
||||
],
|
||||
'embed_with_group' => [
|
||||
'type' => 'object',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
], $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 = [])
|
||||
{
|
||||
return new TestKernel(true, true);
|
||||
|
@ -32,4 +32,16 @@ class BazingaController
|
||||
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="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
|
||||
{
|
||||
|
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'],
|
||||
'Test' => ['type' => 'string'],
|
||||
'JMSPicture_mini' => ['type' => 'object'],
|
||||
'BazingaUser_grouped' => ['type' => 'object'],
|
||||
];
|
||||
|
||||
yield ['/docs/test', 'test', $expected];
|
||||
|
@ -14,8 +14,10 @@ namespace Nelmio\ApiDocBundle\Tests\Functional;
|
||||
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
|
||||
use Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle;
|
||||
use FOS\RestBundle\FOSRestBundle;
|
||||
use Hateoas\Configuration\Embedded;
|
||||
use JMS\SerializerBundle\JMSSerializerBundle;
|
||||
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\BazingaUser;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
|
||||
@ -88,6 +90,12 @@ class TestKernel extends Kernel
|
||||
|
||||
if ($this->useBazinga) {
|
||||
$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,
|
||||
'groups' => ['mini'],
|
||||
],
|
||||
[
|
||||
'alias' => 'BazingaUser_grouped',
|
||||
'type' => BazingaUser::class,
|
||||
'groups' => ['foo'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
@ -51,6 +51,9 @@
|
||||
"api-platform/core": "For using an API oriented framework.",
|
||||
"friendsofsymfony/rest-bundle": "For using the parameters annotations."
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/framework-bundle": "4.2.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nelmio\\ApiDocBundle\\": ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user