diff --git a/.travis.yml b/.travis.yml index 9ead294b1bd..b85854e77eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -209,7 +209,7 @@ jobs: - *validate-openapi-v3-yaml - php: '7.3' - env: SYMFONY_DEPRECATIONS_HELPER=0 + env: SYMFONY_DEPRECATIONS_HELPER=max[total]=0 before_install: - *install-mongodb-php-extension - *disable-xdebug-php-extension @@ -229,5 +229,5 @@ jobs: - *validate-openapi-v3-yaml allow_failures: - - env: SYMFONY_DEPRECATIONS_HELPER=0 + - env: SYMFONY_DEPRECATIONS_HELPER=max[total]=0 fast_finish: true diff --git a/composer.json b/composer.json index c79f0a187bf..7d87a1e2d37 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "symfony/http-kernel": "^3.4 || ^4.0", "symfony/property-access": "^3.4 || ^4.0", "symfony/property-info": "^3.4 || ^4.0", - "symfony/serializer": "^4.2.6", + "symfony/serializer": "^4.3", "symfony/web-link": "^4.1", "willdurand/negotiation": "^2.0.3" }, @@ -44,7 +44,7 @@ "guzzlehttp/guzzle": "^6.0", "jangregor/phpstan-prophecy": "^0.3", "justinrainbow/json-schema": "^5.0", - "nelmio/api-doc-bundle": "^2.13.3", + "nelmio/api-doc-bundle": "^2.13.4", "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", "phpdocumentor/type-resolver": "^0.3 || ^0.4", "phpspec/prophecy": "^1.8", @@ -71,10 +71,10 @@ "symfony/expression-language": "^3.4 || ^4.0", "symfony/finder": "^3.4 || ^4.0", "symfony/form": "^3.4 || ^4.0", - "symfony/framework-bundle": "^4.2", + "symfony/framework-bundle": "^4.3", "symfony/mercure-bundle": "*", - "symfony/messenger": "^4.2", - "symfony/phpunit-bridge": "^4.3", + "symfony/messenger": "^4.3", + "symfony/phpunit-bridge": "^4.3.1", "symfony/routing": "^3.4 || ^4.0", "symfony/security-bundle": "^3.4 || ^4.0", "symfony/security-core": "^3.4 || ^4.0", @@ -86,8 +86,7 @@ }, "conflict": { "doctrine/common": "<2.7", - "doctrine/mongodb-odm": "<2.0", - "symfony/messenger": ">=4.3" + "doctrine/mongodb-odm": "<2.0" }, "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", diff --git a/features/bootstrap/HydraContext.php b/features/bootstrap/HydraContext.php index 84edc9ff52b..9b995f7c359 100644 --- a/features/bootstrap/HydraContext.php +++ b/features/bootstrap/HydraContext.php @@ -230,7 +230,7 @@ private function getPropertyInfo(string $propertyName, string $className): stdCl } } - throw new \InvalidArgumentException(sprintf('Property "%s" of class "%s" does\'nt exist', $propertyName, $className)); + throw new \InvalidArgumentException(sprintf('Property "%s" of class "%s" doesn\'t exist', $propertyName, $className)); } /** diff --git a/features/graphql/mutation.feature b/features/graphql/mutation.feature index 449cf421fba..10036262caf 100644 --- a/features/graphql/mutation.feature +++ b/features/graphql/mutation.feature @@ -324,7 +324,7 @@ Feature: GraphQL mutation support When I send the following GraphQL request: """ mutation { - createDummy(input: {_id: 12, name: "", foo: [], clientMutationId: "myId"}) { + createDummy(input: {name: "", foo: [], clientMutationId: "myId"}) { clientMutationId } } diff --git a/features/hydra/docs.feature b/features/hydra/docs.feature index e77d86b4769..9b8fa98f57c 100644 --- a/features/hydra/docs.feature +++ b/features/hydra/docs.feature @@ -56,8 +56,6 @@ Feature: Documentation support And the value of the node "hydra:title" of the Hydra class "Dummy" is "Dummy" And the value of the node "hydra:description" of the Hydra class "Dummy" is "Dummy." # Properties - And "id" property is readable for Hydra class "Dummy" - And "id" property is writable for Hydra class "Dummy" And "name" property is readable for Hydra class "Dummy" And "name" property is writable for Hydra class "Dummy" And "name" property is required for Hydra class "Dummy" diff --git a/features/jsonld/context.feature b/features/jsonld/context.feature index 42bacdca557..45f324a742f 100644 --- a/features/jsonld/context.feature +++ b/features/jsonld/context.feature @@ -41,7 +41,6 @@ Feature: JSON-LD contexts generation "jsonData": "Dummy/jsonData", "arrayData": "Dummy/arrayData", "nameConverted": "Dummy/nameConverted", - "id": "Dummy/id", "name": "http://schema.org/name", "alias": "https://schema.org/alternateName", "foo": "Dummy/foo" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index acf3d7eb68d..60efa0cd90f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,10 +34,10 @@ parameters: - '#Call to an undefined method Doctrine\\Common\\Persistence\\ObjectManager::getConnection\(\)#' # https://github.com/symfony/symfony/pull/31903 - - message: '#Access to an undefined property object::\$headers\.#' + message: '#Access to property \$headers on an unknown class Symfony\\Component\\HttpKernel\\Response\.#' path: %currentWorkingDirectory%/tests/Bridge/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php - - message: '#Call to an undefined method object::getStatusCode\(\)\.#' + message: '#Call to method getStatusCode\(\) on an unknown class Symfony\\Component\\HttpKernel\\Response\.#' path: %currentWorkingDirectory%/tests/Bridge/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php - '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, .+ given\.#' - '#Parameter \#1 \$exception of static method Symfony\\Component\\Debug\\Exception\\FlattenException::create\(\) expects Exception, Symfony\\Component\\Serializer\\Exception\\ExceptionInterface given\.#' diff --git a/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php b/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php index e9fdac022a3..2d37e04c560 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php +++ b/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php @@ -16,7 +16,9 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Persistence\Mapping\MappingException; use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; @@ -29,7 +31,7 @@ * @author Kévin Dunglas * @author Alan Poulain */ -final class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface +final class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface { private $objectManager; @@ -43,9 +45,7 @@ public function __construct(ObjectManager $objectManager) */ public function getProperties($class, array $context = []) { - try { - $metadata = $this->objectManager->getClassMetadata($class); - } catch (MappingException $exception) { + if (null === $metadata = $this->getMetadata($class)) { return null; } @@ -57,9 +57,7 @@ public function getProperties($class, array $context = []) */ public function getTypes($class, $property, array $context = []) { - try { - $metadata = $this->objectManager->getClassMetadata($class); - } catch (MappingException $exception) { + if (null === $metadata = $this->getMetadata($class)) { return null; } @@ -111,6 +109,39 @@ public function getTypes($class, $property, array $context = []) } } + /** + * {@inheritdoc} + */ + public function isReadable($class, $property, array $context = []): ?bool + { + return null; + } + + /** + * {@inheritdoc} + */ + public function isWritable($class, $property, array $context = []): ?bool + { + if ( + null === ($metadata = $this->getMetadata($class)) + || ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType + || !\in_array($property, $metadata->getIdentifierFieldNames(), true) + ) { + return null; + } + + return false; + } + + private function getMetadata(string $class): ?ClassMetadata + { + try { + return $this->objectManager->getClassMetadata($class); + } catch (MappingException $exception) { + return null; + } + } + /** * Gets the corresponding built-in PHP type. */ diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml index 0b4c6e1bdad..54f4f4d1b79 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml @@ -14,7 +14,7 @@ %api_platform.formats% - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml b/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml index 5117f5e9d41..d50ec0e3d69 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig b/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig index e5983280b4e..79d78a25bf2 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig +++ b/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig @@ -164,11 +164,13 @@ - {% for key, value in collector.resourceMetadata.attributes if key != 'filters' %} - - {{ key }} - {{- profiler_dump(value, 2) -}} - + {% for key, value in collector.resourceMetadata.attributes %} + {% if key != 'filters' %} + + {{ key }} + {{- profiler_dump(value, 2) -}} + + {% endif %} {% endfor %} diff --git a/src/Bridge/Symfony/Messenger/DataPersister.php b/src/Bridge/Symfony/Messenger/DataPersister.php index fb059983eb5..0c3d0d9f957 100644 --- a/src/Bridge/Symfony/Messenger/DataPersister.php +++ b/src/Bridge/Symfony/Messenger/DataPersister.php @@ -88,6 +88,6 @@ public function persist($data, array $context = []) */ public function remove($data, array $context = []) { - $this->messageBus->dispatch(new Envelope($data, new RemoveStamp())); + $this->messageBus->dispatch(new Envelope($data, [new RemoveStamp()])); } } diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php index 61a2b2984e0..2f5c577bc1a 100644 --- a/src/EventListener/ExceptionListener.php +++ b/src/EventListener/ExceptionListener.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\EventListener; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\EventListener\ExceptionListener as BaseExceptionListener; @@ -23,8 +24,15 @@ * @author Samuel ROZE * @author Kévin Dunglas */ -final class ExceptionListener extends BaseExceptionListener +final class ExceptionListener { + private $exceptionListener; + + public function __construct($controller, LoggerInterface $logger = null) + { + $this->exceptionListener = new BaseExceptionListener($controller, $logger); + } + public function onKernelException(GetResponseForExceptionEvent $event): void { $request = $event->getRequest(); @@ -36,6 +44,6 @@ public function onKernelException(GetResponseForExceptionEvent $event): void return; } - parent::onKernelException($event); + $this->exceptionListener->onKernelException($event); } } diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index b11ee52a5af..c0bf199ef41 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -396,7 +396,7 @@ protected function denormalizeCollection(string $attribute, PropertyMetadata $pr ); } - $values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $this->createChildContext($context, $attribute)); + $values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $this->createChildContext($context, $attribute, $format)); } return $values; @@ -531,7 +531,7 @@ protected function getAttributeValue($object, $attribute, $format = null, array $this->resourceClassResolver->isResourceClass($className) ) { $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); - $childContext = $this->createChildContext($context, $attribute); + $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); @@ -543,7 +543,7 @@ protected function getAttributeValue($object, $attribute, $format = null, array $this->resourceClassResolver->isResourceClass($className) ) { $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className, true); - $childContext = $this->createChildContext($context, $attribute); + $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); @@ -660,7 +660,7 @@ private function createAttributeValue($attribute, $value, $format = null, array $this->resourceClassResolver->isResourceClass($className) ) { $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className); - $childContext = $this->createChildContext($context, $attribute); + $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext); diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index f1e18b6d24d..68a7c4679a1 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -30,8 +30,6 @@ /** * Generic item normalizer. * - * @final - * * @author Kévin Dunglas */ class ItemNormalizer extends AbstractItemNormalizer diff --git a/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractorTest.php b/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractorTest.php index 2106410577a..2bf5c361644 100644 --- a/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractorTest.php +++ b/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractorTest.php @@ -18,6 +18,7 @@ use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineDummy; use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineEmbeddable; use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineFooType; +use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineGeneratedValue; use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineRelation; use ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures\DoctrineWithEmbedded; use Doctrine\Common\Collections\Collection; @@ -193,6 +194,15 @@ public function testGetTypesCatchException(): void $this->assertNull($this->createExtractor()->getTypes('Not\Exist', 'baz')); } + public function testGeneratedValueNotWritable() + { + $extractor = $this->createExtractor(); + $this->assertFalse($extractor->isWritable(DoctrineGeneratedValue::class, 'id')); + $this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'id')); + $this->assertNull($extractor->isWritable(DoctrineGeneratedValue::class, 'foo')); + $this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'foo')); + } + private function createExtractor(): DoctrineExtractor { $config = DoctrineMongoDbOdmSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); diff --git a/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php b/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php new file mode 100644 index 00000000000..46ece808957 --- /dev/null +++ b/tests/Bridge/Doctrine/MongoDbOdm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Tests\Bridge\Doctrine\MongoDbOdm\PropertyInfo\Fixtures; + +use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; +use Doctrine\ODM\MongoDB\Mapping\Annotations\Field; +use Doctrine\ODM\MongoDB\Mapping\Annotations\Id; + +/** + * @Document + * + * @author Kévin Dunglas + * @author Alan Poulain + */ +class DoctrineGeneratedValue +{ + /** + * @Id(strategy="INCREMENT", type="integer") + */ + public $id; + + /** + * @Field + */ + public $foo; +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index d2361c1d782..c6a401fae48 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -518,7 +518,7 @@ public function testDisabledSwaggerUIAndRedoc() public function testDisabledMessenger() { $containerBuilderProphecy = $this->getBaseContainerBuilderProphecy(); - $containerBuilderProphecy->setAlias('api_platform.message_bus', 'message_bus')->shouldNotBeCalled(); + $containerBuilderProphecy->setAlias('api_platform.message_bus', 'messenger.default_bus')->shouldNotBeCalled(); $containerBuilderProphecy->setDefinition('api_platform.messenger.data_persister', Argument::type(Definition::class))->shouldNotBeCalled(); $containerBuilderProphecy->setDefinition('api_platform.messenger.data_transformer', Argument::type(Definition::class))->shouldNotBeCalled(); $containerBuilder = $containerBuilderProphecy->reveal(); @@ -1194,7 +1194,7 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo $aliases = [ 'api_platform.http_cache.purger' => 'api_platform.http_cache.purger.varnish', - 'api_platform.message_bus' => 'message_bus', + 'api_platform.message_bus' => 'messenger.default_bus', EagerLoadingExtension::class => 'api_platform.doctrine.orm.query_extension.eager_loading', FilterExtension::class => 'api_platform.doctrine.orm.query_extension.filter', FilterEagerLoadingExtension::class => 'api_platform.doctrine.orm.query_extension.filter_eager_loading', diff --git a/tests/Bridge/Symfony/Messenger/DataPersisterTest.php b/tests/Bridge/Symfony/Messenger/DataPersisterTest.php index fc7bef5ffdb..0ee7becbbf9 100644 --- a/tests/Bridge/Symfony/Messenger/DataPersisterTest.php +++ b/tests/Bridge/Symfony/Messenger/DataPersisterTest.php @@ -80,7 +80,7 @@ public function testHandle() $dummy = new Dummy(); $messageBus = $this->prophesize(MessageBusInterface::class); - $messageBus->dispatch($dummy)->willReturn(new Envelope($dummy, new HandledStamp($dummy, 'DummyHandler::__invoke')))->shouldBeCalled(); + $messageBus->dispatch($dummy)->willReturn(new Envelope($dummy, [new HandledStamp($dummy, 'DummyHandler::__invoke')]))->shouldBeCalled(); $dataPersister = new DataPersister($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), $messageBus->reveal()); $this->assertSame($dummy, $dataPersister->persist($dummy)); diff --git a/tests/Fixtures/app/AppKernel.php b/tests/Fixtures/app/AppKernel.php index f7e09ae561b..feb981f694c 100644 --- a/tests/Fixtures/app/AppKernel.php +++ b/tests/Fixtures/app/AppKernel.php @@ -29,6 +29,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\RouteCollectionBuilder; +use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -96,10 +97,11 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $loader->load(__DIR__."/config/config_{$this->getEnvironment()}.yml"); + $alg = class_exists(SodiumPasswordEncoder::class) && SodiumPasswordEncoder::isSupported() ? 'auto' : 'bcrypt'; $securityConfig = [ 'encoders' => [ - User::class => 'bcrypt', - UserDocument::class => 'bcrypt', + User::class => $alg, + UserDocument::class => $alg, // Don't use plaintext in production! UserInterface::class => 'plaintext', ], diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index 23eb8cda1f2..147ab132550 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -8,8 +8,6 @@ framework: session: storage_id: 'session.storage.mock_file' form: ~ # For FOSUser - templating: - engines: ['twig'] # For Swagger UI profiler: enabled: true collect: false