eonx-com / easy-serverless
Reusable functionalities to ease running PHP applications in serverless context
Requires
- php: ^8.4
- async-aws/secrets-manager: ^2.5
Requires (Dev)
- async-aws/dynamo-db: ^3.6
- async-aws/sqs: ^2.7
- bref/bref: ^2.3.15 || ^3.0
- bref/laravel-bridge: ^3.0
- bref/symfony-bridge: ^1.0.3 || ^2.0 || ^3.0
- bref/symfony-messenger: ^1.4.1
- laravel/framework: ^13.0
- monolog/monolog: ^3.9
- phpunit/phpunit: ^12.5|^13.0
- symfony/cache: ^7.4 || ^8.0
- symfony/config: ^7.4 || ^8.0
- symfony/framework-bundle: ^7.4 || ^8.0
- symfony/http-kernel: ^7.4 || ^8.0
Suggests
None
Provides
None
Conflicts
- easycorp/easyadmin-bundle: <4.13.1
Replaces
None
- 7.x-dev
- 7.0.0-alpha.2
- 7.0.0-alpha.1
- dev-master / 6.28.x-dev
- 6.27.0
- 6.26.1
- 6.26.0
- 6.25.2
- 6.25.1
- 6.25.0
- 6.24.1
- 6.24.0
- 6.23.1
- 6.23.0
- 6.22.5
- 6.22.4
- 6.22.3
- 6.22.2
- 6.22.1
- 6.22.0
- 6.21.0
- 6.20.1
- 6.20.0
- 6.19.4
- 6.19.3
- 6.19.2
- 6.19.1
- 6.19.0
- 6.18.5
- 6.18.4
- 6.18.3
- 6.18.2
- 6.18.1
- 6.18.0
- 6.17.6
- 6.17.5
- 6.17.4
- 6.17.3
- 6.17.2
- 6.17.1
- 6.17.0
- 6.16.0
- 6.15.2
- 6.15.1
- 6.15.0
- 6.14.17
- 6.14.16
- 6.14.15
- 6.14.14
- 6.14.13
- 6.14.12
- 6.14.11
- 6.14.10
- 6.14.9
- 6.14.8
- 6.14.7
- 6.14.6
- 6.14.5
- 6.14.4
- 6.14.3
- 6.14.2
- 6.14.1
- 6.14.0
- 6.13.0
- 6.12.7
- 6.12.6
- 6.12.5
- 6.12.4
- 6.12.3
- 6.12.2
- 6.12.1
- 6.12.0
- 6.11.1
- 6.11.0
- 6.10.0
- 6.9.0
This package is auto-updated.
Last update: 2026-11-09 10:58:57 UTC
README
---eonx_docs--- title: Introduction weight: 0 ---eonx_docs---
The purpose of this package is to ease running PHP applications within a serverless context by taking care of low level and repetitive functionalities so things should "just work".
Require package (Composer)
The recommended way to install this package is to use Composer:
$ composer require eonx-com/easy-serverless
Require dependencies
This package relies on Bref and its Symfony Bridge so make sure you do require them in your project:
$ composer require bref/bref $ composer require bref/symfony-bridge
SQS handler retries (Symfony Messenger)
EonX\EasyServerless\Bundle\SqsHandler\SqsHandler does not send failed messages back to the transport. When a
message fails, the handler keeps the same SQS message and changes its visibility timeout. SQS then delivers it again
after the delay. The handler counts attempts with the SQS ApproximateReceiveCount attribute.
Two settings control the retries:
$appMaxRetriesargument of theSqsHandlerservice (default3): the maximum number of attempts of a message, including the first one. This is a hard limit: the handler does not execute a message received more thanappMaxRetriestimes.- The retry strategy of the transport (
retry_strategyin the Messenger config): it decides whether the handler asks SQS for a retry after a failure, and the delay.max_retriesis the maximum number of retries after the first attempt.
The retry strategy stops asking for retries after max_retries retries, so a failing message usually runs
min(appMaxRetries, max_retries + 1) times. Only appMaxRetries is a hard limit: without partial batch failure,
when another record fails the Lambda invocation, SQS gives the whole batch back, and a message that the retry strategy
already stopped (or that succeeded) runs again while its receive count is within appMaxRetries. To use all the
retries of the strategy, set appMaxRetries to max_retries + 1.
When a failed attempt is the last one, the handler logs "SQS Record failed to process but will not be retried" and
reports the failure with willRetry = false. Then:
- On the attempt that reaches
appMaxRetries, the handler does not acknowledge the record, so SQS keeps the message. With partial batch failure, it puts the message back in the queue with a delay of 1 second. Without it, it throws an exception: the Lambda invocation fails and SQS gives the whole batch back. SQS moves the message to the dead letter queue (DLQ) when the receive count is more than themaxReceiveCountof the redrive policy. Thus, setmaxReceiveCounttoappMaxRetries: the message then goes to the DLQ after its last attempt in both modes. A higher value works only with partial batch failure (ReportBatchItemFailures): the handler puts the message back in the queue without executing it on each receive, until SQS moves it to the DLQ. Without partial batch failure, the handler acknowledges such a message, and SQS deletes it. - When the retry strategy stops before
appMaxRetries, the handler acknowledges the record. SQS deletes the message, and it does not go to the DLQ. SQS only counts receives, so on a next receive the handler could not tell whether the message was executed or only received (for example skipped below the Lambda time threshold). To send all failed messages to the DLQ, setappMaxRetriestomax_retries + 1.
The receive count also grows when a message is received but not executed: skipped by the handler, given back with the batch, or after a Lambda timeout. These receives count as attempts.
The log record has this context:
attempt: the number of the attempt (the SQSApproximateReceiveCount).app_max_retries: the value ofappMaxRetries.retry_stop_reason: why the message will not be retried.retry_strategy(the retry strategy does not allow one more retry, or the transport has no retry strategy),app_max_retries(the retry strategy allows one more retry, but the attempt is the last one allowed byappMaxRetries) orunrecoverable.
If the message fails with an exception that implements UnrecoverableExceptionInterface, the handler does not retry
it and does not put it back in the queue. SQS deletes the message, and it does not go to the DLQ.
Example: max_retries: 3, delay: 10000, multiplier: 8, appMaxRetries: 4 and maxReceiveCount: 4 give
4 attempts with delays of 10 s, 80 s and 640 s between them. Then the message goes to the DLQ.
SQS handler retries (Laravel)
EonX\EasyServerless\Laravel\SqsHandlers\SqsHandler uses the same approach. The number of attempts of a job is
the SQS ApproximateReceiveCount.
max_retriesof the queue connection (appMaxRetries, default3) is the maximum number of attempts of a job, including the first one.$triesof the job can only lower this limit. A value of0or less (unlimited in Laravel), or more thanmax_retries, givesmax_retries. In this case, the handler logs a warning when the job fails on its last attempt.- The delay before the next attempt is the delay given to
release(). When the job throws an exception, the worker releases it with the$backoffof the job for this attempt. When the job calls$this->release($delay)itself, this explicit delay is used, even if the job also has a$backoff. The delay is at least 1 second and at most 42000 seconds: SQS limits it to 12 hours from when the message was received.
On the last attempt, the worker fails the job (the failed() method of the job is called), and the handler logs
"SQS Record failed to process but will not be retried". If the job calls $this->release() on its last attempt, the
handler fails it like the worker would do on the next attempt. Then:
- On the attempt that reaches
max_retries, the handler does not acknowledge the record, as for Symfony: it puts the message back in the queue with partial batch failure, and throws an exception without it. SetmaxReceiveCounttomax_retriesso the message goes to the DLQ after its last attempt. - When the job reaches its own
$triesbeforemax_retries, the handler puts the message back in the queue with partial batch failure. When the message comes back, the handler puts it back again without running the job, until SQS moves it to the DLQ. Without partial batch failure, the handler acknowledges the record and logs a warning: SQS deletes the message, and it does not go to the DLQ. - The handler does not put the message back in the queue when the job has
$tries = 1, or when the job failed before its last attempt (for example$this->fail()in the job,FailOnExceptionor$maxExceptions). SQS deletes these messages, and they do not go to the DLQ.
For FIFO queues, when the handler puts a message back in the queue, the next messages of the same message group in the batch are put back in the queue too, to keep their order.
Without partial batch failure (partial_batch_failure: false), the handler also throws an exception for a retry: the
Lambda invocation fails, and SQS gives the whole batch back.
The log record has the same context as for Symfony. The values of retry_stop_reason are job_max_tries (the
$tries of the job), app_max_retries (max_retries of the queue connection), job_failed (the job failed before
its last attempt) and unrecoverable ($tries = 1).