RedisDemoController. You use Winter Boot for the application runtime and the Winter Redis module from Winter Modules for single-node access via PhpRedisTemplate.
Prerequisites
You need PHP 8.5 or later with theswoole, 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 redis123
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 # Dependencies
Install dependencies
Require the framework and the modules package:composer require suvera/winter-boot suvera/winter-modules
redis PHP extension must be installed separately:
pecl install redis
Source files
Switch between the source files. Each tab shows the exact file from the sample.- RedisSampleApplication.php
- RedisDemoController.php
- bin/application.php
- composer.json
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 The runnable sample in
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/"
}
}
}
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
Switch between the two config files.application.yml enables the module, and redis-config.yml defines the primary single-node connection.
- application.yml
- redis-config.yml
The full sample file registers See Configuration for every
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.0
modules:
- module: dev\winterframework\data\redis\RedisModule
enabled: true
configFile: redis-config.yml
application.yml key.Defines a The
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.5
__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
Start the app, then call the endpoints to store a string, work a hash, and run the full demo. 1. Start the application:composer install
php bin/application.php
curl "http://localhost:8080/redisdemo/ping"
curl -X POST "http://localhost:8080/redisdemo/string?key=my-key&value=Hello%20Redis&ttl=60"
curl "http://localhost:8080/redisdemo/string?key=my-key"
curl -X POST "http://localhost:8080/redisdemo/incr?key=visits&by=5"
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"
curl "http://localhost:8080/redisdemo/demo"
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.