Redis Sample Application
Build a REST app that performs Redis operations through a RedisDemoController. You use Winter Boot for the application runtime and the Winter Redis module from Winter Modules for single-node access via PhpRedisTemplate.
Prerequisites
Section titled “Prerequisites”You need PHP 8.5 or later with the swoole, redis, and pcntl extensions. You also need a Redis server. The sample config points at localhost:30637 with password redis123 — replace the host, port, and password with your own server.
Start Redis before you run the app:
docker run -d -p 30637:6379 --name redis \ redis:7 redis-server --requirepass redis123Project structure
Section titled “Project structure”The sample uses this layout:
redis/├── bin/│ └── application.php # Application entry point├── config/│ ├── application.yml # Winter Boot application config│ └── redis-config.yml # Redis single-node connections config├── src/│ ├── RedisSampleApplication.php # Main application class│ └── controller/│ └── RedisDemoController.php # Redis operations controller└── composer.json # DependenciesInstall dependencies
Section titled “Install dependencies”Require the framework and the modules package:
composer require suvera/winter-boot suvera/winter-modulesThe redis PHP extension must be installed separately:
pecl install redisSource files
Section titled “Source files”Switch between the 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 RedisSampleApplication {
public static function main(): void { $winterApp = new \dev\winterframework\core\app\WinterWebSwooleApplication(); $winterApp->run(self::class); }}The controller. It autowires PhpRedisTemplate and exposes one endpoint per Redis data type — strings, counters, hashes, lists, sets, expiry — plus a demo endpoint that runs them all in sequence.
<?php
namespace dev\example\controller;
use dev\winterframework\data\redis\phpredis\PhpRedisTemplate;use dev\winterframework\stereotype\Autowired;use dev\winterframework\stereotype\RestController;use dev\winterframework\stereotype\web\GetMapping;use dev\winterframework\stereotype\web\PostMapping;use dev\winterframework\stereotype\web\PathVariable;use dev\winterframework\stereotype\web\RequestParam;use Exception;
#[RestController]class RedisDemoController {
#[Autowired] private PhpRedisTemplate $redis;
/** * Check Redis connectivity and server info */ #[GetMapping(path: "/redisdemo/ping")] public function ping(): array { try { $pong = $this->redis->ping(); $info = $this->redis->info('server');
return [ 'success' => true, 'ping' => $pong, 'redisVersion' => $info['redis_version'] ?? 'unknown', 'mode' => $info['redis_mode'] ?? 'unknown' ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Set a string value, with optional expiry in seconds */ #[PostMapping(path: "/redisdemo/string")] public function setString( #[RequestParam] string $key, #[RequestParam] string $value, #[RequestParam(required: false)] ?int $ttl = null ): array { try { if ($ttl !== null && $ttl > 0) { $this->redis->setex($key, $ttl, $value); } else { $this->redis->set($key, $value); }
return [ 'success' => true, 'message' => "Key {$key} set successfully", 'ttl' => $ttl ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Get a string value along with its TTL */ #[GetMapping(path: "/redisdemo/string")] public function getString(#[RequestParam] string $key): array { try { $value = $this->redis->get($key);
return [ 'success' => true, 'key' => $key, 'value' => $value === false ? null : $value, 'exists' => $value !== false, 'ttl' => $this->redis->ttl($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Atomically increment a counter */ #[PostMapping(path: "/redisdemo/incr")] public function incr( #[RequestParam] string $key, #[RequestParam(required: false, defaultValue: '1')] ?int $by = 1 ): array { try { $value = $this->redis->incrBy($key, $by ?? 1);
return [ 'success' => true, 'key' => $key, 'value' => $value ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Set a hash field */ #[PostMapping(path: "/redisdemo/hash")] public function setHashField( #[RequestParam] string $key, #[RequestParam] string $field, #[RequestParam] string $value ): array { try { $this->redis->hSet($key, $field, $value);
return [ 'success' => true, 'message' => "Field {$field} set on hash {$key}" ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Get all fields of a hash */ #[GetMapping(path: "/redisdemo/hash")] public function getHash(#[RequestParam] string $key): array { try { $fields = $this->redis->hGetAll($key);
return [ 'success' => true, 'key' => $key, 'fields' => $fields === false ? [] : $fields, 'length' => $this->redis->hLen($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Push a value to the tail of a list */ #[PostMapping(path: "/redisdemo/list")] public function pushList( #[RequestParam] string $key, #[RequestParam] string $value ): array { try { $length = $this->redis->rPush($key, $value);
return [ 'success' => true, 'key' => $key, 'length' => $length ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Read all elements of a list */ #[GetMapping(path: "/redisdemo/list")] public function getList(#[RequestParam] string $key): array { try { return [ 'success' => true, 'key' => $key, 'values' => $this->redis->lRange($key, 0, -1), 'length' => $this->redis->lLen($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Add a member to a set */ #[PostMapping(path: "/redisdemo/set")] public function addSetMember( #[RequestParam] string $key, #[RequestParam] string $value ): array { try { $added = $this->redis->sAdd($key, $value);
return [ 'success' => true, 'key' => $key, 'added' => $added, 'size' => $this->redis->sCard($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Get all members of a set */ #[GetMapping(path: "/redisdemo/set")] public function getSet(#[RequestParam] string $key): array { try { return [ 'success' => true, 'key' => $key, 'members' => $this->redis->sMembers($key), 'size' => $this->redis->sCard($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Set expiry on a key in seconds */ #[PostMapping(path: "/redisdemo/expire")] public function expire( #[RequestParam] string $key, #[RequestParam] int $seconds ): array { try { return [ 'success' => true, 'key' => $key, 'expireSet' => $this->redis->expire($key, $seconds), 'ttl' => $this->redis->ttl($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Delete one or more keys */ #[PostMapping(path: "/redisdemo/delete")] public function delete(#[RequestParam] string $key): array { try { return [ 'success' => true, 'key' => $key, 'deleted' => $this->redis->del($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Get a key by path variable (route-style access) */ #[GetMapping(path: "/redisdemo/keys/{key}")] public function getByPath(#[PathVariable] string $key): array { try { $type = $this->redis->type($key);
return [ 'success' => true, 'key' => $key, 'type' => $type, 'ttl' => $this->redis->ttl($key) ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }
/** * Demo all operations together */ #[GetMapping(path: "/redisdemo/demo")] public function demoAllOperations(): array { try { $prefix = 'demo:' . time() . ':'; $strKey = $prefix . 'greeting'; $counterKey = $prefix . 'visits'; $hashKey = $prefix . 'user'; $listKey = $prefix . 'events'; $setKey = $prefix . 'tags';
// 1. String set/get $this->redis->set($strKey, 'Hello Redis'); $greeting = $this->redis->get($strKey);
// 2. Counter $this->redis->incrBy($counterKey, 5);
// 3. Hash $this->redis->hSet($hashKey, 'name', 'Winter Boot'); $this->redis->hSet($hashKey, 'lang', 'PHP'); $user = $this->redis->hGetAll($hashKey);
// 4. List $this->redis->rPush($listKey, 'login'); $this->redis->rPush($listKey, 'purchase'); $events = $this->redis->lRange($listKey, 0, -1);
// 5. Set $this->redis->sAdd($setKey, 'php'); $this->redis->sAdd($setKey, 'redis'); $tags = $this->redis->sMembers($setKey);
// 6. Expiry $this->redis->expire($strKey, 60); $ttl = $this->redis->ttl($strKey);
// 7. Cleanup $deleted = $this->redis->del($strKey, $counterKey, $hashKey, $listKey, $setKey);
return [ 'success' => true, 'operations' => [ 'string' => "Set and read back: {$greeting}", 'counter' => "Incremented {$counterKey} by 5", 'hash' => "Stored and read " . count($user) . " user fields", 'list' => "Pushed and read " . count($events) . " events", 'set' => "Added and read " . count($tags) . " tags", 'expire' => "TTL on {$strKey}: {$ttl}s", 'cleanup' => "Deleted {$deleted} demo keys" ], 'prefix' => $prefix, 'contentMatch' => $greeting === 'Hello Redis' ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }}The launch script. It loads the Composer autoloader and starts the application.
#!/usr/bin/env php<?php
use dev\example\RedisSampleApplication;
require_once(dirname(__DIR__) . '/vendor/autoload.php');
RedisSampleApplication::main();The sample declares framework dependencies with PSR-4 autoloading for its own namespace. The redis extension comes from PECL, not Composer.
{ "name": "suvera/winter-boot-redis-sample", "require": { "ext-pcntl": "*", "ext-swoole": "*", "ext-redis": "*", "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 redis-config.yml defines the primary single-node connection.
The full sample file registers RedisModule and sets the server and app identity:
server: port: 8080 address: 0.0.0.0 context-path: /winter: application: name: Redis Sample Application id: redis-sample-app version: 1.0.0modules: - module: dev\winterframework\data\redis\RedisModule enabled: true configFile: redis-config.ymlSee Configuration for every application.yml key.
Defines a primary single-node connection. Replace the host, port, and password with your own server.
phpredis: singles: - name: __default__ timeout: 2.5 readTimeout: 2.5
- name: primary host: localhost port: 30637 auth: redis123 timeout: 2.5 readTimeout: 2.5The __default__ entry supplies shared timeouts. The primary connection is also injectable by name with #[Autowired("primary")]. See the Redis module for clusters, arrays, sentinels, and Dynomite rings.
Run the app
Section titled “Run the app”Start the app, then call the endpoints to store a string, work a hash, and run the full demo.
1. Start the application:
composer installphp bin/application.php2. Check connectivity:
curl "http://localhost:8080/redisdemo/ping"3. Set a string with a 60s TTL:
curl -X POST "http://localhost:8080/redisdemo/string?key=my-key&value=Hello%20Redis&ttl=60"4. Get the string:
curl "http://localhost:8080/redisdemo/string?key=my-key"5. Increment a counter:
curl -X POST "http://localhost:8080/redisdemo/incr?key=visits&by=5"6. Work with a hash:
curl -X POST "http://localhost:8080/redisdemo/hash?key=user:1&field=name&value=Winter%20Boot"curl "http://localhost:8080/redisdemo/hash?key=user:1"7. Run the complete demo:
curl "http://localhost:8080/redisdemo/demo"The demo writes string, counter, hash, list, and set keys under a time-based prefix, verifies each read, sets a TTL, cleans up, and returns one status entry per operation.
Next steps
Section titled “Next steps”- Read the Redis module for cluster, array, sentinel, and Dynomite templates.
- Browse all Libraries when you need Kafka, SQS, S3, or Doctrine in the same app.