├── phpstan.neon.dist ├── .prettyci.composer.json ├── src ├── Resources │ └── config │ │ └── services.yaml ├── BrefMessengerBundle.php ├── DependencyInjection │ └── BrefMessengerExtension.php └── Sqs │ ├── SqsTransportFactory.php │ ├── SqsConsumer.php │ └── SqsTransport.php ├── .phpcs.xml.dist ├── .github ├── workflows │ └── phpstan.yml ├── FUNDING.yml └── CONTRIBUTING.md ├── composer.json ├── LICENSE └── README.md /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | paths: 4 | - src 5 | - tests 6 | -------------------------------------------------------------------------------- /.prettyci.composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "mnapoli/hard-mode": "^0.2.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Resources/config/services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | Bref\Messenger\Sqs\SqsTransportFactory: 3 | tags: ['messenger.transport_factory'] 4 | arguments: 5 | $sqs: '@Aws\Sqs\SqsClient' 6 | -------------------------------------------------------------------------------- /.phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | tests 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/BrefMessengerBundle.php: -------------------------------------------------------------------------------- 1 | load('services.yaml'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mnapoli # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, **thank you** for contributing! 4 | 5 | Here are a few rules to follow in order to ease code reviews and merging: 6 | 7 | - follow [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/) 8 | - run the test suite 9 | - write (or update) tests when applicable 10 | - write documentation for new features 11 | - use [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 12 | 13 | One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to "clean" your pull request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.). 14 | 15 | When creating your pull request on GitHub, please write a description which gives the context and/or explains why you are creating it. 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bref/symfony-messenger-sqs", 3 | "description": "Symfony Messenger bridge to run with SQS on AWS Lambda with Bref", 4 | "keywords": ["bref", "symfony", "messenger", "sqs", "aws", "aws-lambda"], 5 | "license": "MIT", 6 | "type": "library", 7 | "autoload": { 8 | "psr-4": { 9 | "Bref\\Messenger\\": "src/" 10 | } 11 | }, 12 | "autoload-dev": { 13 | "psr-4": { 14 | "Bref\\Messenger\\Test\\": "tests/" 15 | } 16 | }, 17 | "require": { 18 | "php": "^7.3", 19 | "ext-json": "*", 20 | "aws/aws-sdk-php": "^3.127", 21 | "symfony/messenger": "^4.3|^5.0", 22 | "symfony/config": "^4.3|^5.0", 23 | "symfony/dependency-injection": "^4.3|^5.0", 24 | "symfony/http-kernel": "^4.3|^5.0", 25 | "symfony/yaml": "^4.3|^5.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^8.0", 29 | "mnapoli/hard-mode": "^0.2.0", 30 | "phpstan/phpstan": "^0.12.0", 31 | "symfony/framework-bundle": "^4.3|^5.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Matthieu Napoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Sqs/SqsTransportFactory.php: -------------------------------------------------------------------------------- 1 | sqs = $sqs; 20 | $this->serializer = $serializer; 21 | } 22 | 23 | public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface 24 | { 25 | return new SqsTransport($this->sqs, $this->serializer, $dsn); 26 | } 27 | 28 | public function supports(string $dsn, array $options): bool 29 | { 30 | return preg_match('#^https://sqs\.[\w\-]+\.amazonaws\.com/.+#', $dsn) === 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Sqs/SqsConsumer.php: -------------------------------------------------------------------------------- 1 | bus = $bus; 36 | $this->serializer = $serializer; 37 | $this->logger = $logger; 38 | $this->transportName = $transportName; 39 | } 40 | 41 | /** 42 | * @param mixed $event 43 | */ 44 | public function consumeLambdaEvent($event): void 45 | { 46 | if (! is_array($event) || ! isset($event['Records'])) { 47 | throw new RuntimeException('The Lambda event data is not a SQS event'); 48 | } 49 | 50 | foreach ($event['Records'] as $record) { 51 | $envelope = $this->serializer->decode(['body' => $record['body']]); 52 | 53 | $this->consume($envelope); 54 | } 55 | } 56 | 57 | private function consume(Envelope $envelope): void 58 | { 59 | $this->bus->dispatch($envelope->with(new ReceivedStamp($this->transportName))); 60 | 61 | $message = $envelope->getMessage(); 62 | $this->logger->info('{class} was handled successfully.', [ 63 | 'class' => get_class($message), 64 | 'message' => $message, 65 | ]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Sqs/SqsTransport.php: -------------------------------------------------------------------------------- 1 | sqs = $sqs; 26 | $this->serializer = $serializer ?? new PhpSerializer; 27 | $this->queueUrl = $queueUrl; 28 | } 29 | 30 | public function send(Envelope $envelope): Envelope 31 | { 32 | $encodedMessage = $this->serializer->encode($envelope); 33 | 34 | $headers = $encodedMessage['headers'] ?? []; 35 | $arguments = [ 36 | 'MessageAttributes' => [ 37 | 'Headers' => [ 38 | 'DataType' => 'String', 39 | 'StringValue' => json_encode($headers, JSON_THROW_ON_ERROR), 40 | ], 41 | ], 42 | 'MessageBody' => $encodedMessage['body'], 43 | 'QueueUrl' => $this->queueUrl, 44 | ]; 45 | 46 | try { 47 | $result = $this->sqs->sendMessage($arguments); 48 | } catch (Throwable $e) { 49 | throw new TransportException($e->getMessage(), 0, $e); 50 | } 51 | 52 | if ($result->hasKey('MessageId') === false) { 53 | throw new TransportException('Could not add a message to the SQS queue'); 54 | } 55 | 56 | return $envelope; 57 | } 58 | 59 | public function get(): iterable 60 | { 61 | throw new Exception('Not implemented'); 62 | } 63 | 64 | public function ack(Envelope $envelope): void 65 | { 66 | throw new Exception('Not implemented'); 67 | } 68 | 69 | public function reject(Envelope $envelope): void 70 | { 71 | throw new Exception('Not implemented'); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This package is deprecated in favor of the more generic [bref/symfony-messenger](https://github.com/brefphp/symfony-messenger).** 2 | 3 | --- 4 | 5 | Bridge to use Symfony Messenger with SQS on AWS Lambda with [Bref](https://bref.sh). 6 | 7 | ## Installation 8 | 9 | This guide assumes that: 10 | 11 | - Symfony is installed 12 | - Symfony Messenger is installed 13 | - Bref is installed and [configured to deploy Symfony](https://bref.sh/docs/frameworks/symfony.html) 14 | - a SQS queue has already been created 15 | 16 | First, install this package: 17 | 18 | ``` 19 | composer require bref/symfony-messenger-sqs 20 | ``` 21 | 22 | Next, register the bundle in `config/bundles.php`: 23 | 24 | ```php 25 | return [ 26 | ... 27 | Bref\Messenger\BrefMessengerBundle::class => ['all' => true], 28 | ]; 29 | ``` 30 | 31 | Next, configure Symfony Messenger to dispatch a message via SQS: 32 | 33 | ```yaml 34 | # config/packages/messenger.yaml 35 | 36 | framework: 37 | messenger: 38 | transports: 39 | async: '%env(MESSENGER_TRANSPORT_DSN)%' 40 | routing: 41 | 'App\Message\MyMessage': async 42 | ``` 43 | 44 | Here, the `MyMessage` class will be dispatch to the `async` transport. We can now configure the `async` transport to use our SQS queue. 45 | 46 | To do that, let's configure the `MESSENGER_TRANSPORT_DSN` environment variable to contain the URL of the queue: 47 | 48 | ```dotenv 49 | MESSENGER_TRANSPORT_DSN=https://sqs.us-east-1.amazonaws.com/123456789101/my-queue 50 | ``` 51 | 52 | ### Sending messages 53 | 54 | Now that Messenger is configured with SQS, we can send messages using the `MessageBusInterface`. For example, in a controller: 55 | 56 | ```php 57 | class DefaultController extends AbstractController 58 | { 59 | public function index() 60 | { 61 | $this->dispatchMessage(new App\Message\MyMessage()); 62 | } 63 | } 64 | ``` 65 | 66 | Read [the Symfony documentation to learn more](https://symfony.com/doc/current/messenger.html#dispatching-the-message). 67 | 68 | ### Processing message 69 | 70 | Messages are sent to SQS, we now need to process those messages asynchronously. 71 | 72 | We can create a Lambda to do that in `serverless.yml`: 73 | 74 | ```yaml 75 | functions: 76 | worker: 77 | handler: consumer.php 78 | timeout: 120 # in seconds 79 | reservedConcurrency: 5 # max. 5 messages processed in parallel 80 | layers: 81 | - ${bref:layer.php-73} 82 | events: 83 | - sqs: 84 | arn: arn:aws:sqs:us-east-1:123456789101:my-queue 85 | # Only 1 item at a time to simplify error handling 86 | batchSize: 1 87 | ``` 88 | 89 | The Lambda handler will be `consumer.php`, a file we must create: 90 | 91 | ```php 92 | boot(); 101 | 102 | $sqsConsumer = $kernel->getContainer()->get(SqsConsumer::class); 103 | $sqsConsumer->consumeLambdaEvent($event); 104 | }); 105 | ``` 106 | 107 | Finally, we must configure the `SqsConsumer` service in `config/services.yaml` (this configuration relies on autowiring being enabled by default): 108 | 109 | ```yaml 110 | services: 111 | ... 112 | 113 | Bref\Messenger\Sqs\SqsConsumer: 114 | arguments: 115 | # Inject the transport name used in config/packages/messenger.yaml 116 | $transportName: 'async' 117 | public: true 118 | ``` 119 | --------------------------------------------------------------------------------