Skip to content

Commit 60433b3

Browse files
committed
Merge branch '2.3'
2 parents 750e9be + 66d6a98 commit 60433b3

File tree

33 files changed

+219
-47
lines changed

33 files changed

+219
-47
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ jobs:
117117
name: Run PHPStan
118118
command: |-
119119
export PATH="$PATH:$HOME/.composer/vendor/bin"
120-
phpstan analyse -c phpstan.neon -l5 --ansi src tests
120+
phpstan analyse -c phpstan.neon -l6 --ansi src tests
121121
122122
phpunit-coverage:
123123
docker:

composer.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
"willdurand/negotiation": "^2.0.3"
2727
},
2828
"require-dev": {
29-
"symfony/http-foundation": "^3.1@dev || ^4.0@dev",
30-
3129
"behat/behat": "^3.1",
3230
"behat/mink": "^1.7",
3331
"behat/mink-browserkit-driver": "^1.3.1",

features/http_cache/tags.feature

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,40 @@ Feature: Cache invalidation through HTTP Cache tags
103103
"""
104104
Then the response status code should be 200
105105
And "/relation1s,/relation1s/1,/relation2s/2,/relation2s/1" IRIs should be purged
106+
107+
Scenario: Create a Relation3 with many to many
108+
When I add "Content-Type" header equal to "application/ld+json"
109+
And I send a "POST" request to "/relation3s" with body:
110+
"""
111+
{
112+
"relation2s": ["/relation2s/1", "/relation2s/2"]
113+
}
114+
"""
115+
Then the response status code should be 201
116+
And "/relation3s,/relation2s/1,/relation2s/2" IRIs should be purged
117+
118+
Scenario: Get a Relation3
119+
When I add "Content-Type" header equal to "application/ld+json"
120+
And I send a "GET" request to "/relation3s"
121+
Then the response status code should be 200
122+
And the header "Cache-Tags" should be equal to "/relation3s/1,/relation2s/1,/relation2s/2,/relation3s"
123+
124+
Scenario: Update a collection member only
125+
When I add "Content-Type" header equal to "application/ld+json"
126+
And I send a "PUT" request to "/relation3s/1" with body:
127+
"""
128+
{
129+
"relation2s": ["/relation2s/2"]
130+
}
131+
"""
132+
Then the response status code should be 200
133+
And the header "Cache-Tags" should not exist
134+
And "/relation3s,/relation3s/1,/relation2s/2,/relation2s,/relation2s/1" IRIs should be purged
135+
136+
Scenario: Delete the collection owner
137+
When I add "Content-Type" header equal to "application/ld+json"
138+
And I send a "DELETE" request to "/relation3s/1"
139+
Then the response status code should be 204
140+
And the header "Cache-Tags" should not exist
141+
And "/relation3s,/relation3s/1,/relation2s/2" IRIs should be purged
142+

phpstan.neon

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ parameters:
1414
- '#Access to an undefined property object::\$isIdentifierComposite.#'
1515
- '#Call to an undefined method Doctrine\\Common\\Persistence\\ObjectManager::getConnection\(\)#'
1616
- '#Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject::[a-zA-Z0-9_]+\(\)#'
17-
- '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::[a-zA-Z0-9_]+\(\)#'
17+
- '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy(|.*)?::[a-zA-Z0-9_]+\(\)#'
1818
- '#Method ApiPlatform\\Core\\Tests\\Bridge\\Doctrine\\Orm\\ItemDataProviderTest::getManagerRegistry\(\) should return Doctrine\\Common\\Persistence\\ManagerRegistry but returns object\.#'
1919
- '#Method ApiPlatform\\Core\\Tests\\Bridge\\Doctrine\\Orm\\Util\\IdentifierManagerTraitTest::getObjectManager\(\) should return Doctrine\\Common\\Persistence\\ObjectManager but returns object\.#'
20+
- '#Parameter \#1 \$function of function call_user_func expects callable, .+ given\.#'
21+
- '#Parameter \#1 \$classes of class ApiPlatform\\Core\\Metadata\\Resource\\ResourceNameCollection constructor expects array<string>, array<int, int\|string> given\.#'
22+
- '#Method ApiPlatform\\Core\\Util\\RequestParser::parseRequestParams\(\) should return array but returns array\|false\.#'
2023
# Temporary fix while the PHPStan extension for Prophecy isn't compatible with 0.10
2124
- '#Parameter .* expects .*, .*object.* given\.#'
2225
- '#Parameter \#[0-9] \$filterLocator of class ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\FilterExtension constructor expects ApiPlatform\\Core\\Api\\FilterCollection|Psr\\Container\\ContainerInterface(\|null)?, ArrayObject given\.#'
@@ -26,6 +29,7 @@ parameters:
2629
- '#Strict comparison using === between null and int will always evaluate to false\.#'
2730
- '#Strict comparison using !== between null and null will always evaluate to false\.#'
2831
- '#Class ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Entity\\DummyBis not found.#'
32+
- '#Method ApiPlatform\\Core\\(Serializer\\Abstract|JsonApi\\Serializer\\)ItemNormalizer::normalizeRelation\(\) should return array\|string but returns array\|bool\|float\|int\|string\.#'
2933

3034
# Expected, due to deprecations
3135
- '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::getResult\(\) invoked with 4 parameters, 1 required\.#'

src/Annotation/ApiFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class ApiFilter
3737
public $strategy;
3838

3939
/**
40-
* @var string
40+
* @var string|FilterInterface
4141
*/
4242
public $filterClass;
4343

src/Annotation/AttributesHydratorTrait.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private function hydrateAttributes(array $values)
5050
}
5151

5252
foreach ($values as $key => $value) {
53+
$key = (string) $key;
5354
if (!property_exists($this, $key)) {
5455
throw new InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, self::class));
5556
}

src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,6 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt
167167
if ($inAttributes = isset($normalizationContext[AbstractNormalizer::ATTRIBUTES][$association])) {
168168
// prepare the child context
169169
$normalizationContext[AbstractNormalizer::ATTRIBUTES] = $normalizationContext[AbstractNormalizer::ATTRIBUTES][$association];
170-
} else {
171-
unset($normalizationContext[AbstractNormalizer::ATTRIBUTES]);
172170
}
173171
} else {
174172
$inAttributes = null;

src/Bridge/Doctrine/Orm/Filter/DateFilter.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
9494
}
9595

9696
$nullManagement = $this->properties[$property] ?? null;
97-
$type = $this->getDoctrineFieldType($property, $resourceClass);
97+
$type = (string) $this->getDoctrineFieldType($property, $resourceClass);
9898

9999
if (self::EXCLUDE_NULL === $nullManagement) {
100100
$queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)));
@@ -160,6 +160,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
160160
*/
161161
protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null, $type = null)
162162
{
163+
$type = (string) $type;
163164
try {
164165
$value = false === strpos($type, '_immutable') ? new \DateTime($value) : new \DateTimeImmutable($value);
165166
} catch (\Exception $e) {
@@ -206,7 +207,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
206207
*/
207208
protected function isDateField(string $property, string $resourceClass): bool
208209
{
209-
return isset(self::DOCTRINE_DATE_TYPES[$this->getDoctrineFieldType($property, $resourceClass)]);
210+
return isset(self::DOCTRINE_DATE_TYPES[(string) $this->getDoctrineFieldType($property, $resourceClass)]);
210211
}
211212

212213
/**

src/Bridge/Doctrine/Orm/Filter/NumericFilter.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function getDescription(string $resourceClass): array
6363

6464
$description[$property] = [
6565
'property' => $property,
66-
'type' => $this->getType($this->getDoctrineFieldType($property, $resourceClass)),
66+
'type' => $this->getType((string) $this->getDoctrineFieldType($property, $resourceClass)),
6767
'required' => false,
6868
];
6969
}
@@ -114,7 +114,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
114114
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
115115
}
116116

117-
if (!isset(self::DOCTRINE_NUMERIC_TYPES[$this->getDoctrineFieldType($property, $resourceClass)])) {
117+
if (!isset(self::DOCTRINE_NUMERIC_TYPES[(string) $this->getDoctrineFieldType($property, $resourceClass)])) {
118118
$this->logger->notice('Invalid filter ignored', [
119119
'exception' => new InvalidArgumentException(sprintf('The field "%s" of class "%s" is not a doctrine numeric type.', $field, $resourceClass)),
120120
]);
@@ -126,7 +126,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
126126

127127
$queryBuilder
128128
->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter))
129-
->setParameter($valueParameter, $value, $this->getDoctrineFieldType($property, $resourceClass));
129+
->setParameter($valueParameter, $value, (string) $this->getDoctrineFieldType($property, $resourceClass));
130130
}
131131

132132
/**
@@ -137,6 +137,6 @@ protected function isNumericField(string $property, string $resourceClass): bool
137137
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
138138
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
139139

140-
return isset(self::DOCTRINE_NUMERIC_TYPES[$metadata->getTypeOfField($propertyParts['field'])]);
140+
return isset(self::DOCTRINE_NUMERIC_TYPES[(string) $metadata->getTypeOfField($propertyParts['field'])]);
141141
}
142142
}

src/Bridge/Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public function getDescription(string $resourceClass): array
9898
}
9999

100100
if ($metadata->hasField($field)) {
101-
$typeOfField = $this->getType($metadata->getTypeOfField($field));
101+
$typeOfField = $this->getType((string) $metadata->getTypeOfField($field));
102102
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
103103
$filterParameterNames = [$property];
104104

src/Bridge/Doctrine/Orm/ItemDataProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null
6969
{
7070
$manager = $this->managerRegistry->getManagerForClass($resourceClass);
7171

72-
if (!($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false)) {
72+
if (!\is_array($id) && !($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false)) {
7373
$id = $this->normalizeIdentifiers($id, $manager, $resourceClass);
7474
}
7575

@@ -87,7 +87,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null
8787
$queryNameGenerator = new QueryNameGenerator();
8888
$doctrineClassMetadata = $manager->getClassMetadata($resourceClass);
8989

90-
$this->addWhereForIdentifiers($id, $queryBuilder, $doctrineClassMetadata);
90+
$this->addWhereForIdentifiers((array) $id, $queryBuilder, $doctrineClassMetadata);
9191

9292
foreach ($this->itemExtensions as $extension) {
9393
$extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $id, $operationName, $context);

src/Bridge/Doctrine/Orm/SubresourceDataProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private function buildQuery(array $identifiers, array $context, QueryNameGenerat
191191
foreach ($normalizedIdentifiers as $key => $value) {
192192
$placeholder = $queryNameGenerator->generateParameterName($key);
193193
$qb->andWhere("$alias.$key = :$placeholder");
194-
$topQueryBuilder->setParameter($placeholder, $value, $classMetadata->getTypeOfField($key));
194+
$topQueryBuilder->setParameter($placeholder, $value, (string) $classMetadata->getTypeOfField($key));
195195
}
196196

197197
// Recurse queries

src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
6767
{
6868
$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats);
6969
$data = $this->documentationNormalizer->normalize($documentation);
70-
$content = $input->getOption('yaml') ? Yaml::dump($data, 6, 4, Yaml::DUMP_OBJECT_AS_MAP) : json_encode($data, JSON_PRETTY_PRINT);
71-
if (!empty($input->getOption('output'))) {
72-
file_put_contents($input->getOption('output'), $content);
70+
$content = $input->getOption('yaml') ? Yaml::dump($data, 6, 4, Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE) : (json_encode($data, JSON_PRETTY_PRINT) ?: '');
71+
if (!empty($filename = $input->getOption('output')) && \is_string($filename)) {
72+
file_put_contents($filename, $content);
7373
$output->writeln(
74-
sprintf('Data written to %s', $input->getOption('output'))
74+
sprintf('Data written to %s', $filename)
7575
);
7676
} else {
7777
$output->writeln($content);

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ public function load(array $configs, ContainerBuilder $container)
130130
$useDoctrine = isset($bundles['DoctrineBundle']) && class_exists(Version::class);
131131

132132
$this->registerMetadataConfiguration($container, $config, $loader);
133-
$this->registerOAuthConfiguration($container, $config, $loader);
134-
$this->registerApiKeysConfiguration($container, $config, $loader);
133+
$this->registerOAuthConfiguration($container, $config);
134+
$this->registerApiKeysConfiguration($container, $config);
135135
$this->registerSwaggerConfiguration($container, $config, $loader);
136136
$this->registerJsonApiConfiguration($formats, $loader);
137137
$this->registerJsonLdConfiguration($container, $formats, $loader, $config['enable_docs']);
@@ -141,9 +141,9 @@ public function load(array $configs, ContainerBuilder $container)
141141
$this->registerBundlesConfiguration($bundles, $config, $loader, $useDoctrine);
142142
$this->registerCacheConfiguration($container);
143143
$this->registerDoctrineExtensionConfiguration($container, $config, $useDoctrine);
144-
$this->registerHttpCache($container, $config, $loader, $useDoctrine);
145-
$this->registerValidatorConfiguration($container, $config, $loader);
146-
$this->registerDataCollector($container, $config, $loader);
144+
$this->registerHttpCacheConfiguration($container, $config, $loader, $useDoctrine);
145+
$this->registerValidatorConfiguration($container, $config);
146+
$this->registerDataCollectorConfiguration($config, $loader);
147147
}
148148

149149
/**
@@ -291,7 +291,7 @@ private function getResourcesToWatch(ContainerBuilder $container, array $resourc
291291
/**
292292
* Registers the OAuth configuration.
293293
*/
294-
private function registerOAuthConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader)
294+
private function registerOAuthConfiguration(ContainerBuilder $container, array $config)
295295
{
296296
if (!$config['oauth']) {
297297
return;
@@ -310,7 +310,7 @@ private function registerOAuthConfiguration(ContainerBuilder $container, array $
310310
/**
311311
* Registers the api keys configuration.
312312
*/
313-
private function registerApiKeysConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader)
313+
private function registerApiKeysConfiguration(ContainerBuilder $container, array $config)
314314
{
315315
$container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']);
316316
}
@@ -457,7 +457,7 @@ private function registerDoctrineExtensionConfiguration(ContainerBuilder $contai
457457
$container->removeDefinition('api_platform.doctrine.orm.query_extension.filter_eager_loading');
458458
}
459459

460-
private function registerHttpCache(ContainerBuilder $container, array $config, XmlFileLoader $loader, bool $useDoctrine)
460+
private function registerHttpCacheConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, bool $useDoctrine)
461461
{
462462
$loader->load('http_cache.xml');
463463

@@ -501,7 +501,7 @@ private function getFormats(array $configFormats): array
501501
/**
502502
* Registers the Validator configuration.
503503
*/
504-
private function registerValidatorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader)
504+
private function registerValidatorConfiguration(ContainerBuilder $container, array $config)
505505
{
506506
if (!$config['validator']) {
507507
return;
@@ -513,7 +513,7 @@ private function registerValidatorConfiguration(ContainerBuilder $container, arr
513513
/**
514514
* Registers the DataCollector configuration.
515515
*/
516-
private function registerDataCollector(ContainerBuilder $container, array $config, XmlFileLoader $loader)
516+
private function registerDataCollectorConfiguration(array $config, XmlFileLoader $loader)
517517
{
518518
if (!$config['enable_profiler']) {
519519
return;

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ final class ApiLoader extends Loader
5757

5858
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $container, array $formats, array $resourceClassDirectories = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $graphqlEnabled = false, bool $entrypointEnabled = true, bool $docsEnabled = true)
5959
{
60-
$this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing')));
60+
/** @var string[]|string $paths */
61+
$paths = $kernel->locateResource('@ApiPlatformBundle/Resources/config/routing');
62+
$this->fileLoader = new XmlFileLoader(new FileLocator($paths));
6163
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
6264
$this->resourceMetadataFactory = $resourceMetadataFactory;
6365
$this->operationPathResolver = $operationPathResolver;

src/Bridge/Symfony/Routing/IriConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public function getItemFromIri(string $iri, array $context = [])
9696
}
9797

9898
if (isset($attributes['subresource_operation_name'])) {
99-
if ($item = $this->getSubresourceData($identifiers, $attributes, $context)) {
99+
if (($item = $this->getSubresourceData($identifiers, $attributes, $context)) && !\is_array($item)) {
100100
return $item;
101101
}
102102

src/DataProvider/OperationDataProviderTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private function getItemData($identifiers, array $attributes, array $context)
6767
*
6868
* @throws RuntimeException
6969
*
70-
* @return object|null
70+
* @return array|object|null
7171
*/
7272
private function getSubresourceData($identifiers, array $attributes, array $context)
7373
{

src/EventListener/AddFormatListener.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public function onKernelRequest(GetResponseEvent $event)
8484
}
8585

8686
// First, try to guess the format from the Accept header
87+
/** @var string|null $accept */
8788
$accept = $request->headers->get('Accept');
8889
if (null !== $accept) {
8990
if (null === $acceptHeader = $this->negotiator->getBest($accept, $mimeTypes)) {

src/Filter/QueryParameterValidateListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private function isRequiredFilterValid(string $name, Request $request): bool
8686
return false;
8787
}
8888

89-
$rootName = array_keys($matches)[0] ?? '';
89+
$rootName = (string) (array_keys($matches)[0] ?? null);
9090
if (!$rootName) {
9191
return false;
9292
}

src/GraphQl/Action/EntrypointAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private function parseRequest(Request $request): array
8383
}
8484

8585
if ('json' === $request->getContentType()) {
86-
$input = \json_decode($request->getContent(), true);
86+
$input = \json_decode((string) $request->getContent(), true);
8787

8888
if (isset($input['query'])) {
8989
$query = $input['query'];

src/GraphQl/Serializer/ItemNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu
7979

8080
if (($context['api_denormalize'] ?? false) && false !== ($indexId = array_search('id', $allowedAttributes, true))) {
8181
$allowedAttributes[] = '_id';
82-
array_splice($allowedAttributes, $indexId, 1);
82+
array_splice($allowedAttributes, (int) $indexId, 1);
8383
}
8484

8585
return $allowedAttributes;

0 commit comments

Comments
 (0)