From 13d2ea100d38fd2748559cbf0d6b3450097613d4 Mon Sep 17 00:00:00 2001 From: Hugues Tavernier Date: Wed, 17 Jan 2024 15:46:58 +0100 Subject: [PATCH 01/27] docs(messenger): deprecation MessageHandlerInterface (#1882) --- core/messenger.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/messenger.md b/core/messenger.md index da036af60a6..ab6dc2a7efb 100644 --- a/core/messenger.md +++ b/core/messenger.md @@ -85,9 +85,10 @@ To process the message that will be dispatched, [a handler](https://symfony.com/ namespace App\Handler; use App\Entity\Person; -use Symfony\Component\Messenger\Handler\MessageHandlerInterface; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; -final class PersonHandler implements MessageHandlerInterface +#[AsMessageHandler] +final class PersonHandler { public function __invoke(Person $person) { @@ -174,9 +175,10 @@ In this case, when a `POST` request is issued on `/users/reset_password` the mes namespace App\Handler; use App\Dto\ResetPasswordRequest; -use Symfony\Component\Messenger\Handler\MessageHandlerInterface; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; -final class ResetPasswordRequestHandler implements MessageHandlerInterface +#[AsMessageHandler] +final class ResetPasswordRequestHandler { public function __invoke(ResetPasswordRequest $forgotPassword) { From fc84f3b58f3dc4caf0db3d85e039c49087da1dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 19 Jan 2024 19:52:17 +0100 Subject: [PATCH 02/27] improve state processor and provider docs (#1873) * improve state processor and provider docs * update according to latest changes in core * Update core/state-providers.md Co-authored-by: Ben Davies * Update core/state-providers.md Co-authored-by: Ben Davies * clarify * cleanup * Update core/dto.md Co-authored-by: Antoine Bluchet --------- Co-authored-by: Ben Davies Co-authored-by: Antoine Bluchet --- core/dto.md | 25 +++++++++-- core/extensions.md | 6 ++- core/identifiers.md | 12 ++++- core/state-processors.md | 93 ++++++++++++++++++++------------------ core/state-providers.md | 96 ++++++++++++++++++++++++---------------- core/user.md | 24 +++++++--- core/validation.md | 12 +++-- 7 files changed, 173 insertions(+), 95 deletions(-) diff --git a/core/dto.md b/core/dto.md index a3171424d8e..462a6a8ae17 100644 --- a/core/dto.md +++ b/core/dto.md @@ -11,6 +11,7 @@ Using an input, the request body will be denormalized to the input instead of yo ```php + */ final class UserResetPasswordProcessor implements ProcessorInterface { /** * @param UserResetPasswordDto $data + * + * @throws NotFoundHttpException */ - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User { if ('user@example.com' === $data->email) { return new User(email: $data->email, id: 1); @@ -71,6 +79,7 @@ Let's use a message that will be processed by [Symfony Messenger](https://symfon ```php + */ final class BookRepresentationProvider implements ProviderInterface { - public function provide(Operation $operation, array $uriVariables = [], array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation { return new AnotherRepresentation(); } @@ -128,6 +142,7 @@ For returning another representation of your data in a [State Processor](./state ```php + */ final class BookRepresentationProcessor implements ProcessorInterface { /** * @param Book $data */ - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation { return new AnotherRepresentation( $data->getId(), diff --git a/core/extensions.md b/core/extensions.md index a669f7afa0d..4f75c864e09 100644 --- a/core/extensions.md +++ b/core/extensions.md @@ -65,10 +65,12 @@ use App\Entity\Offer; use Doctrine\ORM\QueryBuilder; use Symfony\Bundle\SecurityBundle\Security; -final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface +final readonly class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { - public function __construct(private readonly Security $security) + public function __construct( + private Security $security, + ) { } diff --git a/core/identifiers.md b/core/identifiers.md index 8eaf4161841..1b9d9231889 100644 --- a/core/identifiers.md +++ b/core/identifiers.md @@ -64,15 +64,19 @@ Let's create a `Provider` for the `Person` entity: ```php + */ final class PersonProvider implements ProviderInterface { - public function provide(Operation $operation, array $uriVariables = [], array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []): Person { // Our identifier is: // $uriVariables['code'] @@ -105,7 +109,8 @@ final class UuidUriVariableTransformer implements UriVariableTransformerInterfac * * @throws InvalidUriVariableException Occurs when the uriVariable could not be transformed */ - public function transform($value, array $types, array $context = []) { + public function transform($value, array $types, array $context = []): Uuid + { try { return Uuid::fromString($value); } catch (InvalidUuidStringException $e) { @@ -138,6 +143,8 @@ Tag this service as an `api_platform.uri_variables.transformer`: ```yaml +# api/config/services.yaml + services: App\Identifier\UuidUriVariableTransformer: tags: @@ -161,6 +168,7 @@ If your resource is also a Doctrine entity and you want to use another identifie ```php + */ +final class BlogPostProcessor implements ProcessorInterface { - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): BlogPost|void { // call your persistence layer to save $data return $data; @@ -51,6 +54,9 @@ class BlogPostProcessor implements ProcessorInterface } ``` +The `process()` method must return the created or modified object, or nothing (that's why `void` is allowed) for `DELETE` operations. +The `process()` method can also take an object as input, in the `$data` parameter, that isn't of the same type that its output (the returned object). See [the DTO documentation entry](dto.md) for more details. + We then configure our operation to use this processor: ```php @@ -66,28 +72,11 @@ use App\State\BlogPostProcessor; class BlogPost {} ``` -If service autowiring and autoconfiguration are enabled (they are by default), you are done! - -Otherwise, if you use a custom dependency injection configuration, you need to register the corresponding service and add the -`api_platform.state_processor` tag. - -```yaml -# api/config/services.yaml - -services: - # ... - App\State\BlogPostProcessor: ~ - # Uncomment only if autoconfiguration is disabled - #tags: [ 'api_platform.state_processor' ] -``` - ## Hooking into the Built-In State Processors -If you want to execute custom business logic before or after persistence, this can be achieved by [decorating](https://symfony.com/doc/current/service_container/service_decoration.html) the built-in state processors or using [composition](https://en.wikipedia.org/wiki/Object_composition). +If you want to execute custom business logic before or after persistence, this can be achieved by using [composition](https://en.wikipedia.org/wiki/Object_composition). -The next example uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html). Read its documentation if you want to use it. - -Here is an implementation example which sends new users a welcome email after a REST `POST` or GraphQL `create` operation, in a project using the native Doctrine ORM state processor: +Here is an implementation example which uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send new users a welcome email after a REST `POST` or GraphQL `create` operation, in a project using the native Doctrine ORM state processor: ```php + */ final class UserProcessor implements ProcessorInterface { - public function __construct(private ProcessorInterface $persistProcessor, private ProcessorInterface $removeProcessor, MailerInterface $mailer) + public function __construct( + #[Autowire('api_platform.doctrine.orm.state.persist_processor')] + private ProcessorInterface $persistProcessor, + #[Autowire('api_platform.doctrine.orm.state.remove_processor')] + private ProcessorInterface $removeProcessor, + MailerInterface $mailer, + ) { } - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User|void { if ($operation instanceof DeleteOperationInterface) { return $this->removeProcessor->process($data, $operation, $uriVariables, $context); @@ -115,10 +114,11 @@ final class UserProcessor implements ProcessorInterface $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context); $this->sendWelcomeEmail($data); + return $result; } - private function sendWelcomeEmail(User $user) + private function sendWelcomeEmail(User $user): void { // Your welcome email logic... // $this->mailer->send(...); @@ -126,26 +126,11 @@ final class UserProcessor implements ProcessorInterface } ``` -Even with service autowiring and autoconfiguration enabled, you must still configure the decoration: - -```yaml -# api/config/services.yaml +The `Autowire` attribute is used to inject the built-in processor services registered by API Platform. -services: - # ... - App\State\UserProcessor: - bind: - $persistProcessor: '@api_platform.doctrine.orm.state.persist_processor' - $removeProcessor: '@api_platform.doctrine.orm.state.remove_processor' - # If you're using Doctrine MongoDB ODM, you can use the following code: - # $persistProcessor: '@api_platform.doctrine_mongodb.odm.state.persist_processor' - # $removeProcessor: '@api_platform.doctrine_mongodb.odm.state.remove_processor' - # Uncomment only if autoconfiguration is disabled - #arguments: ['@App\State\UserProcessor.inner'] - #tags: [ 'api_platform.state_processor' ] -``` +If you're using Doctrine MongoDB ODM instead of Doctrine ORM, replace `orm` by `odm` in the name of the injected services. -And configure that you want to use this processor on the User resource: +Finally, configure that you want to use this processor on the User resource: ```php + */ final class BlogPostProvider implements ProviderInterface { - public function provide(Operation $operation, array $uriVariables = [], array $context = []) + private const DATA = [ + 'ab' => new BlogPost('ab'), + 'cd' => new BlogPost('cd'), + ]; + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): BlogPost|null { - return new BlogPost($uriVariables['id']); + return self::DATA[$uriVariables['id']] ?? null; } } ``` -As this operation expects a BlogPost we return an instance of the BlogPost in the `provide` method. -The `uriVariables` parameter is an array with the values of the URI variables. +For the example, we store the list of our blog posts in an associative array (the `BlogPostProvider::DATA` constant). + +As this operation expects a `BlogPost`, the `provide` methods return the instance of the `BlogPost` corresponding to the ID passed in the URL. If the ID doesn't exist in the associative array, `provide()` returns `null`. API Platform will automatically generate a 404 response if the provider returns `null`. + +The `$uriVariables` parameter contains an array with the values of the URI variables. To use this provider we need to configure the provider on the operation: @@ -68,20 +79,6 @@ use App\State\BlogPostProvider; class BlogPost {} ``` -If you use the default configuration, the corresponding service will be automatically registered thanks to -[autowiring](https://symfony.com/doc/current/service_container/autowiring.html). -To declare the service explicitly, you can use the following snippet: - -```yaml -# api/config/services.yaml - -services: - # ... - App\State\BlogPostProvider: ~ - # Uncomment only if autoconfiguration is disabled - #tags: [ 'api_platform.state_provider' ] -``` - Now let's say that we also want to handle the `/blog_posts` URI which returns a collection. We can change the Provider into supporting a wider range of operations. Then we can provide a collection of blog posts when the operation is a `CollectionOperationInterface`: @@ -96,15 +93,23 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Metadata\CollectionOperationInterface; +/** + * @implements ProviderInterface + */ final class BlogPostProvider implements ProviderInterface { - public function provide(@Operation $operation, array $uriVariables = [], array $context = []) + private const DATA = [ + 'ab' => new BlogPost('ab'), + 'cd' => new BlogPost('cd'), + ]; + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable|BlogPost|null { if ($operation instanceof CollectionOperationInterface) { - return [new BlogPost(), new BlogPost()]; + return self::DATA; } - return new BlogPost($uriVariables['id']); + return self::DATA[$uriVariables['id']] ?? null; } } ``` @@ -140,14 +145,21 @@ use App\Dto\AnotherRepresentation; use App\Model\Book; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +/** + * @implements ProviderInterface + */ final class BookRepresentationProvider implements ProviderInterface { - public function __construct(private ProviderInterface $itemProvider) + public function __construct( + #[Autowire('api_platform.doctrine.orm.state.item_provider')] + private ProviderInterface $itemProvider, + ) { } - public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + public function provide(Operation $operation, array $uriVariables = [], array $context = []): AnotherRepresentation { $book = $this->itemProvider->provide($operation, $uriVariables, $context); @@ -159,20 +171,6 @@ final class BookRepresentationProvider implements ProviderInterface } ``` -Even with service autowiring and autoconfiguration enabled, you must still configure the decoration: - -```yaml -# api/config/services.yaml - -services: - # ... - App\State\BookRepresentationProvider: - bind: - $itemProvider: '@api_platform.doctrine.orm.state.item_provider' - # Uncomment only if autoconfiguration is disabled - #tags: [ 'api_platform.state_provider' ] -``` - And configure that you want to use this provider on the Book resource: ```php @@ -188,3 +186,27 @@ use App\State\BookRepresentationProvider; #[Get(output: AnotherRepresentation::class, provider: BookRepresentationProvider::class)] class Book {} ``` + +## Registering Services Without Autowiring + +The services in the previous examples are automatically registered because +[autowiring](https://symfony.com/doc/current/service_container/autowiring.html) + and autoconfiguration are enabled by default in API Platform. +To declare the service explicitly, you can use the following snippet: + +```yaml +# api/config/services.yaml + +services: + # ... + App\State\BlogPostProvider: ~ + tags: [ 'api_platform.state_provider' ] + +# api/config/services.yaml +services: + # ... + App\State\BookRepresentationProvider: + arguments: + $itemProvider: '@api_platform.doctrine.orm.state.item_provider' + tags: [ 'api_platform.state_provider' ] +``` diff --git a/core/user.md b/core/user.md index f65ed7825d9..fafb1a3bcba 100644 --- a/core/user.md +++ b/core/user.md @@ -8,7 +8,7 @@ You can follow the [official Symfony Documentation](https://symfony.com/doc/curr ```php + */ +final readonly class UserPasswordHasher implements ProcessorInterface { - public function __construct(private readonly ProcessorInterface $processor, private readonly UserPasswordHasherInterface $passwordHasher) + public function __construct( + private ProcessorInterface $processor, + private UserPasswordHasherInterface $passwordHasher + ) { } - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + /** + * @param User $data + */ + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): User|void { if (!$data->getPlainPassword()) { return $this->processor->process($data, $operation, $uriVariables, $context); @@ -262,6 +271,9 @@ Then bind it to the ORM persist processor: ```yaml # api/config/services.yaml +services: + # ... + App\State\UserPasswordHasher: bind: $processor: '@api_platform.doctrine.orm.state.persist_processor' diff --git a/core/validation.md b/core/validation.md index 60145dac3dd..7676328d599 100644 --- a/core/validation.md +++ b/core/validation.md @@ -408,15 +408,16 @@ use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Validator\ValidatorInterface; use App\Entity\MyEntity; -class MyEntityRemoveProcessor implements ProcessorInterface +final readonly class MyEntityRemoveProcessor implements ProcessorInterface { public function __construct( private DoctrineRemoveProcessor $doctrineProcessor, private ValidatorInterface $validator, - ) { + ) + { } - public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) { $this->validator->validate($data, ['groups' => ['deleteValidation']]); $this->doctrineProcessor->process($data, $operation, $uriVariables, $context); @@ -469,6 +470,7 @@ For example: ```php Date: Wed, 31 Jan 2024 10:15:39 -0800 Subject: [PATCH 03/27] Update state-processors.md (#1887) --- core/state-processors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state-processors.md b/core/state-processors.md index b689216b314..3d2ddc0a04b 100644 --- a/core/state-processors.md +++ b/core/state-processors.md @@ -97,9 +97,9 @@ use Symfony\Component\Mailer\MailerInterface; final class UserProcessor implements ProcessorInterface { public function __construct( - #[Autowire('api_platform.doctrine.orm.state.persist_processor')] + #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')] private ProcessorInterface $persistProcessor, - #[Autowire('api_platform.doctrine.orm.state.remove_processor')] + #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')] private ProcessorInterface $removeProcessor, MailerInterface $mailer, ) From f333e32df9486b10a4293ba397d164538ddb3dbf Mon Sep 17 00:00:00 2001 From: davy-beauzil <38990335+davy-beauzil@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:32:01 +0100 Subject: [PATCH 04/27] fix(errors): fix broken href in errors page (#1890) --- core/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/errors.md b/core/errors.md index f14c2c69d00..0539f937982 100644 --- a/core/errors.md +++ b/core/errors.md @@ -25,7 +25,7 @@ This can also be configured on an `ApiResource` or in an `HttpOperation`, for ex ## Exception status code decision -There are many ways of configuring the exception status code we recommend reading the guides on how to use an [Error Provider](/docs/guides/error-provider) or create an [Error Resource](/docs/guides/error-resource). +There are many ways of configuring the exception status code we recommend reading the guides on how to use an [Error Provider](https://api-platform.com/docs/guides/error-provider/) or create an [Error Resource](https://api-platform.com/docs/guides/error-resource/). 1. we look at `exception_to_status` and take one if there's a match 2. If your exception is a `Symfony\Component\HttpKernel\Exception\HttpExceptionInterface` we get its status. From 97f5c9edd1a2450f2244850bf2069fa6dec1f581 Mon Sep 17 00:00:00 2001 From: Tac Tacelosky Date: Tue, 20 Feb 2024 01:17:07 -0700 Subject: [PATCH 05/27] fix wrong value (#1902) --- core/upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/upgrade-guide.md b/core/upgrade-guide.md index 9f4d08ee740..a28482e5e3a 100644 --- a/core/upgrade-guide.md +++ b/core/upgrade-guide.md @@ -54,7 +54,7 @@ Event listeners will not get removed and are not deprecated, they'll use our pro We're switching to `symfony/string` [inflector](https://symfony.com/doc/current/components/string.html#inflector), to keep using `doctrine/inflector` use: ```yaml -keep_legacy_inflector: false +keep_legacy_inflector: true ``` We strongly recommend that you use your own inflector anyways with a [PathSegmentNameGenerator](https://github.com/api-platform/core/blob/f776f11fd23e5397a65c1355a9ebcbb20afac9c2/src/Metadata/Operation/UnderscorePathSegmentNameGenerator.php). From 3d73ba147dd7c5b6ec30dabfd001429d7b6542a9 Mon Sep 17 00:00:00 2001 From: Glen Date: Fri, 23 Feb 2024 06:25:31 -0800 Subject: [PATCH 06/27] Add missing namespace in Normalizer and fix Caddyfile --- core/file-upload.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/file-upload.md b/core/file-upload.md index 2e62b897296..12c89330ac9 100644 --- a/core/file-upload.md +++ b/core/file-upload.md @@ -176,9 +176,10 @@ namespace App\Serializer; use App\Entity\MediaObject; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Vich\UploaderBundle\Storage\StorageInterface; -final class MediaObjectNormalizer implements NormalizerAwareInterface +final class MediaObjectNormalizer implements NormalizerAwareInterface, NormalizerInterface { use NormalizerAwareTrait; @@ -234,8 +235,8 @@ You will need to modify your `Caddyfile` to allow the above `contentUrl` to be a @pwa expression `( header({'Accept': '*text/html*'}) && !path( -- '/docs*', '/graphql*', '/bundles*', '/media*', '/contexts*', '/_profiler*', '/_wdt*', -+ '/media*', '/docs*', '/graphql*', '/bundles*', '/media*', '/contexts*', '/_profiler*', '/_wdt*', +- '/docs*', '/graphql*', '/bundles*', '/contexts*', '/_profiler*', '/_wdt*', ++ '/media*', '/docs*', '/graphql*', '/bundles*', '/contexts*', '/_profiler*', '/_wdt*', '*.json*', '*.html', '*.csv', '*.yml', '*.yaml', '*.xml' ) ) From 7a433f89db7eb7db2102c88ca54a88061d1ba0f2 Mon Sep 17 00:00:00 2001 From: mehdi Date: Wed, 28 Feb 2024 10:54:09 +0100 Subject: [PATCH 07/27] update create-client doc for react --- create-client/react.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/create-client/react.md b/create-client/react.md index 5075cbdc2c4..ba183eb8a2e 100644 --- a/create-client/react.md +++ b/create-client/react.md @@ -9,14 +9,12 @@ from the ecosystem: * [React Router](https://reactrouter.com/) * [React Hook Form](https://react-hook-form.com/) -It is designed to generate code that works seamlessly with [Facebook's Create React App](https://create-react-app.dev/). - ## Install Bootstrap a React application: ```console -npm init react-app -- --template typescript my-app +npm create vite@latest my-app -- --template react-ts cd my-app ``` @@ -35,7 +33,7 @@ npm install bootstrap font-awesome Finally, start the integrated web server: ```console -npm run start +npm run dev ``` ## Generating a Web App @@ -54,7 +52,7 @@ The code has been generated, and is ready to be executed! Register the reducers and the routes: ```typescript -// client/src/index.tsx +// my-app/src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; @@ -74,9 +72,9 @@ root.render( - + }/> {/* Add your routes here */} -

Not Found

} /> + } />
From 1ab12e2b576430104e93bd229f72cbf2a3b721e1 Mon Sep 17 00:00:00 2001 From: Bredillet Thomas Date: Sun, 3 Mar 2024 18:41:58 +0100 Subject: [PATCH 08/27] Fix typo on state processors doc (#1913) --- core/state-processors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state-processors.md b/core/state-processors.md index 3d2ddc0a04b..7149b994207 100644 --- a/core/state-processors.md +++ b/core/state-processors.md @@ -80,7 +80,7 @@ Here is an implementation example which uses [Symfony Mailer](https://symfony.co ```php Date: Mon, 11 Mar 2024 13:05:02 +0100 Subject: [PATCH 09/27] Fixed readinessProbe tabs (#1915) --- deployment/kubernetes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/kubernetes.md b/deployment/kubernetes.md index 9d5e571f340..415385ce64b 100644 --- a/deployment/kubernetes.md +++ b/deployment/kubernetes.md @@ -223,6 +223,6 @@ The `readinessProbe` and the `livenessProble` can not use the default `docker-he readinessProbe: exec: command: ["/bin/sh", "-c", "/bin/ps -ef | grep messenger:consume | grep -v grep"] - initialDelaySeconds: 120 - periodSeconds: 3 + initialDelaySeconds: 120 + periodSeconds: 3 ``` From a449c6e245bd2b521e604fbf41fb31626bbb48a5 Mon Sep 17 00:00:00 2001 From: Mat <7613997+Ak1r0@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:42:44 +0100 Subject: [PATCH 10/27] Fix command line to generate public and private keys using lexik:jwt:generate-keypair --- core/jwt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/jwt.md b/core/jwt.md index f7a0d5644fc..593f0bb4e47 100644 --- a/core/jwt.md +++ b/core/jwt.md @@ -23,7 +23,7 @@ Then we need to generate the public and private keys used for signing JWT tokens ```console docker compose exec php sh -c ' set -e - apk add openssl + apt-get install openssl php bin/console lexik:jwt:generate-keypair setfacl -R -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt setfacl -dR -m u:www-data:rX -m u:"$(whoami)":rwX config/jwt From 06f03d14a3216d6e8a68e67587db7e083144ebba Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 18 Mar 2024 12:18:40 +0100 Subject: [PATCH 11/27] graphql: fix code selector --- core/graphql.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/core/graphql.md b/core/graphql.md index 85fa68d6205..801617320c5 100644 --- a/core/graphql.md +++ b/core/graphql.md @@ -318,7 +318,7 @@ Now that your resolver is created and registered, you can configure your custom In your resource, add the following: -[codeselector] + ```php ```php ```php ```php ```php ```php ```php ```php ```php ```php ```php ```php -[codeselector] + ```php ```php ```php ```php Date: Mon, 18 Mar 2024 12:46:39 +0100 Subject: [PATCH 12/27] typo on subresources.md (#1912) missing line break was causing formatting error on https://api-platform.com/docs/core/subresources/ --- core/subresources.md | 1 + 1 file changed, 1 insertion(+) diff --git a/core/subresources.md b/core/subresources.md index 4723b139acf..cc3905b21f2 100644 --- a/core/subresources.md +++ b/core/subresources.md @@ -109,6 +109,7 @@ To make things work, API Platform needs information about how to retrieve the `A the `Question`, this is done by configuring the `uriVariables`: + ```php Date: Mon, 18 Mar 2024 13:02:24 +0100 Subject: [PATCH 13/27] Add \Stringable as new list item (#1922) --- core/identifiers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/identifiers.md b/core/identifiers.md index 1b9d9231889..03c2ea9eec5 100644 --- a/core/identifiers.md +++ b/core/identifiers.md @@ -204,4 +204,4 @@ API Platform supports the following identifier types: - `\Ramsey\Uuid\Uuid` (see [UuidNormalizer](https://github.com/api-platform/core/blob/main/src/RamseyUuid/UriVariableTransformer/UuidUriVariableTransformer.php)) - `\Symfony\Component\Uid\Ulid` (see [UlidNormalizer](https://github.com/api-platform/core/blob/main/src/Symfony/UriVariableTransformer/UlidUriVariableTransformer.php)) - `\Symfony\Component\Uid\Uuid` (see [UuidNormalizer](https://github.com/api-platform/core/blob/main/src/Symfony/UriVariableTransformer/UuidUriVariableTransformer.php)) - `\Stringable` (essential when using composite identifiers from related resource classes) +- `\Stringable` (essential when using composite identifiers from related resource classes) From 85f5b070c4ffcf78b63c501e1d2314150097b6bf Mon Sep 17 00:00:00 2001 From: darkweak Date: Mon, 18 Mar 2024 13:02:53 +0100 Subject: [PATCH 14/27] feat(performance): add Souin setup to the documentation (#1914) --- core/performance.md | 62 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/core/performance.md b/core/performance.md index 68d00907372..94d0eb3f719 100644 --- a/core/performance.md +++ b/core/performance.md @@ -20,6 +20,63 @@ cache. This ensures that the content served will always be fresh, because the ca most specific cases such as the invalidation of collections when a document is added or removed or for relationships and inverse relations is built-in. +### Integrations + +#### Built-in Caddy HTTP cache + +The API Platform distribution relies on the [Caddy web server](https://caddyserver.com) which provide an official HTTP cache module called [cache-handler](https://github.com/caddyserver/cache-handler), that is based on [Souin](https://github.com/darkweak/souin). + +The integration using the cache-handler is quite simple. You juste have to update the `api/Dockerfile` to build your caddy instance with the HTTP cache + +```diff +# Versions +-FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream + ++FROM dunglas/frankenphp:latest-builder AS builder ++COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy ++ ++ENV CGO_ENABLED=1 XCADDY_SETCAP=1 XCADDY_GO_BUILD_FLAGS="-ldflags \"-w -s -extldflags '-Wl,-z,stack-size=0x80000'\"" ++RUN xcaddy build \ ++ --output /usr/local/bin/frankenphp \ ++ --with github.com/dunglas/frankenphp=./ \ ++ --with github.com/dunglas/frankenphp/caddy=./caddy/ \ ++ --with github.com/dunglas/mercure/caddy \ ++ --with github.com/dunglas/vulcain/caddy \ ++ # Use --with github.com/darkweak/souin for the latest improvements ++ --with github.com/caddyserver/cache-handler ++ ++FROM dunglas/frankenphp:latest AS frankenphp_upstream ++COPY --from=builder --link /usr/local/bin/frankenphp /usr/local/bin/frankenphp +``` + +Update your Caddyfile with the following configuration: +```caddyfile +{ + order cache before rewrite + ... + cache { + api { + souin + } + } +} +``` +This will tell to caddy to use the HTTP cache and activate the tag based invalidation API. You can refer to the [cache-handler documentation](https://github.com/caddyserver/cache-handler) or the [souin website documentation](https://docs.souin.io) to learn how to configure the HTTP cache server. + +Setup the HTTP cache invalidation in your API Platform project +```yaml +api_platform: + http_cache: + invalidation: + # We assume that your API can reach your caddy instance by the hostname http://caddy. + # The endpoint /souin-api/souin is the default path to the invalidation API. + urls: [ 'http://caddy/souin-api/souin' ] + purger: api_platform.http_cache.purger.souin +``` +And voilà, you have a fully working HTTP cache with it's own invalidation API. + +#### Varnish + Integration with Varnish and Doctrine ORM is shipped with the core library. Add the following configuration to enable the cache invalidation system: @@ -38,8 +95,9 @@ api_platform: vary: ['Content-Type', 'Authorization', 'Origin'] ``` -Support for reverse proxies other than Varnish can be added by implementing the `ApiPlatform\HttpCache\PurgerInterface`. -Two purgers are available, the http tags (`api_platform.http_cache.purger.varnish.ban`) or the surrogate key implementation +## Configuration +Support for reverse proxies other than Varnish or Caddy with the HTTP cache module can be added by implementing the `ApiPlatform\HttpCache\PurgerInterface`. +Three purgers are available, the built-in caddy http cache purger (`api_platform.http_cache.purger.souin`), the http tags (`api_platform.http_cache.purger.varnish.ban`), the surrogate key implementation (`api_platform.http_cache.purger.varnish.xkey`). You can specify the implementation using the `purger` configuration node, for example to use the xkey implementation: From ad6c3eb49c00dac60abc8487514b9d5a139b6cdc Mon Sep 17 00:00:00 2001 From: Damsito <60774219+Damsito@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:03:13 +0100 Subject: [PATCH 15/27] Update file-upload.md (#1909) Update normalization with the symfony 7 normalizer interface + update test --- core/file-upload.md | 80 +++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/core/file-upload.md b/core/file-upload.md index 12c89330ac9..7014d0b01bf 100644 --- a/core/file-upload.md +++ b/core/file-upload.md @@ -169,44 +169,53 @@ A [normalizer](serialization.md#normalization) could be used to set the `content ```php contentUrl = $this->storage->resolveUri($object, 'file'); + $object->contentUrl = $this->storage->resolveUri($object, 'file'); - return $this->normalizer->normalize($object, $format, $context); - } + return $this->normalizer->normalize($object, $format, $context); + } - public function supportsNormalization($data, ?string $format = null, array $context = []): bool - { - if (isset($context[self::ALREADY_CALLED])) { - return false; - } + public function supportsNormalization($data, ?string $format = null, array $context = []): bool + { - return $data instanceof MediaObject; + if (isset($context[self::ALREADY_CALLED])) { + return false; } + + return $data instanceof MediaObject; + } + + public function getSupportedTypes(?string $format): array + { + return [ + MediaObject::class => true, + ]; + } } + ``` ### Making a Request to the `/media_objects` Endpoint @@ -314,25 +323,26 @@ class MediaObjectTest extends ApiTestCase public function testCreateAMediaObject(): void { - $file = new UploadedFile('fixtures/files/image.png', 'image.png'); + // The file "image.jpg" is the folder fixtures which is in the project dir + $file = new UploadedFile(__DIR__ . '/../fixtures/image.jpg', 'image.jpg'); $client = self::createClient(); - $client->request('POST', '/media_objects', [ - 'headers' => ['Content-Type' => 'multipart/form-data'], - 'extra' => [ - // If you have additional fields in your MediaObject entity, use the parameters. - 'parameters' => [ - 'title' => 'My file uploaded', - ], - 'files' => [ - 'file' => $file, - ], - ] + $client->request('POST', 'http://localhost:8888/api/media_objects', [ + 'headers' => ['Content-Type' => 'multipart/form-data'], + 'extra' => [ + // If you have additional fields in your MediaObject entity, use the parameters. + 'parameters' => [ + // 'title' => 'title' + ], + 'files' => [ + 'file' => $file, + ], + ] ]); $this->assertResponseIsSuccessful(); $this->assertMatchesResourceItemJsonSchema(MediaObject::class); $this->assertJsonContains([ - 'title' => 'My file uploaded', + // 'title' => 'My file uploaded', ]); } } From 7cc70df8cca122a6a8a3c873b25ce1ea8da37374 Mon Sep 17 00:00:00 2001 From: MaximumP Date: Mon, 18 Mar 2024 13:04:10 +0100 Subject: [PATCH 16/27] Update Caddyfile path in docs (#1908) * Update index.md Adapted path to Caddyfile * Update caddy.md Adapted path to Caddyfile --- distribution/caddy.md | 2 +- distribution/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/caddy.md b/distribution/caddy.md index b6fa7a22f69..af0e3da931a 100644 --- a/distribution/caddy.md +++ b/distribution/caddy.md @@ -19,7 +19,7 @@ For instance, when you create your own Symfony controllers serving HTML pages, or when using bundles such as EasyAdmin or SonataAdmin. To do so, you have to tweak the rules used to route the requests. -Open `api-platform/api/docker/caddy/Caddyfile` and modify the expression. +Open `api-platform/api/frankenphp/Caddyfile` and modify the expression. You can use [any CEL (Common Expression Language) expression](https://caddyserver.com/docs/caddyfile/matchers#expression) supported by Caddy. For instance, if you want to route all requests to a path starting with `/admin` to the API, modify the existing expression like this: diff --git a/distribution/index.md b/distribution/index.md index 4b4e16912cf..c91cac6a5e7 100644 --- a/distribution/index.md +++ b/distribution/index.md @@ -201,7 +201,7 @@ You'll need to add a security exception in your browser to accept the self-signe for this container when installing the framework. Later you will probably replace this welcome screen by the homepage of your Next.js application. If you don't plan to create -a Progressive Web App, you can remove the `pwa/` directory as well as the related lines in `docker-compose*.yml` and in `api/docker/caddy/Caddyfile` (don't do it +a Progressive Web App, you can remove the `pwa/` directory as well as the related lines in `docker-compose*.yml` and in `api/frankenphp/Caddyfile` (don't do it now, we'll use this container later in this tutorial). Click on the "API" button, or go to `https://localhost/docs/`: From 2b0a32c023579e1ee1fb06883f9fc1b1c596525d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= Date: Mon, 18 Mar 2024 13:05:24 +0100 Subject: [PATCH 17/27] doc(UploadFile): fix #1893 doc of Uploading to an Existing Resource with its Fields (#1897) - Adding the getSupport method to make the class compatible with Symfony 7. - Switching from UploadedFile to File for consistency with Vich. Co-authored-by: Kinhelm --- core/file-upload.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/file-upload.md b/core/file-upload.md index 7014d0b01bf..b0b30e57983 100644 --- a/core/file-upload.md +++ b/core/file-upload.md @@ -462,19 +462,28 @@ We also need to make sure the field containing the uploaded file is not denormal namespace App\Serializer; -use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; final class UploadedFileDenormalizer implements DenormalizerInterface { - public function denormalize($data, string $type, string $format = null, array $context = []): UploadedFile + public function denormalize($data, string $type, string $format = null, array $context = []): File { return $data; } - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, $type, $format = null, array $context = []): bool + { + return $data instanceof File; + } + + public function getSupportedTypes(?string $format): array { - return $data instanceof UploadedFile; + return [ + 'object' => null, + '*' => false, + File::class => true, + ]; } } ``` From ac4a8645e5f596a83e36c23d38450af2f6f6c847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Suwi=C5=84ski?= Date: Tue, 19 Mar 2024 08:24:31 +0100 Subject: [PATCH 18/27] admin resolves semantincs by @id (iris) --- admin/schema.org.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/schema.org.md b/admin/schema.org.md index 61769e2e53c..feb76d1b561 100644 --- a/admin/schema.org.md +++ b/admin/schema.org.md @@ -18,7 +18,7 @@ To configure which property should be shown to represent your entity, map the pr // api/src/Entity/Person.php ... -#[ApiProperty(types: ["https://schema.org/name"])] +#[ApiProperty(iris: ["https://schema.org/name"])] private $name; ... From 2782d9494041a210bdef1b6200da4b69c43004f6 Mon Sep 17 00:00:00 2001 From: Joni Halme Date: Tue, 26 Mar 2024 13:23:42 +0200 Subject: [PATCH 19/27] Fix typo in validation.md --- core/validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/validation.md b/core/validation.md index 7676328d599..407ae9fd64d 100644 --- a/core/validation.md +++ b/core/validation.md @@ -208,7 +208,7 @@ use ApiPlatform\Metadata\ApiResource; use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( - validationContext: ['groups' => [Book::class, 'validationGroups'] + validationContext: ['groups' => [Book::class, 'validationGroups']] )] class Book { From 9c6d206626e8777a783330e85957d488e2dc1283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Monterisi?= Date: Fri, 29 Mar 2024 17:34:20 +0100 Subject: [PATCH 20/27] Remove Foundry from alternatives It's not an alternative anymore since it's the snippets. --- distribution/testing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/distribution/testing.md b/distribution/testing.md index 78dc2bbc86c..51a00f44421 100644 --- a/distribution/testing.md +++ b/distribution/testing.md @@ -372,7 +372,6 @@ The API Platform Demo [contains a CD workflow](https://github.com/api-platform/d You may also be interested in these alternative testing tools (not included in the API Platform distribution): * [Hoppscotch](https://docs.hoppscotch.io/features/tests), create functional test for your API -* [Foundry](https://github.com/zenstruck/foundry), a modern fixtures library that will replace Alice as the recommended fixtures library soon; * [Hoppscotch](https://docs.hoppscotch.io/documentation/features/rest-api-testing/), create functional test for your API Platform project using a nice UI, benefit from its Swagger integration and run tests in the CI using [the command-line tool](https://docs.hoppscotch.io/cli); * [Behat](https://behat.org), a From 84771e760b177c9ae4b264c322a9d80f6f726e4c Mon Sep 17 00:00:00 2001 From: Jems <5523410+J3m5@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:30:48 +0200 Subject: [PATCH 21/27] Add instructions for building Next.js client with SSG locally in Docker Compose (#1918) * docs(deployment/docker-compose.md): Add a section Building Next.js client locally with SSG Added detailed instructions for building a Next.js client with Static Site Generation (SSG) in a Docker Compose environment. The steps include adjustments to the compose.prod.yaml file, starting the php service container, optional creation of a .env file for the Next.js client, building the pwa service, and finally bringing up the full project. This setup ensures the Next.js client can access the API at build time for static page generation. * docs(create-client/nextjs.md): Generating a production build locally with docker compose Updated the Next.js documentation to include instructions for generating a production build locally using Docker Compose. The update provides a link to the detailed steps in the Docker Compose deployment guide. * docs(deployment/kubernetes.ms): Added Docker build command for SSG in Kubernetes doc Added optional Docker build command for SSG projects in Kubernetes doc. This command allows the PWA to access a locally running API during the build process, useful for generating static pages at build time. * docs(deployement/docker-compose): add a step to override the php env variables To ensure TRUSTED_HOSTS and MERCURE_PUBLIC_URL remain valid when altering SERVER_NAME to use an HTTP scheme or include a port, these variables must be overridden. This adjustment accommodates configurations where SERVER_NAME deviates from the standard HTTPS format or includes port specifications. * docs(deployement/docker-compose): adapt docs for variables update --------- Co-authored-by: JH --- create-client/nextjs.md | 4 +++ deployment/docker-compose.md | 69 ++++++++++++++++++++++++++++++++++-- deployment/kubernetes.md | 18 ++++++---- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/create-client/nextjs.md b/create-client/nextjs.md index 58301befe78..4320c89ad7f 100644 --- a/create-client/nextjs.md +++ b/create-client/nextjs.md @@ -98,6 +98,10 @@ yarn dev Go to `http://localhost:3000/books/` to start using your app. +## Generating a production build locally with docker compose + +If you want to generate a production build locally with docker compose, follow [these instructions](../deployment/docker-compose.md) + ## Screenshots ![List](images/nextjs/create-client-nextjs-list.png) diff --git a/deployment/docker-compose.md b/deployment/docker-compose.md index 364161d0b7d..4b7798336d1 100644 --- a/deployment/docker-compose.md +++ b/deployment/docker-compose.md @@ -89,8 +89,10 @@ Go to `https://your-domain-name.example.com` and enjoy! Alternatively, if you don't want to expose an HTTPS server but only an HTTP one, run the following command: -```console -SERVER_NAME=:80 \ +```bash +SERVER_NAME=http://localhost \ +MERCURE_PUBLIC_URL=http://localhost/.well-known/mercure \ +TRUSTED_HOSTS='^localhost|php$' \ APP_SECRET=ChangeMe \ CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ docker compose -f -compose.yaml -f compose.prod.yaml up --wait @@ -114,3 +116,66 @@ As a shortcut, `private_ranges` may be configured to trust all private IP ranges + trusted_proxies private_ranges +} ``` + +## Building Next.js client locally with SSG + +When deploying API Platform with Docker Compose and you need to build a Next.js client that utilizes Static Site Generation (SSG), a specific setup is required. +This setup ensures the Next.js client can access the API at build time to generate static pages. + +### Configuration Steps + +#### 1. Adjust the compose.prod.yaml file + +Modify the pwa service to ensure network communication between the pwa and php services during the build: + +```yaml + pwa: + build: + context: ./pwa + target: prod + network: host + extra_hosts: + - php=127.0.0.1 +``` + +#### 2. Build and start the php service + +Begin by starting the php service container: + +```bash +SERVER_NAME=http://localhost \ +MERCURE_PUBLIC_URL=http://localhost/.well-known/mercure \ +TRUSTED_HOSTS='^localhost|php$' \ +APP_SECRET=!ChangeMe! \ +CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ +POSTGRES_PASSWORD=!ChangeMe! \ +docker compose -f compose.yaml -f compose.prod.yaml up -d --build --wait php +``` + +#### 3. Optional: Env file with create-client + +If your are using the [create-client](../create-client/nextjs.md) generator inside your Next.js client, you need to create a `.env` file in the `pwa` directory with the `NEXT_PUBLIC_ENTRYPOINT` environment variable to ensure the Next.js client knows where to find the API: + +```dotenv +NEXT_PUBLIC_ENTRYPOINT=http://php +``` + +#### 4. Build the pwa service + +```bash +docker compose -f compose.yaml -f compose.prod.yaml build pwa +``` + +#### 5. Finally, bring up the full project + +```bash +SERVER_NAME=http://localhost \ +MERCURE_PUBLIC_URL=http://localhost/.well-known/mercure \ +TRUSTED_HOSTS='^localhost|php$' \ +APP_SECRET=!ChangeMe! \ +CADDY_MERCURE_JWT_SECRET=ChangeThisMercureHubJWTSecretKey \ +POSTGRES_PASSWORD=!ChangeMe! \ +docker compose -f compose.yaml -f compose.prod.yaml up -d --wait +``` + +These steps ensure the Next.js client can statically generate pages by accessing the API during the build process. diff --git a/deployment/kubernetes.md b/deployment/kubernetes.md index 415385ce64b..b72316304f0 100644 --- a/deployment/kubernetes.md +++ b/deployment/kubernetes.md @@ -44,6 +44,12 @@ docker build -t gcr.io/test-api-platform/caddy:0.1.0 -t gcr.io/test-api-platform docker build -t gcr.io/test-api-platform/pwa:0.1.0 -t gcr.io/test-api-platform/pwa:latest pwa --target prod ``` +Optional: If your pwa project use Static Site Generation (SSG) and you need to build it against the API running locally, you can build the pwa with the command below. + +```bash +docker build -t gcr.io/test-api-platform/pwa:0.1.0 -t gcr.io/test-api-platform/pwa:latest pwa --target prod --network=host --add-host php=127.0.0.1 +``` + #### 2. Push your images to your Docker registry ```console @@ -53,12 +59,12 @@ docker push gcr.io/test-api-platform/caddy docker push gcr.io/test-api-platform/pwa ``` -Optional push the version images: +Optional: push the version images: ```console -docker push gcr.io/test-api-platform/php:0.1.0 -docker push gcr.io/test-api-platform/caddy:0.1.0 -docker push gcr.io/test-api-platform/pwa:0.1.0 +docker push gcr.io/test-api-platform/php:0.1.0 +docker push gcr.io/test-api-platform/caddy:0.1.0 +docker push gcr.io/test-api-platform/pwa:0.1.0 ``` The result should look similar to these images. @@ -114,7 +120,7 @@ Replace the values with the image parameters from the stage above. The parameter `php.appSecret` is the `AppSecret` from ./.env Fill the rest of the values with the correct settings. For available options see /helm/api-platform/values.yaml. -If you want a test deploy you can set corsAllowOrigin='*' +If you want a test deploy you can set corsAllowOrigin='\*' After a successful installation, there is a message at the end. You can copy these commands and execute them to set a port-forwarding and @@ -166,7 +172,7 @@ You can upgrade with the same command from the installation and pass all paramet ### 2. Use :latest tags Infos about [best practices for tagging images for Kubernetes](https://kubernetes.io/docs/concepts/containers/images/) -You have to use the *.image.pullPolicy=Always see the last 3 parameters. +You have to use the \*.image.pullPolicy=Always see the last 3 parameters. ```console PHP_POD=$(kubectl --namespace=bar get pods -l app=php -o jsonpath="{.items[0].metadata.name}") From eb85fcf1a5c1fa6b1bf435c9768468bc712ba26d Mon Sep 17 00:00:00 2001 From: simon511000 Date: Wed, 17 Apr 2024 14:28:25 +0200 Subject: [PATCH 22/27] Add missing private to mailer property --- core/state-processors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state-processors.md b/core/state-processors.md index 7149b994207..c8c6e5277fc 100644 --- a/core/state-processors.md +++ b/core/state-processors.md @@ -101,7 +101,7 @@ final class UserProcessor implements ProcessorInterface private ProcessorInterface $persistProcessor, #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')] private ProcessorInterface $removeProcessor, - MailerInterface $mailer, + private MailerInterface $mailer, ) { } From 665d32f480dd84b1d9914d617b83b8997eb458a3 Mon Sep 17 00:00:00 2001 From: mboultoureau Date: Thu, 18 Apr 2024 11:19:04 +0200 Subject: [PATCH 23/27] fix: autowire item provider --- core/state-providers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state-providers.md b/core/state-providers.md index 7f6ad3d772b..b5de9769c54 100644 --- a/core/state-providers.md +++ b/core/state-providers.md @@ -153,7 +153,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire; final class BookRepresentationProvider implements ProviderInterface { public function __construct( - #[Autowire('api_platform.doctrine.orm.state.item_provider')] + #[Autowire(service: 'api_platform.doctrine.orm.state.item_provider')] private ProviderInterface $itemProvider, ) { From 0249fa835120642427102c7f83f61eaabee0434c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20H=C3=A9lias?= Date: Mon, 29 Apr 2024 10:55:10 +0200 Subject: [PATCH 24/27] fix: Use NormalizerInterface --- core/serialization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/serialization.md b/core/serialization.md index 7a22f360f3a..20da77f4f2d 100644 --- a/core/serialization.md +++ b/core/serialization.md @@ -911,11 +911,11 @@ Here is an example: namespace App\Serializer; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class BookAttributeNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface +class BookAttributeNormalizer implements NormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; From c957aa94bec73880a1206154a4b68a0b39f6b076 Mon Sep 17 00:00:00 2001 From: Nozarashi Date: Fri, 19 Apr 2024 21:13:23 +0200 Subject: [PATCH 25/27] docs: remove enable_authenticator_manager option --- core/jwt.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/jwt.md b/core/jwt.md index 593f0bb4e47..a8f49813ad6 100644 --- a/core/jwt.md +++ b/core/jwt.md @@ -64,8 +64,6 @@ security: password_hashers: App\Entity\User: 'auto' - # https://symfony.com/doc/current/security/authenticator_manager.html - enable_authenticator_manager: true # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: # used to reload user from session & other features (e.g. switch_user) From e3a1488768d2a9fd7c5487018992dc83e7efc330 Mon Sep 17 00:00:00 2001 From: matthieu-delisle <94405658+matthieu-delisle@users.noreply.github.com> Date: Mon, 29 Apr 2024 07:23:19 -0400 Subject: [PATCH 26/27] kubernetes: Messenger with FrankenPHP (#1939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(kubernetes) : Adding instruction for the messenger queue with FrankenPHP image The readinessProbe and livenessProbe suggested in the documentation failed because the /bin/ps wasn't installed on the Docker Image. * Update deployment/kubernetes.md Co-authored-by: Kévin Dunglas * Update kubernetes.md * Update kubernetes.md * lint --------- Co-authored-by: Kévin Dunglas --- deployment/kubernetes.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/deployment/kubernetes.md b/deployment/kubernetes.md index b72316304f0..fb8255f77d4 100644 --- a/deployment/kubernetes.md +++ b/deployment/kubernetes.md @@ -225,10 +225,31 @@ commandArgs: ['messenger:consume', 'async', '--memory-limit=100M'] The `readinessProbe` and the `livenessProble` can not use the default `docker-healthcheck` but should test if the command is running. +First, make sure to install the `/bin/ps` binary, otherwise the `readinessProbe` and `livenessProbe` will fail: + + + +```patch +# api/Dockerfile + +RUN apt-get update && apt-get install --no-install-recommends -y \ ++ procps \ +# ... +``` + + + +Then, update the probes: + ```yaml readinessProbe: exec: command: ["/bin/sh", "-c", "/bin/ps -ef | grep messenger:consume | grep -v grep"] initialDelaySeconds: 120 periodSeconds: 3 +livenessProbe: + exec: + command: ["/bin/sh", "-c", "/bin/ps -ef | grep messenger:consume | grep -v grep"] + initialDelaySeconds: 120 + periodSeconds: 3 ``` From 2a7ba969aa202ea84c65711e9375a7a5b45c7427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 29 Apr 2024 14:02:51 +0200 Subject: [PATCH 27/27] Using Xdebug with PHPStorm and various updates (#1923) * Using Xdebug with PHPStorm and various updates * Update debugging.md * Delete .idea/workspace.xml * ZTS support * linter --- core/performance.md | 27 ++++++++++-------- distribution/debugging.md | 58 ++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/core/performance.md b/core/performance.md index 94d0eb3f719..d1745cf409c 100644 --- a/core/performance.md +++ b/core/performance.md @@ -2,12 +2,12 @@ ## Enabling the Built-in HTTP Cache Invalidation System -Exposing a hypermedia API has [many advantages](http://blog.theamazingrando.com/in-band-vs-out-of-band.html). One of them +Exposing a hypermedia API has [many advantages](http://blog.theamazingrando.com/in-band-vs-out-of-band.html). One is the ability to know exactly which resources are included in HTTP responses created by the API. We used this specificity to make API Platform apps blazing fast. When the cache mechanism [is enabled](configuration.md), API Platform collects identifiers of every resource included in -a given HTTP response (including lists, embedded documents and subresources) and returns them in a special HTTP header +a given HTTP response (including lists, embedded documents, and subresources) and returns them in a special HTTP header called [Cache-Tags](https://support.cloudflare.com/hc/en-us/articles/206596608-How-to-Purge-Cache-Using-Cache-Tags-Enterprise-only-). A caching [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) supporting cache tags (e.g. Varnish, Cloudflare, @@ -16,7 +16,7 @@ This means that after the first request, all subsequent requests will not hit th from the cache. When a resource is modified, API Platform takes care of purging all responses containing it in the proxy’s -cache. This ensures that the content served will always be fresh, because the cache is purged in real time. Support for +cache. This ensures that the content served will always be fresh because the cache is purged in real-time. Support for most specific cases such as the invalidation of collections when a document is added or removed or for relationships and inverse relations is built-in. @@ -24,9 +24,9 @@ inverse relations is built-in. #### Built-in Caddy HTTP cache -The API Platform distribution relies on the [Caddy web server](https://caddyserver.com) which provide an official HTTP cache module called [cache-handler](https://github.com/caddyserver/cache-handler), that is based on [Souin](https://github.com/darkweak/souin). +The API Platform distribution relies on the [Caddy web server](https://caddyserver.com) which provides an official HTTP cache module called [cache-handler](https://github.com/caddyserver/cache-handler), that is based on [Souin](https://github.com/darkweak/souin). -The integration using the cache-handler is quite simple. You juste have to update the `api/Dockerfile` to build your caddy instance with the HTTP cache +The integration using the cache handler is quite simple. You just have to update the `api/Dockerfile` to build your caddy instance with the HTTP cache ```diff # Versions @@ -61,7 +61,7 @@ Update your Caddyfile with the following configuration: } } ``` -This will tell to caddy to use the HTTP cache and activate the tag based invalidation API. You can refer to the [cache-handler documentation](https://github.com/caddyserver/cache-handler) or the [souin website documentation](https://docs.souin.io) to learn how to configure the HTTP cache server. +This will tell to caddy to use the HTTP cache and activate the tag-based invalidation API. You can refer to the [cache-handler documentation](https://github.com/caddyserver/cache-handler) or the [souin website documentation](https://docs.souin.io) to learn how to configure the HTTP cache server. Setup the HTTP cache invalidation in your API Platform project ```yaml @@ -96,6 +96,7 @@ api_platform: ``` ## Configuration + Support for reverse proxies other than Varnish or Caddy with the HTTP cache module can be added by implementing the `ApiPlatform\HttpCache\PurgerInterface`. Three purgers are available, the built-in caddy http cache purger (`api_platform.http_cache.purger.souin`), the http tags (`api_platform.http_cache.purger.varnish.ban`), the surrogate key implementation (`api_platform.http_cache.purger.varnish.xkey`). You can specify the implementation using the `purger` configuration node, @@ -411,7 +412,7 @@ More details are available on the [pagination documentation](pagination.md#parti Blackfire.io allows you to monitor the performance of your applications. For more information, visit the [Blackfire.io website](https://blackfire.io/). -To configure Blackfire.io follow these simple steps: +To configure Blackfire.io follow these steps: 1. Add the following to your `compose.override.yaml` file: @@ -444,12 +445,14 @@ export BLACKFIRE_SERVER_TOKEN=xxxxxxxxxx 3. Install and configure the Blackfire probe in the app container, by adding the following to your `./Dockerfile`: ```dockerfile - RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \ - && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/alpine/amd64/$version \ + RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION.(PHP_ZTS ? '-zts' : '');") \ + && architecture=$(uname -m) \ + && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/$architecture/$version \ && mkdir -p /tmp/blackfire \ - && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \ - && mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get('extension_dir');")/blackfire.so \ - && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8307\n" > $PHP_INI_DIR/conf.d/blackfire.ini + && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \ + && mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get ('extension_dir');")/blackfire.so \ + && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8307\n" > $PHP_INI_DIR/conf.d/blackfire.ini \ + && rm -rf /tmp/blackfire /tmp/blackfire-probe.tar.gz ``` 4. Rebuild and restart all your containers diff --git a/distribution/debugging.md b/distribution/debugging.md index 728137aa573..0575a303ed2 100644 --- a/distribution/debugging.md +++ b/distribution/debugging.md @@ -4,31 +4,43 @@ ## Xdebug -The default Docker stack is shipped without a Xdebug stage. It's easy -though to add [Xdebug](https://xdebug.org/) to your project, for development -purposes such as debugging tests or remote API requests. +For development purposes such as debugging tests or remote API requests, +[Xdebug](https://xdebug.org/) is shipped by default with the API Platform distribution. -## Add a Development Stage to the Dockerfile +To enable it, run: -To avoid deploying API Platform to production with an active Xdebug extension, -it's recommended to add a custom stage to the end of the `api/Dockerfile`. +```console +XDEBUG_MODE=debug docker compose up --wait +``` -```Dockerfile -# api/Dockerfile -FROM api_platform_php as api_platform_php_dev +## Using Xdebug with PhpStorm -ARG XDEBUG_VERSION=3.1.3 -RUN set -eux; \ - apk add --no-cache --virtual .build-deps $PHPIZE_DEPS; \ - pecl install xdebug-$XDEBUG_VERSION; \ - docker-php-ext-enable xdebug; \ - apk del .build-deps -``` +First, [create a PHP debug remote server configuration](https://www.jetbrains.com/help/phpstorm/creating-a-php-debug-server-configuration.html): + +1. In the `Settings/Preferences` dialog, go to `PHP | Servers` +2. Create a new server: + * Name: `api` (or whatever you want to use for the variable `PHP_IDE_CONFIG`) + * Host: `localhost` (or the one defined using the `SERVER_NAME` environment variable) + * Port: `443` + * Debugger: `Xdebug` + * Check `Use path mappings` + * Map the local `api/` directory to the `/app` absolute path on the server + +You can now use the debugger! + +1. In PhpStorm, open the `Run` menu and click on `Start Listening for PHP Debug Connections` +2. Add the `XDEBUG_SESSION=PHPSTORM` query parameter to the URL of the page you want to debug or use [other available triggers](https://xdebug.org/docs/step_debug#activate_debugger). + Alternatively, you can use [the Xdebug extension](https://xdebug.org/docs/step_debug#browser-extensions) for your preferred web browser. + +3. On the command-line, we might need to tell PhpStorm which [path mapping configuration](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging-cli.html#configure-path-mappings) should be used, set the value of the PHP_IDE_CONFIG environment variable to `serverName=api`, where `api` is the name of the debug server configured higher. + + Example: -## Configure Xdebug with Docker Compose Override + ```console + XDEBUG_SESSION=1 PHP_IDE_CONFIG="serverName=api" php bin/console ... + ``` -XDebug is shipped by default with the API Platform distribution. -To enable it, run: `XDEBUG_MODE=debug docker compose up --wait` +## Using Xdebug With VSCode If you are using VSCode, use the following `launch.json` to debug. Note that this configuration includes the path mappings for the Docker image. @@ -51,8 +63,10 @@ Note that this configuration includes the path mappings for the Docker image. } ``` -Note: On Linux, the `client_host` setting of `host.docker.internal` may not work. -In this case you will need the actual local IP address of your computer. +> [!NOTE] +> +> On Linux, the `client_host` setting of `host.docker.internal` may not work. +> In this case you will need the actual local IP address of your computer. ## Troubleshooting @@ -64,6 +78,6 @@ $ docker compose exec php \ php --version PHP … - with Xdebug v3.1.3, Copyright (c) 2002-2021, by Derick Rethans + with Xdebug v…, Copyright (c) 2002-2021, by Derick Rethans … ```