SQS Sample Application
Build a consumer app that polls an SQS queue and writes every message to a file. You use Winter Boot for the application runtime and the Winter SQS module from Winter Modules for queue access. You test the whole flow locally with ElasticMQ, an SQS-compatible emulator.
Prerequisites
Section titled “Prerequisites”You need PHP 8.5 or later with the swoole and pcntl extensions. You also need Docker to run ElasticMQ and the AWS CLI to create the queue and send messages.
Start ElasticMQ before you run the app:
docker run -d -p 30932:9324 softwaremill/elasticmq-nativeProject structure
Section titled “Project structure”The sample uses this layout:
sqs/├── bin/│ └── application.php # Application entry point├── config/│ ├── application.yml # Winter Boot application config│ └── sqs-config.yml # SQS connections and consumers config├── src/│ ├── SqsSampleApplication.php # Main application class│ └── consumer/│ └── MessageFileWriterConsumer.php # SQS consumer worker└── composer.json # DependenciesInstall dependencies
Section titled “Install dependencies”Require the framework and the modules package:
composer require suvera/winter-boot suvera/winter-modulesSource files
Section titled “Source files”Switch between the four source files. Each tab shows the exact file from the sample.
The single entry point. It points Winter Boot at the config directory and at the sample namespace.
<?php
namespace dev\example;
use dev\winterframework\stereotype\WinterBootApplication;
#[WinterBootApplication( configDirectory: [__DIR__ . "/../config"], scanNamespaces: [ ['dev\\example', __DIR__ . ''] ])]class SqsSampleApplication {
public static function main(): void { $winterApp = new \dev\winterframework\core\app\WinterWebSwooleApplication(); $winterApp->run(self::class); }}The worker. It extends AbstractConsumer and appends one line per message with timestamp, message ID, queue name, and body.
<?php
namespace dev\example\consumer;
use dev\winterframework\sqs\consumer\AbstractConsumer;use dev\winterframework\sqs\consumer\ConsumerRecord;use dev\winterframework\sqs\consumer\ConsumerRecords;
class MessageFileWriterConsumer extends AbstractConsumer {
private const OUTPUT_FILE = '/tmp/sqs-messages.txt';
public function consume(ConsumerRecords $records): void { foreach ($records as $record) { /** @var ConsumerRecord $record */ $body = $record->getBody(); $messageId = $record->getMessageId(); $queueName = $record->getQueueName();
$line = sprintf( "[%s] MessageId: %s | Queue: %s | Body: %s", date('Y-m-d H:i:s'), $messageId, $queueName, $body ) . PHP_EOL;
file_put_contents( self::OUTPUT_FILE, $line, FILE_APPEND | LOCK_EX );
self::logInfo('Wrote message to file: ' . trim($line)); } }}AbstractConsumer gives you the logInfo() helper used on the last line. The worker process deletes each message from the queue after consume() returns successfully.
The launch script. It loads the Composer autoloader and starts the application.
<?php
use dev\example\SqsSampleApplication;
require_once(dirname(__DIR__) . '/vendor/autoload.php');
SqsSampleApplication::main();The sample declares framework dependencies with PSR-4 autoloading for its own namespace.
{ "name": "suvera/winter-boot-sqs-sample", "require": { "ext-pcntl": "*", "ext-swoole": "*", "suvera/winter-boot": "@dev", "suvera/winter-modules": "@dev" }, "autoload": { "psr-4": { "dev\\example\\": "src/" } }}The runnable sample in winter-boot-samples adds local path repositories for winter-boot and winter-modules so it resolves them from a sibling checkout. You do not need those entries when you install released packages from Packagist.
Configuration
Section titled “Configuration”Switch between the two config files. application.yml enables the module, and sqs-config.yml defines the connection and consumer.
The full sample file registers SqsModule and sets the server and app identity:
server: port: 8080 address: 0.0.0.0 context-path: /winter: application: name: SQS Sample Application id: sqs-sample-app version: 1.0.0modules: - module: dev\winterframework\sqs\SqsModule enabled: true configFile: sqs-config.ymlSee Configuration for every application.yml key.
Defines a primary connection to ElasticMQ and a my-queue-consumer consumer that polls my-queue with one worker:
sqs: connections: - name: __default__ version: latest region: elasticmq retries: 3 delaySeconds: 0
- name: primary region: elasticmq credentials: key: dummy secret: dummy endpoint: http://localhost:30932
consumers: - name: __default__ version: latest region: elasticmq waitTimeSeconds: 5 maxNumberOfMessages: 5 visibilityTimeout: 30 pollIntervalMs: 500
- name: my-queue-consumer connection: primary queueName: my-queue workerNum: 1 workerClass: dev\example\consumer\MessageFileWriterConsumer transientExceptions: []The __default__ entries supply shared defaults. The named consumer overrides the queue, connection, worker count, and worker class. See the SQS module for every connection and consumer property.
Run the app
Section titled “Run the app”Create the queue, start the app, send a message, then check the output file.
1. Create the queue:
aws sqs create-queue --queue-name my-queue \ --endpoint-url http://localhost:30932 \ --region elasticmq2. Start the application:
composer installphp bin/application.phpThe SQS worker starts polling my-queue as soon as the app boots.
3. Send a message:
aws sqs send-message --queue-url http://localhost:30932/queue/my-queue \ --message-body "Hello ElasticMQ" \ --endpoint-url http://localhost:30932 \ --region elasticmq4. Verify consumption:
cat /tmp/sqs-messages.txtYou see one line per consumed message, for example:
[2026-09-10 12:00:01] MessageId: abc-123 | Queue: my-queue | Body: Hello ElasticMQNext steps
Section titled “Next steps”- Read the SQS module for producer APIs (
SqsService), IAM-role setup, and consumer tuning (waitTimeSeconds,maxNumberOfMessages,visibilityTimeout). - Browse all Libraries when you need Kafka, S3, Redis, or Doctrine in the same app.