mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
feature #15063 [API] Allow overriding xml resource mapping (NoResponseMate)
This PR was merged into the 1.13 branch. Discussion ---------- | Q | A | |-----------------|--------------------------------------------------------------| | Branch? | 1.13 | | Bug fix? | no | | New feature? | yes | | BC breaks? | no | | Deprecations? | no | | Related tickets | replaces #13292 | | License | MIT | Commits -------4f5e141611[API] Test overriding xml mapping3db63e41ee[API] Allow overriding xml mapping
This commit is contained in:
commit
0d8d558808
15 changed files with 900 additions and 44 deletions
|
|
@ -54,11 +54,12 @@
|
|||
"Payum\\Core\\Security\\TokenInterface",
|
||||
"Payum\\Core\\Security\\Util\\Random",
|
||||
"Payum\\Core\\Storage\\AbstractStorage",
|
||||
"Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactoryInterface",
|
||||
"PHPUnit\\Framework\\ExpectationFailedException",
|
||||
"Psr\\Container\\ContainerInterface",
|
||||
"Psr\\Http\\Message\\RequestFactoryInterface",
|
||||
"Psr\\Http\\Message\\StreamFactoryInterface",
|
||||
"Stripe\\Stripe"
|
||||
"Stripe\\Stripe",
|
||||
"Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactoryInterface"
|
||||
],
|
||||
"php-core-extensions" : [
|
||||
"Core",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
"ext-hash": "*",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-simplexml": "*",
|
||||
"api-platform/core": "^2.7.10",
|
||||
"babdev/pagerfanta-bundle": "^3.0",
|
||||
"behat/transliterator": "^1.3",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
|
||||
<DeprecatedClass>
|
||||
<errorLevel type="info">
|
||||
<referencedClass name="ApiPlatform\Core\Metadata\Extractor\XmlExtractor" /> <!-- deprecated in ApiPlatform 2.7 -->
|
||||
<referencedClass name="Http\Message\MessageFactory" /> <!-- deprecated in HttpMessage 1.1 -->
|
||||
<referencedClass name="Payum\Core\Action\GatewayAwareAction" />
|
||||
<referencedClass name="Payum\Core\Security\GenericTokenFactoryInterface" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger;
|
||||
|
||||
final class LegacyResourceMetadataMerger implements MetadataMergerInterface
|
||||
{
|
||||
public function merge(array $oldMetadata, array $newMetadata): array
|
||||
{
|
||||
if ([] === $newMetadata || [] === $oldMetadata) {
|
||||
return [] === $newMetadata ? $oldMetadata : $newMetadata;
|
||||
}
|
||||
|
||||
foreach ($newMetadata as $key => $value) {
|
||||
if ('properties' === $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value) && str_contains($key, 'Operations')) {
|
||||
foreach ($value as $operationKey => $operationData) {
|
||||
if (isset($operationData['enabled']) && false === $operationData['enabled']) {
|
||||
unset($oldMetadata[$key][$operationKey]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldMetadata[$key][$operationKey] = array_merge(
|
||||
$oldMetadata[$key][$operationKey] ?? [],
|
||||
$operationData,
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldMetadata[$key] = $value;
|
||||
}
|
||||
|
||||
return $oldMetadata;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger;
|
||||
|
||||
interface MetadataMergerInterface
|
||||
{
|
||||
public function merge(array $oldMetadata, array $newMetadata): array;
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\ApiPlatform\Metadata;
|
||||
|
||||
use ApiPlatform\Core\Metadata\Extractor\XmlExtractor;
|
||||
use ApiPlatform\Exception\InvalidArgumentException;
|
||||
use ApiPlatform\Metadata\Extractor\AbstractResourceExtractor;
|
||||
use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface;
|
||||
use ApiPlatform\Metadata\Extractor\XmlPropertyExtractor;
|
||||
use ApiPlatform\Metadata\Extractor\XmlResourceExtractor;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger\MetadataMergerInterface;
|
||||
use Symfony\Component\Config\Util\XmlUtils;
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*
|
||||
* @see XmlExtractor
|
||||
*/
|
||||
final class MergingXmlExtractor extends AbstractResourceExtractor implements PropertyExtractorInterface
|
||||
{
|
||||
private array $properties = [];
|
||||
|
||||
/**
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public function __construct(
|
||||
array $paths,
|
||||
ContainerInterface $container = null,
|
||||
private ?MetadataMergerInterface $merger = null,
|
||||
) {
|
||||
parent::__construct($paths, $container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getResources(): array
|
||||
{
|
||||
if (!empty($this->resources)) {
|
||||
return $this->resources;
|
||||
}
|
||||
|
||||
$this->resources = [];
|
||||
foreach ($this->paths as $path) {
|
||||
$this->extractPath($path);
|
||||
}
|
||||
|
||||
return $this->resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getProperties(): array
|
||||
{
|
||||
if (!empty($this->properties)) {
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
$this->properties = [];
|
||||
foreach ($this->paths as $path) {
|
||||
$this->extractPath($path);
|
||||
}
|
||||
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
protected function extractPath(string $path): void
|
||||
{
|
||||
try {
|
||||
/** @var \SimpleXMLElement $xml */
|
||||
$xml = simplexml_import_dom(XmlUtils::loadFile($path, XmlExtractor::RESOURCE_SCHEMA));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// Test if this is a new resource
|
||||
try {
|
||||
$xml = XmlUtils::loadFile($path, XmlResourceExtractor::SCHEMA);
|
||||
|
||||
return;
|
||||
} catch (\InvalidArgumentException) {
|
||||
try {
|
||||
$xml = XmlUtils::loadFile($path, XmlPropertyExtractor::SCHEMA);
|
||||
|
||||
return;
|
||||
} catch (\InvalidArgumentException) {
|
||||
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xml->resource as $resource) {
|
||||
$resourceClass = $this->resolve((string) $resource['class']);
|
||||
$resourceMetadata = $this->buildResource($resource);
|
||||
|
||||
if (null !== $this->merger && isset($this->resources[$resourceClass])) {
|
||||
$this->resources[$resourceClass] = $this->merger->merge(
|
||||
$this->resources[$resourceClass],
|
||||
$resourceMetadata,
|
||||
);
|
||||
$this->properties[$resourceClass] = array_merge(
|
||||
$this->properties[$resourceClass],
|
||||
$resourceMetadata['properties'],
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->resources[$resourceClass] = $resourceMetadata;
|
||||
$this->properties[$resourceClass] = $this->resources[$resourceClass]['properties'];
|
||||
}
|
||||
}
|
||||
|
||||
private function buildResource(\SimpleXMLElement $resource): array
|
||||
{
|
||||
return [
|
||||
'shortName' => $this->phpizeAttribute($resource, 'shortName', 'string'),
|
||||
'description' => $this->phpizeAttribute($resource, 'description', 'string'),
|
||||
'iri' => $this->phpizeAttribute($resource, 'iri', 'string'),
|
||||
'itemOperations' => $this->extractOperations($resource, 'itemOperation'),
|
||||
'collectionOperations' => $this->extractOperations($resource, 'collectionOperation'),
|
||||
'subresourceOperations' => $this->extractOperations($resource, 'subresourceOperation'),
|
||||
'graphql' => $this->extractOperations($resource, 'operation'),
|
||||
'attributes' => $this->extractAttributes($resource, 'attribute') ?: null,
|
||||
'properties' => $this->extractProperties($resource) ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array containing configured operations. Returns NULL if there is no operation configuration.
|
||||
*/
|
||||
private function extractOperations(\SimpleXMLElement $resource, string $operationType): ?array
|
||||
{
|
||||
$graphql = 'operation' === $operationType;
|
||||
if (!$graphql && $legacyOperations = $this->extractAttributes($resource, $operationType)) {
|
||||
@trigger_error(
|
||||
sprintf('Configuring "%1$s" tags without using a parent "%1$ss" tag is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3', $operationType),
|
||||
\E_USER_DEPRECATED,
|
||||
);
|
||||
|
||||
return $legacyOperations;
|
||||
}
|
||||
|
||||
$operationsParent = $graphql ? 'graphql' : "{$operationType}s";
|
||||
if (!isset($resource->{$operationsParent})) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->extractAttributes($resource->{$operationsParent}, $operationType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively transforms an attribute structure into an associative array.
|
||||
*/
|
||||
private function extractAttributes(\SimpleXMLElement $resource, string $elementName, bool $topLevel = false): array
|
||||
{
|
||||
$attributes = [];
|
||||
foreach ($resource->{$elementName} as $attribute) {
|
||||
$value = isset($attribute->attribute[0]) ? $this->extractAttributes($attribute, 'attribute') : $this->phpizeContent($attribute);
|
||||
// allow empty operations definition, like <collectionOperation name="post" />
|
||||
if ($topLevel && '' === $value) {
|
||||
$value = [];
|
||||
}
|
||||
if (isset($attribute['name'])) {
|
||||
$attributes[(string) $attribute['name']] = $value;
|
||||
} else {
|
||||
$attributes[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets metadata of a property.
|
||||
*/
|
||||
private function extractProperties(\SimpleXMLElement $resource): array
|
||||
{
|
||||
$properties = [];
|
||||
foreach ($resource->property as $property) {
|
||||
$properties[(string) $property['name']] = [
|
||||
'description' => $this->phpizeAttribute($property, 'description', 'string'),
|
||||
'readable' => $this->phpizeAttribute($property, 'readable', 'bool'),
|
||||
'writable' => $this->phpizeAttribute($property, 'writable', 'bool'),
|
||||
'readableLink' => $this->phpizeAttribute($property, 'readableLink', 'bool'),
|
||||
'writableLink' => $this->phpizeAttribute($property, 'writableLink', 'bool'),
|
||||
'required' => $this->phpizeAttribute($property, 'required', 'bool'),
|
||||
'identifier' => $this->phpizeAttribute($property, 'identifier', 'bool'),
|
||||
'iri' => $this->phpizeAttribute($property, 'iri', 'string'),
|
||||
'attributes' => $this->extractAttributes($property, 'attribute'),
|
||||
'subresource' => $property->subresource ? [
|
||||
'collection' => $this->phpizeAttribute($property->subresource, 'collection', 'bool'),
|
||||
'resourceClass' => $this->resolve($this->phpizeAttribute($property->subresource, 'resourceClass', 'string')),
|
||||
'maxDepth' => $this->phpizeAttribute($property->subresource, 'maxDepth', 'integer'),
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an XML attribute's value in a PHP value.
|
||||
*/
|
||||
private function phpizeAttribute(\SimpleXMLElement $array, string $key, string $type): bool|int|string|null
|
||||
{
|
||||
if (!isset($array[$key])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
'string' => (string) $array[$key],
|
||||
'integer' => (int) $array[$key],
|
||||
'bool' => (bool) XmlUtils::phpize($array[$key]),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an XML element's content in a PHP value.
|
||||
*/
|
||||
private function phpizeContent(\SimpleXMLElement $array)
|
||||
{
|
||||
$type = $array['type'] ?? null;
|
||||
$value = (string) $array;
|
||||
|
||||
switch ($type) {
|
||||
case 'string':
|
||||
return $value;
|
||||
case 'constant':
|
||||
return \constant($value);
|
||||
default:
|
||||
return XmlUtils::phpize($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger\LegacyResourceMetadataMerger;
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\MergingXmlExtractor;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
final class ExtractorMergingCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if ($container->hasDefinition('api_platform.metadata.extractor.xml.legacy')) {
|
||||
$definition = $container->getDefinition('api_platform.metadata.extractor.xml.legacy');
|
||||
$definition->setClass(MergingXmlExtractor::class);
|
||||
$definition->addArgument(new Reference(LegacyResourceMetadataMerger::class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,8 @@
|
|||
<argument type="service" id="Sylius\Bundle\ApiBundle\ApiPlatform\ApiResourceConfigurationMerger" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger\LegacyResourceMetadataMerger" />
|
||||
|
||||
<service
|
||||
id="api_platform.metadata.resource.metadata_factory.yaml"
|
||||
class="Sylius\Bundle\ApiBundle\ApiPlatform\Factory\MergingExtractorResourceMetadataFactory"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\ApiBundle;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\CommandDataTransformerPass;
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\ExtractorMergingCompilerPass;
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\FlattenExceptionNormalizerDecoratorCompilerPass;
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\LegacyErrorHandlingCompilerPass;
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\SyliusPriceHistoryLegacyAliasesPass;
|
||||
|
|
@ -29,5 +30,6 @@ final class SyliusApiBundle extends Bundle
|
|||
$container->addCompilerPass(new FlattenExceptionNormalizerDecoratorCompilerPass());
|
||||
$container->addCompilerPass(new LegacyErrorHandlingCompilerPass());
|
||||
$container->addCompilerPass(new SyliusPriceHistoryLegacyAliasesPass());
|
||||
$container->addCompilerPass(new ExtractorMergingCompilerPass());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<resources xmlns="https://api-platform.com/schema/metadata"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
|
||||
>
|
||||
<resource class="%sylius.model.country.class%" shortName="Country">
|
||||
<collectionOperations>
|
||||
<collectionOperation name="another_admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/updated/countries</attribute>
|
||||
<attribute name="filters">
|
||||
<attribute>test.country.id_filter</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
|
||||
<collectionOperation name="shop_get">
|
||||
<attribute name="enabled">false</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations>
|
||||
<itemOperation name="shop_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/shop/countries/new/{code}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">shop:country:read</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="admin_put">
|
||||
<attribute name="enabled">false</attribute>
|
||||
</itemOperation>
|
||||
|
||||
<itemOperation name="custom_delete">
|
||||
<attribute name="method">DELETE</attribute>
|
||||
<attribute name="path">/admin/countries/{code}</attribute>
|
||||
</itemOperation>
|
||||
</itemOperations>
|
||||
|
||||
<subresourceOperations>
|
||||
<subresourceOperation name="provinces_get_subresource">
|
||||
<attribute name="enabled">false</attribute>
|
||||
</subresourceOperation>
|
||||
|
||||
<subresourceOperation name="provinces_delete_subresource">
|
||||
<attribute name="method">DELETE</attribute>
|
||||
<attribute name="path">/admin/countries/{code}/provinces/{id}</attribute>
|
||||
</subresourceOperation>
|
||||
</subresourceOperations>
|
||||
|
||||
<property name="id" identifier="false" writable="false" />
|
||||
<property name="code" identifier="true" required="true" />
|
||||
<property name="name" writable="false" readable="false" />
|
||||
<property name="provinces" readable="false" writable="true" />
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
@ -85,6 +85,11 @@ services:
|
|||
arguments: [{id: 'exact', hostname: 'partial'}]
|
||||
tags: ['api_platform.filter']
|
||||
|
||||
test.country.id_filter:
|
||||
parent: api_platform.doctrine.orm.search_filter
|
||||
arguments: [{id: 'exact', code: 'partial'}]
|
||||
tags: ['api_platform.filter']
|
||||
|
||||
Sylius\Bundle\ApiBundle\Application\CommandHandler\FooHandler:
|
||||
tags:
|
||||
- { name: messenger.message_handler, bus: sylius.command_bus }
|
||||
|
|
|
|||
|
|
@ -18,3 +18,7 @@ Sylius\Component\Currency\Model\Currency:
|
|||
Sylius\Component\Locale\Model\Locale:
|
||||
locale:
|
||||
code: "en_US"
|
||||
|
||||
Sylius\Component\Addressing\Model\Country:
|
||||
country_US:
|
||||
code: "US"
|
||||
|
|
|
|||
|
|
@ -27,10 +27,8 @@ final class SyliusConfigMergeTest extends ApiTestCase
|
|||
$this->setUpTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_removes_api_method_to_endpoint(): void
|
||||
/** @test */
|
||||
public function it_removes_api_method_to_endpoint_with_yaml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
|
|
@ -38,60 +36,111 @@ final class SyliusConfigMergeTest extends ApiTestCase
|
|||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_to_add_new_operation(): void
|
||||
/** @test */
|
||||
public function it_removes_api_method_to_endpoint_with_xml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/countries',
|
||||
);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_add_new_operation_with_yaml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/channels-new-path',
|
||||
);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertJsonContains(['@type' => 'hydra:Collection']);
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertJsonContains(['@type' => 'hydra:Collection']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_to_add_new_filter(): void
|
||||
/** @test */
|
||||
public function it_allows_to_add_new_operation_with_xml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'DELETE',
|
||||
'/api/v2/admin/countries/US',
|
||||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_add_new_filter_with_yaml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/channels-new-path?id=20',
|
||||
);
|
||||
|
||||
$this->assertJsonContains(['hydra:totalItems' => 0]);
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertJsonContains(['hydra:totalItems' => 0]);
|
||||
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/channels-new-path',
|
||||
);
|
||||
|
||||
$this->assertJsonContains(['hydra:totalItems' => 1]);
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertJsonContains(['hydra:totalItems' => 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_merges_configs(): void
|
||||
/** @test */
|
||||
public function it_allows_to_add_new_filter_with_xml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/admin/updated/countries?id=42',
|
||||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertJsonContains(['hydra:totalItems' => 0]);
|
||||
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/admin/updated/countries',
|
||||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertJsonContains(['hydra:totalItems' => 1]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_merges_configs_with_yaml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/channels/WEB',
|
||||
);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_to_overwrite_endpoint(): void
|
||||
/** @test */
|
||||
public function it_merges_configs_with_xml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/admin/countries',
|
||||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_overwrite_endpoint_with_yaml(): void
|
||||
{
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
|
|
@ -99,7 +148,7 @@ final class SyliusConfigMergeTest extends ApiTestCase
|
|||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
|
|
@ -107,25 +156,42 @@ final class SyliusConfigMergeTest extends ApiTestCase
|
|||
['auth_bearer' => $this->JWTAdminUserToken],
|
||||
);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function it_allows_to_remove_non_crud_endpoint(): void
|
||||
/** @test */
|
||||
public function it_allows_to_overwrite_endpoint_with_xml(): void
|
||||
{
|
||||
$response =
|
||||
json_decode(
|
||||
static::createClient()
|
||||
->request(
|
||||
'PATCH',
|
||||
'/api/v2/shop/orders/TOKEN/shipments/TEST',
|
||||
)->getContent(false),
|
||||
true,
|
||||
);
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/countries/US',
|
||||
);
|
||||
|
||||
$this->assertResponseStatusCodeSame(404);
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
|
||||
static::createClient()->request(
|
||||
'GET',
|
||||
'/api/v2/shop/countries/new/US',
|
||||
);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_to_remove_non_crud_endpoint_with_yaml(): void
|
||||
{
|
||||
$response = json_decode(
|
||||
static::createClient()
|
||||
->request(
|
||||
'PATCH',
|
||||
'/api/v2/shop/orders/TOKEN/shipments/TEST',
|
||||
)->getContent(false),
|
||||
true,
|
||||
512,
|
||||
\JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
Assert::contains($response['hydra:description'], 'No route found');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DependencyInjection\Compiler;
|
||||
|
||||
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger\LegacyResourceMetadataMerger;
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\MergingXmlExtractor;
|
||||
use Sylius\Bundle\ApiBundle\DependencyInjection\Compiler\ExtractorMergingCompilerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
final class ExtractorMergingCompilerPassTest extends AbstractCompilerPassTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_does_nothing_when_container_has_no_xml_extractor(): void
|
||||
{
|
||||
$this->compile();
|
||||
|
||||
$this->assertContainerBuilderNotHasService('api_platform.metadata.extractor.xml.legacy');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_overwrites_xml_extractor(): void
|
||||
{
|
||||
$this->setDefinition(
|
||||
'api_platform.metadata.extractor.xml.legacy',
|
||||
new Definition(null, [[], new Reference('service_container')]),
|
||||
);
|
||||
$this->setDefinition(LegacyResourceMetadataMerger::class, new Definition());
|
||||
|
||||
$this->compile();
|
||||
|
||||
$this->assertContainerBuilderHasService(
|
||||
'api_platform.metadata.extractor.xml.legacy',
|
||||
MergingXmlExtractor::class,
|
||||
);
|
||||
$this->assertContainerBuilderHasServiceDefinitionWithArgument(
|
||||
'api_platform.metadata.extractor.xml.legacy',
|
||||
2,
|
||||
new Reference(LegacyResourceMetadataMerger::class),
|
||||
);
|
||||
}
|
||||
|
||||
protected function registerCompilerPass(ContainerBuilder $container): void
|
||||
{
|
||||
$container->addCompilerPass(new ExtractorMergingCompilerPass());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\ApiPlatform\Metadata\Merger\MetadataMergerInterface;
|
||||
|
||||
final class LegacyResourceMetadataMergerSpec extends ObjectBehavior
|
||||
{
|
||||
function it_is_a_metadata_merger(): void
|
||||
{
|
||||
$this->shouldImplement(MetadataMergerInterface::class);
|
||||
}
|
||||
|
||||
function it_does_nothing_when_both_new_and_old_metadata_are_epmty(): void
|
||||
{
|
||||
$this->merge([], [])->shouldReturn([]);
|
||||
}
|
||||
|
||||
function it_returns_old_metadata_as_is_when_new_metadata_is_empty(): void
|
||||
{
|
||||
$this->merge(['foo' => 'bar'], [])->shouldReturn(['foo' => 'bar']);
|
||||
}
|
||||
|
||||
function it_returns_new_metadata_as_is_when_old_metadata_is_empty(): void
|
||||
{
|
||||
$this->merge([], ['foo' => 'bar'])->shouldReturn(['foo' => 'bar']);
|
||||
}
|
||||
|
||||
function it_ignores_properties_when_merging(): void
|
||||
{
|
||||
$this->merge(
|
||||
['properties' => ['foo' => 'bar']],
|
||||
['properties' => ['foo' => 'baz']],
|
||||
)->shouldReturn(['properties' => ['foo' => 'bar']]);
|
||||
}
|
||||
|
||||
function it_adds_metadata_missing_from_old_metadata_as_they_are(): void
|
||||
{
|
||||
$this->merge(
|
||||
['foo' => 'bar'],
|
||||
['baz' => 'qux'],
|
||||
)->shouldReturn(['foo' => 'bar', 'baz' => 'qux']);
|
||||
}
|
||||
|
||||
function it_overrides_metadata_present_in_both_old_and_new_metadata(): void
|
||||
{
|
||||
$this->merge(
|
||||
['foo' => 'bar'],
|
||||
['foo' => 'baz'],
|
||||
)->shouldReturn(['foo' => 'baz']);
|
||||
}
|
||||
|
||||
function it_adds_new_collection_operations_when_old_metadata_has_none(): void
|
||||
{
|
||||
$this->merge(
|
||||
[],
|
||||
['collectionOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['collectionOperations' => ['get' => ['baz' => 'qux']]]);
|
||||
}
|
||||
|
||||
function it_adds_new_collection_operations_when_old_metadata_did_not_have_them(): void
|
||||
{
|
||||
$this->merge(
|
||||
['collectionOperations' => ['post' => ['foo' => 'bar']]],
|
||||
['collectionOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['collectionOperations' => [
|
||||
'post' => ['foo' => 'bar'],
|
||||
'get' => ['baz' => 'qux'],
|
||||
]]);
|
||||
}
|
||||
|
||||
function it_merges_collection_operations(): void
|
||||
{
|
||||
$this->merge([
|
||||
'collectionOperations' => [
|
||||
'get' => ['foo' => 'bar'],
|
||||
],
|
||||
], [
|
||||
'collectionOperations' => [
|
||||
'get' => ['baz' => 'qux'],
|
||||
],
|
||||
])->shouldReturn([
|
||||
'collectionOperations' => [
|
||||
'get' => ['foo' => 'bar', 'baz' => 'qux'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function it_adds_new_item_operations_when_old_metadata_has_none(): void
|
||||
{
|
||||
$this->merge(
|
||||
[],
|
||||
['itemOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['itemOperations' => ['get' => ['baz' => 'qux']]]);
|
||||
}
|
||||
|
||||
function it_adds_new_item_operations_when_old_metadata_did_not_have_them(): void
|
||||
{
|
||||
$this->merge(
|
||||
['itemOperations' => ['post' => ['foo' => 'bar']]],
|
||||
['itemOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['itemOperations' => [
|
||||
'post' => ['foo' => 'bar'],
|
||||
'get' => ['baz' => 'qux'],
|
||||
]]);
|
||||
}
|
||||
|
||||
function it_merges_item_operations(): void
|
||||
{
|
||||
$this->merge([
|
||||
'itemOperations' => [
|
||||
'get' => ['foo' => 'bar'],
|
||||
],
|
||||
], [
|
||||
'itemOperations' => [
|
||||
'get' => ['baz' => 'qux'],
|
||||
],
|
||||
])->shouldReturn([
|
||||
'itemOperations' => [
|
||||
'get' => ['foo' => 'bar', 'baz' => 'qux'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function it_adds_new_subresource_operations_when_old_metadata_has_none(): void
|
||||
{
|
||||
$this->merge(
|
||||
[],
|
||||
['subresourceOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['subresourceOperations' => ['get' => ['baz' => 'qux']]]);
|
||||
}
|
||||
|
||||
function it_adds_new_subresource_operations_when_old_metadata_did_not_have_them(): void
|
||||
{
|
||||
$this->merge(
|
||||
['subresourceOperations' => ['post' => ['foo' => 'bar']]],
|
||||
['subresourceOperations' => ['get' => ['baz' => 'qux']]],
|
||||
)->shouldReturn(['subresourceOperations' => [
|
||||
'post' => ['foo' => 'bar'],
|
||||
'get' => ['baz' => 'qux'],
|
||||
]]);
|
||||
}
|
||||
|
||||
function it_merges_subresource_operations(): void
|
||||
{
|
||||
$this->merge([
|
||||
'subresourceOperations' => [
|
||||
'get' => ['foo' => 'bar'],
|
||||
],
|
||||
], [
|
||||
'subresourceOperations' => [
|
||||
'get' => ['baz' => 'qux'],
|
||||
],
|
||||
])->shouldReturn([
|
||||
'subresourceOperations' => [
|
||||
'get' => ['foo' => 'bar', 'baz' => 'qux'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function it_merges_complex_metadata(): void
|
||||
{
|
||||
$this->merge([
|
||||
'validation_groups' => ['Default', 'sylius'],
|
||||
'route_prefix' => 'old',
|
||||
'collectionOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/old',
|
||||
'controller' => 'old',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
],
|
||||
'itemOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/old/{id}',
|
||||
'controller' => 'old',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
],
|
||||
'subresourceOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/old/{id}/old',
|
||||
'controller' => 'old',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
],
|
||||
'properties' => [
|
||||
'foo' => [
|
||||
'description' => 'old',
|
||||
'readable' => true,
|
||||
'writable' => true,
|
||||
'required' => true,
|
||||
'identifier' => true,
|
||||
],
|
||||
],
|
||||
], [
|
||||
'validation_groups' => 'sylius',
|
||||
'collectionOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/new',
|
||||
'controller' => 'new',
|
||||
],
|
||||
],
|
||||
'itemOperations' => [
|
||||
'post' => [
|
||||
'method' => 'POST',
|
||||
'path' => '/new/{id}',
|
||||
'controller' => 'new',
|
||||
],
|
||||
],
|
||||
'properties' => [
|
||||
'bar' => [
|
||||
'description' => 'new',
|
||||
'readable' => true,
|
||||
'writable' => true,
|
||||
'required' => false,
|
||||
'readableLink' => true,
|
||||
'identifier' => false,
|
||||
],
|
||||
],
|
||||
])->shouldIterateLike([
|
||||
'validation_groups' => 'sylius',
|
||||
'route_prefix' => 'old',
|
||||
'collectionOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/new',
|
||||
'controller' => 'new',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
],
|
||||
'itemOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/old/{id}',
|
||||
'controller' => 'old',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
'post' => [
|
||||
'method' => 'POST',
|
||||
'path' => '/new/{id}',
|
||||
'controller' => 'new',
|
||||
],
|
||||
],
|
||||
'subresourceOperations' => [
|
||||
'get' => [
|
||||
'method' => 'GET',
|
||||
'path' => '/old/{id}/old',
|
||||
'controller' => 'old',
|
||||
'defaults' => ['_controller' => 'old'],
|
||||
'requirements' => ['_format' => 'old'],
|
||||
'options' => ['foo' => 'bar'],
|
||||
'openapi_context' => ['foo' => 'bar'],
|
||||
],
|
||||
],
|
||||
'properties' => [
|
||||
'foo' => [
|
||||
'description' => 'old',
|
||||
'readable' => true,
|
||||
'writable' => true,
|
||||
'required' => true,
|
||||
'identifier' => true,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue