From 89d9a2bf3b0885bf36996ab4fd7d6d57a6709210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Tue, 26 Sep 2023 20:43:41 +0200 Subject: [PATCH] code challenge 2 solution --- CODING-CHALLENGE-3.md | 14 + composer.json | 5 + composer.lock | 727 ++++++++++++------ config/packages/framework.yaml | 3 + src/Controller/Attendee/ListController.php | 12 +- src/Controller/Attendee/ReadController.php | 10 +- src/Controller/Workshop/ListController.php | 12 +- src/Controller/Workshop/ReadController.php | 10 +- src/Entity/Attendee.php | 12 - src/Entity/Workshop.php | 19 +- src/Serializer/AttendeeNormalizer.php | 50 ++ src/Serializer/WorkshopNormalizer.php | 50 ++ ..._test_it_should_list_all_attendees__1.json | 12 +- ..._it_should_show_requested_attendee__1.json | 12 +- .../Attendee/fixtures/list_attendee.yaml | 8 +- .../Attendee/fixtures/read_attendee.yaml | 8 +- ..._test_it_should_list_all_workshops__1.json | 12 +- ..._it_should_show_requested_workshop__1.json | 12 +- .../Workshop/fixtures/list_workshop.yaml | 6 + .../Workshop/fixtures/read_workshop.yaml | 6 + 20 files changed, 696 insertions(+), 304 deletions(-) create mode 100644 CODING-CHALLENGE-3.md create mode 100644 src/Serializer/AttendeeNormalizer.php create mode 100644 src/Serializer/WorkshopNormalizer.php diff --git a/CODING-CHALLENGE-3.md b/CODING-CHALLENGE-3.md new file mode 100644 index 0000000..4aca9f8 --- /dev/null +++ b/CODING-CHALLENGE-3.md @@ -0,0 +1,14 @@ +# RESTful Webservices in Symfony + +## Coding Challenge 3 - Pagination + +### Tasks + +- add pagination to your workshop and attendee list actions using a `page` and `size` query parameter + +### Solution + +- introduce query params `page` and `size` in your ListControllers +- use the Doctrine Paginator to paginate the workshop and attendee lists +- implement a `PaginatedCollection` object and add the properties `items`, `total` and `count` +- implement a `PaginatedCollectionFactory` to encapsulate your pagination logic diff --git a/composer.json b/composer.json index ae0734c..031825a 100644 --- a/composer.json +++ b/composer.json @@ -10,13 +10,18 @@ "doctrine/doctrine-bundle": "^2.10", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.16", + "phpdocumentor/reflection-docblock": "^5.3", + "phpstan/phpdoc-parser": "^1.24", "ramsey/uuid": "*", "ramsey/uuid-doctrine": "^2.0", "symfony/console": "6.3.*", "symfony/dotenv": "6.3.*", "symfony/flex": "^2", "symfony/framework-bundle": "6.3.*", + "symfony/property-access": "6.3.*", + "symfony/property-info": "6.3.*", "symfony/runtime": "6.3.*", + "symfony/serializer": "6.3.*", "symfony/yaml": "6.3.*", "webmozart/assert": "^1.11" }, diff --git a/composer.lock b/composer.lock index ec3fbe7..e49b66b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae128b05edc1c2756d495e5792c4bdcf", + "content-hash": "81fadf3eb9b9ecfbe68ef98a2e841210", "packages": [ { "name": "brick/math", @@ -1375,6 +1375,221 @@ }, "time": "2022-05-23T21:33:49+00:00" }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" + }, + "time": "2023-08-12T11:01:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.24.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "bcad8d995980440892759db0c32acae7c8e79442" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + }, + "time": "2023-09-26T12:28:12+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -3598,6 +3813,166 @@ ], "time": "2023-08-16T06:22:46+00:00" }, + { + "name": "symfony/property-access", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/2dc4f9da444b8f8ff592e95d570caad67924f1d0", + "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/property-info": "^5.4|^6.0" + }, + "require-dev": { + "symfony/cache": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v6.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-13T15:26:11+00:00" + }, + { + "name": "symfony/property-info", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4|^2", + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-19T08:06:44+00:00" + }, { "name": "symfony/routing", "version": "v6.3.3", @@ -3758,7 +4133,101 @@ "type": "tidelift" } ], - "time": "2023-07-16T17:05:46+00:00" + "time": "2023-07-16T17:05:46+00:00" + }, + { + "name": "symfony/serializer", + "version": "v6.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/96d28a58d5a128bf77c54534b380eb7c92c8f846", + "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<5.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4.24|>=6,<6.2.11", + "symfony/uid": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12|^2", + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4.24|^6.2.11", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v6.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-24T14:35:28+00:00" }, { "name": "symfony/service-contracts", @@ -7026,260 +7495,6 @@ ], "time": "2023-08-07T10:39:22+00:00" }, - { - "name": "symfony/property-access", - "version": "v6.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-access.git", - "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/2dc4f9da444b8f8ff592e95d570caad67924f1d0", - "reference": "2dc4f9da444b8f8ff592e95d570caad67924f1d0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/property-info": "^5.4|^6.0" - }, - "require-dev": { - "symfony/cache": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyAccess\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides functions to read and write from/to an object or array using a simple string notation", - "homepage": "https://symfony.com", - "keywords": [ - "access", - "array", - "extraction", - "index", - "injection", - "object", - "property", - "property-path", - "reflection" - ], - "support": { - "source": "https://github.com/symfony/property-access/tree/v6.3.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-13T15:26:11+00:00" - }, - { - "name": "symfony/property-info", - "version": "v6.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/property-info.git", - "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", - "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/string": "^5.4|^6.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<5.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4|^2", - "phpdocumentor/reflection-docblock": "^5.2", - "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\PropertyInfo\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "dunglas@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", - "homepage": "https://symfony.com", - "keywords": [ - "doctrine", - "phpdoc", - "property", - "symfony", - "type", - "validator" - ], - "support": { - "source": "https://github.com/symfony/property-info/tree/v6.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-05-19T08:06:44+00:00" - }, - { - "name": "symfony/serializer", - "version": "v6.3.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/serializer.git", - "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/96d28a58d5a128bf77c54534b380eb7c92c8f846", - "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "doctrine/annotations": "<1.12", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<5.4", - "symfony/property-access": "<5.4", - "symfony/property-info": "<5.4.24|>=6,<6.2.11", - "symfony/uid": "<5.4", - "symfony/yaml": "<5.4" - }, - "require-dev": { - "doctrine/annotations": "^1.12|^2", - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4.24|^6.2.11", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0", - "symfony/var-exporter": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Serializer\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/serializer/tree/v6.3.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-08-24T14:35:28+00:00" - }, { "name": "theofidry/alice-data-fixtures", "version": "1.6.0", diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7be8dea..4157a91 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -7,6 +7,9 @@ framework: property_access: true + serializer: + name_converter: 'serializer.name_converter.camel_case_to_snake_case' + # Enables session support. Note that the session will ONLY be started if you read or write from it. # Remove or comment this section to explicitly disable session support. session: diff --git a/src/Controller/Attendee/ListController.php b/src/Controller/Attendee/ListController.php index a98df29..31a1991 100644 --- a/src/Controller/Attendee/ListController.php +++ b/src/Controller/Attendee/ListController.php @@ -4,16 +4,17 @@ namespace App\Controller\Attendee; -use App\Entity\Attendee; use App\Repository\AttendeeRepository; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\SerializerInterface; #[Route('/attendees', name: 'list_attendee', methods: ['GET'])] final class ListController { public function __construct( - private readonly AttendeeRepository $attendeeRepository + private readonly AttendeeRepository $attendeeRepository, + private readonly SerializerInterface $serializer, ) { } @@ -21,12 +22,9 @@ public function __invoke(): Response { $allAttendees = $this->attendeeRepository->findAll(); - $allAttendeesAsArray = array_map( - static fn (Attendee $attendee) => $attendee->toArray(), - $allAttendees - ); + $serializedAttendees = $this->serializer->serialize($allAttendees, 'json'); - return new Response(json_encode($allAttendeesAsArray), Response::HTTP_OK, [ + return new Response($serializedAttendees, Response::HTTP_OK, [ 'Content-Type' => 'application/json', ]); } diff --git a/src/Controller/Attendee/ReadController.php b/src/Controller/Attendee/ReadController.php index fb34b1f..5b12ac1 100644 --- a/src/Controller/Attendee/ReadController.php +++ b/src/Controller/Attendee/ReadController.php @@ -7,13 +7,21 @@ use App\Entity\Attendee; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\SerializerInterface; #[Route('/attendees/{identifier}', name: 'read_attendee', methods: ['GET'])] final class ReadController { + public function __construct( + private readonly SerializerInterface $serializer + ) { + } + public function __invoke(Attendee $attendee): Response { - return new Response(json_encode($attendee->toArray()), Response::HTTP_OK, [ + $serializedAttendee = $this->serializer->serialize($attendee, 'json'); + + return new Response($serializedAttendee, Response::HTTP_OK, [ 'Content-Type' => 'application/json', ]); } diff --git a/src/Controller/Workshop/ListController.php b/src/Controller/Workshop/ListController.php index b0faf46..de84820 100644 --- a/src/Controller/Workshop/ListController.php +++ b/src/Controller/Workshop/ListController.php @@ -4,16 +4,17 @@ namespace App\Controller\Workshop; -use App\Entity\Workshop; use App\Repository\WorkshopRepository; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\SerializerInterface; #[Route('/workshops', name: 'list_workshop', methods: ['GET'])] final class ListController { public function __construct( - private readonly WorkshopRepository $workshopRepository + private readonly WorkshopRepository $workshopRepository, + private readonly SerializerInterface $serializer ) { } @@ -21,12 +22,9 @@ public function __invoke(): Response { $allWorkshops = $this->workshopRepository->findAll(); - $allWorkshopsAsArray = array_map( - static fn (Workshop $workshop) => $workshop->toArray(), - $allWorkshops - ); + $serializedWorkshops = $this->serializer->serialize($allWorkshops, 'json'); - return new Response(json_encode($allWorkshopsAsArray), Response::HTTP_OK, [ + return new Response($serializedWorkshops, Response::HTTP_OK, [ 'Content-Type' => 'application/json', ]); } diff --git a/src/Controller/Workshop/ReadController.php b/src/Controller/Workshop/ReadController.php index e4c69e1..f0feec1 100644 --- a/src/Controller/Workshop/ReadController.php +++ b/src/Controller/Workshop/ReadController.php @@ -7,13 +7,21 @@ use App\Entity\Workshop; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Serializer\SerializerInterface; #[Route('/workshops/{identifier}', name: 'read_workshop', methods: ['GET'])] final class ReadController { + public function __construct( + private readonly SerializerInterface $serializer + ) { + } + public function __invoke(Workshop $workshop): Response { - return new Response(json_encode($workshop->toArray()), Response::HTTP_OK, [ + $serializedWorkshop = $this->serializer->serialize($workshop, 'json'); + + return new Response($serializedWorkshop, Response::HTTP_OK, [ 'Content-Type' => 'application/json', ]); } diff --git a/src/Entity/Attendee.php b/src/Entity/Attendee.php index b974648..baef389 100644 --- a/src/Entity/Attendee.php +++ b/src/Entity/Attendee.php @@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use JetBrains\PhpStorm\ArrayShape; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Webmozart\Assert\Assert; @@ -120,15 +119,4 @@ public function removeWorkshop(Workshop $workshop): self return $this; } - - #[ArrayShape(['identifier' => "\Ramsey\Uuid\UuidInterface", 'firstname' => 'string', 'lastname' => 'string', 'email' => 'string'])] - public function toArray(): array - { - return [ - 'identifier' => $this->identifier, - 'firstname' => $this->firstname, - 'lastname' => $this->lastname, - 'email' => $this->email, - ]; - } } diff --git a/src/Entity/Workshop.php b/src/Entity/Workshop.php index 6740da5..cbacf27 100644 --- a/src/Entity/Workshop.php +++ b/src/Entity/Workshop.php @@ -8,9 +8,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use JetBrains\PhpStorm\ArrayShape; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; +use Symfony\Component\Serializer\Annotation as Serializer; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Webmozart\Assert\Assert; #[ORM\Entity(repositoryClass: WorkshopRepository::class)] @@ -28,6 +30,8 @@ class Workshop private string $title; #[ORM\Column(type: 'datetime_immutable', length: 255)] + #[Serializer\Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])] + // configuring the date format via AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER in the serializer context is also possible private \DateTimeImmutable $workshopDate; #[ORM\ManyToMany(targetEntity: Attendee::class, inversedBy: 'workshops')] @@ -91,17 +95,4 @@ public function removeAttendee(Attendee $attendee): self return $this; } - - #[ArrayShape(['identifier' => "\Ramsey\Uuid\UuidInterface", 'title' => 'string', 'workshop_date' => 'string', 'attendees' => 'array[]'])] - public function toArray(): array - { - return [ - 'identifier' => $this->identifier, - 'title' => $this->getTitle(), - 'workshop_date' => $this->getWorkshopDate()->format('Y-m-d'), - 'attendees' => array_map(function (Attendee $attendee): array { - return $attendee->toArray(); - }, $this->getAttendees()), - ]; - } } diff --git a/src/Serializer/AttendeeNormalizer.php b/src/Serializer/AttendeeNormalizer.php new file mode 100644 index 0000000..3035b65 --- /dev/null +++ b/src/Serializer/AttendeeNormalizer.php @@ -0,0 +1,50 @@ + ['id'], + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $object->getFirstname().' '.$object->getLastname(), + ]; + + $context = array_merge($context, $customContext); + + return $this->normalizer->normalize($object, $format, $context); + } + + // see: https://github.com/symfony/symfony-docs/issues/18042 + public function getSupportedTypes(?string $format): array + { + return [ + Attendee::class => true, + ]; + } +} diff --git a/src/Serializer/WorkshopNormalizer.php b/src/Serializer/WorkshopNormalizer.php new file mode 100644 index 0000000..5b23d09 --- /dev/null +++ b/src/Serializer/WorkshopNormalizer.php @@ -0,0 +1,50 @@ + ['id'], + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $object->getTitle(), + ]; + + $context = array_merge($context, $customContext); + + return $this->normalizer->normalize($object, $format, $context); + } + + // see: https://github.com/symfony/symfony-docs/issues/18042 + public function getSupportedTypes(?string $format): array + { + return [ + Workshop::class => true, + ]; + } +} diff --git a/tests/Controller/Attendee/__snapshots__/ListControllerTest__test_it_should_list_all_attendees__1.json b/tests/Controller/Attendee/__snapshots__/ListControllerTest__test_it_should_list_all_attendees__1.json index 1a7f23b..527cc31 100644 --- a/tests/Controller/Attendee/__snapshots__/ListControllerTest__test_it_should_list_all_attendees__1.json +++ b/tests/Controller/Attendee/__snapshots__/ListControllerTest__test_it_should_list_all_attendees__1.json @@ -3,6 +3,16 @@ "identifier": "803449f4-9a4c-4ecb-8ce4-cebc804fe70a", "firstname": "Jan", "lastname": "Sch\u00e4dlich", - "email": "mail@janschaedlich.de" + "email": "schaedlich.jan@gmail.com", + "workshops": [ + { + "identifier": "abba667a-96ae-4f75-9b71-97819b682e8d", + "title": "RESTful Webservices in Symfony", + "workshop_date": "2022-06-14", + "attendees": [ + "Jan Sch\u00e4dlich" + ] + } + ] } ] diff --git a/tests/Controller/Attendee/__snapshots__/ReadControllerTest__test_it_should_show_requested_attendee__1.json b/tests/Controller/Attendee/__snapshots__/ReadControllerTest__test_it_should_show_requested_attendee__1.json index 9efbf25..763a24e 100644 --- a/tests/Controller/Attendee/__snapshots__/ReadControllerTest__test_it_should_show_requested_attendee__1.json +++ b/tests/Controller/Attendee/__snapshots__/ReadControllerTest__test_it_should_show_requested_attendee__1.json @@ -2,5 +2,15 @@ "identifier": "17058af8-1b0f-4afe-910d-669b4bd0fd26", "firstname": "Jan", "lastname": "Sch\u00e4dlich", - "email": "mail@janschaedlich.de" + "email": "schaedlich.jan@gmail.com", + "workshops": [ + { + "identifier": "12bc7c60-7f77-41e0-90eb-9996a64a3b14", + "title": "RESTful Webservices in Symfony", + "workshop_date": "2022-06-14", + "attendees": [ + "Jan Sch\u00e4dlich" + ] + } + ] } diff --git a/tests/Controller/Attendee/fixtures/list_attendee.yaml b/tests/Controller/Attendee/fixtures/list_attendee.yaml index 94fcf49..7381a6a 100644 --- a/tests/Controller/Attendee/fixtures/list_attendee.yaml +++ b/tests/Controller/Attendee/fixtures/list_attendee.yaml @@ -1,3 +1,9 @@ +App\Entity\Workshop: + workshop_1: + __construct: [ 'abba667a-96ae-4f75-9b71-97819b682e8d', 'RESTful Webservices in Symfony', <(new \DateTimeImmutable('2022-06-14'))> ] + __calls: + - addAttendee: [ '@attendee_1' ] + App\Entity\Attendee: attendee_1: - __construct: [ '803449f4-9a4c-4ecb-8ce4-cebc804fe70a', 'Jan', 'Schädlich', 'mail@janschaedlich.de' ] + __construct: [ '803449f4-9a4c-4ecb-8ce4-cebc804fe70a', 'Jan', 'Schädlich', 'schaedlich.jan@gmail.com' ] diff --git a/tests/Controller/Attendee/fixtures/read_attendee.yaml b/tests/Controller/Attendee/fixtures/read_attendee.yaml index 5e26232..8e5ab2c 100644 --- a/tests/Controller/Attendee/fixtures/read_attendee.yaml +++ b/tests/Controller/Attendee/fixtures/read_attendee.yaml @@ -1,3 +1,9 @@ +App\Entity\Workshop: + workshop_1: + __construct: [ '12bc7c60-7f77-41e0-90eb-9996a64a3b14', 'RESTful Webservices in Symfony', <(new \DateTimeImmutable('2022-06-14'))> ] + __calls: + - addAttendee: [ '@attendee_1' ] + App\Entity\Attendee: attendee_1: - __construct: [ '17058af8-1b0f-4afe-910d-669b4bd0fd26', 'Jan', 'Schädlich', 'mail@janschaedlich.de' ] + __construct: [ '17058af8-1b0f-4afe-910d-669b4bd0fd26', 'Jan', 'Schädlich', 'schaedlich.jan@gmail.com' ] diff --git a/tests/Controller/Workshop/__snapshots__/ListControllerTest__test_it_should_list_all_workshops__1.json b/tests/Controller/Workshop/__snapshots__/ListControllerTest__test_it_should_list_all_workshops__1.json index 59b5eed..2bfa915 100644 --- a/tests/Controller/Workshop/__snapshots__/ListControllerTest__test_it_should_list_all_workshops__1.json +++ b/tests/Controller/Workshop/__snapshots__/ListControllerTest__test_it_should_list_all_workshops__1.json @@ -3,6 +3,16 @@ "identifier": "ef6e13ef-f601-4e74-bae8-bdf8f41cd8db", "title": "RESTful Webservices in Symfony", "workshop_date": "2022-06-14", - "attendees": [] + "attendees": [ + { + "identifier": "2a451d60-2fef-437b-838e-10edb2ade8eb", + "firstname": "Jan", + "lastname": "Sch\u00e4dlich", + "email": "schaedlich.jan@gmail.com", + "workshops": [ + "RESTful Webservices in Symfony" + ] + } + ] } ] diff --git a/tests/Controller/Workshop/__snapshots__/ReadControllerTest__test_it_should_show_requested_workshop__1.json b/tests/Controller/Workshop/__snapshots__/ReadControllerTest__test_it_should_show_requested_workshop__1.json index 77d1085..0bb7af6 100644 --- a/tests/Controller/Workshop/__snapshots__/ReadControllerTest__test_it_should_show_requested_workshop__1.json +++ b/tests/Controller/Workshop/__snapshots__/ReadControllerTest__test_it_should_show_requested_workshop__1.json @@ -2,5 +2,15 @@ "identifier": "8acf8f2b-95c1-46e1-85a4-ea6ff88081ce", "title": "RESTful Webservices in Symfony", "workshop_date": "2022-06-14", - "attendees": [] + "attendees": [ + { + "identifier": "36ffcf19-7560-4ead-8d8e-3c40cf169784", + "firstname": "Jan", + "lastname": "Sch\u00e4dlich", + "email": "schaedlich.jan@gmail.com", + "workshops": [ + "RESTful Webservices in Symfony" + ] + } + ] } diff --git a/tests/Controller/Workshop/fixtures/list_workshop.yaml b/tests/Controller/Workshop/fixtures/list_workshop.yaml index 9c1f749..9d9cfad 100644 --- a/tests/Controller/Workshop/fixtures/list_workshop.yaml +++ b/tests/Controller/Workshop/fixtures/list_workshop.yaml @@ -1,3 +1,9 @@ +App\Entity\Attendee: + attendee_1: + __construct: [ '2a451d60-2fef-437b-838e-10edb2ade8eb', 'Jan', 'Schädlich', 'schaedlich.jan@gmail.com' ] + App\Entity\Workshop: workshop_1: __construct: [ 'ef6e13ef-f601-4e74-bae8-bdf8f41cd8db', 'RESTful Webservices in Symfony', <(new \DateTimeImmutable('2022-06-14'))> ] + __calls: + - addAttendee: [ '@attendee_1' ] diff --git a/tests/Controller/Workshop/fixtures/read_workshop.yaml b/tests/Controller/Workshop/fixtures/read_workshop.yaml index 96a4d85..98a477c 100644 --- a/tests/Controller/Workshop/fixtures/read_workshop.yaml +++ b/tests/Controller/Workshop/fixtures/read_workshop.yaml @@ -1,3 +1,9 @@ +App\Entity\Attendee: + attendee_1: + __construct: [ '36ffcf19-7560-4ead-8d8e-3c40cf169784', 'Jan', 'Schädlich', 'schaedlich.jan@gmail.com' ] + App\Entity\Workshop: workshop_1: __construct: [ '8acf8f2b-95c1-46e1-85a4-ea6ff88081ce', 'RESTful Webservices in Symfony', <(new \DateTimeImmutable('2022-06-14'))> ] + __calls: + - addAttendee: [ '@attendee_1' ]