{"id":30176475,"url":"https://github.com/goktugcy/redisync","last_synced_at":"2026-01-20T16:42:32.348Z","repository":{"id":309216712,"uuid":"1035476261","full_name":"goktugcy/RediSync","owner":"goktugcy","description":"High-performance caching middleware for PHP that stores data in Redis while syncing with MySQL or PostgreSQL.","archived":false,"fork":false,"pushed_at":"2025-08-10T16:30:36.000Z","size":49,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-10T16:45:58.850Z","etag":null,"topics":["cache","mysql","php","php8","phpunit","postgre","redis","redis-cache"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/goktugcy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-10T13:37:22.000Z","updated_at":"2025-08-10T16:31:45.000Z","dependencies_parsed_at":"2025-08-10T16:46:01.671Z","dependency_job_id":"6e8b94e8-4a54-4373-9112-08d4db219dcc","html_url":"https://github.com/goktugcy/RediSync","commit_stats":null,"previous_names":["goktugcy/redisync"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/goktugcy/RediSync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goktugcy%2FRediSync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goktugcy%2FRediSync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goktugcy%2FRediSync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goktugcy%2FRediSync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/goktugcy","download_url":"https://codeload.github.com/goktugcy/RediSync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goktugcy%2FRediSync/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270364971,"owners_count":24571423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-14T02:00:10.309Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","mysql","php","php8","phpunit","postgre","redis","redis-cache"],"created_at":"2025-08-12T03:00:56.787Z","updated_at":"2026-01-20T16:42:32.341Z","avatar_url":"https://github.com/goktugcy.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RediSync\n\nHigh-performance HTTP caching for PHP with Redis storage and optional DB-driven invalidation/write-through (MySQL/PostgreSQL via Doctrine DBAL).\n\n![Packagist Version](https://img.shields.io/packagist/v/redisync/core?style=flat-square)\n![Total Downloads](https://img.shields.io/packagist/dt/redisync/core?style=flat-square)\n![PHP Version](https://img.shields.io/packagist/php-v/redisync/core?style=flat-square)\n![License](https://img.shields.io/packagist/l/redisync/core?style=flat-square)\n![PSR](https://img.shields.io/badge/PSR--7%2F17-1.x%20%7C%202.x-blue?style=flat-square)\n\n\u003e Zero-friction HTTP caching for PHP apps: PSR-15 middleware, Redis-backed, DB-aware invalidation.\n\nQuick nav: [Install](#install) · [Configuration](#configuration) · [Middleware](#middleware-usage) · [Facade](#facade-usage) · [Logging](#logging-psr-3) · [Write-through](#write-through-db-to-cache) · [Laravel](#laravel-quickstart) · [CLI](#cli) · [Notes](#notes) · [API contracts](#api-contracts-and-errors) · [Troubleshooting](#troubleshooting-installs-laravelcarbon-and-doctrine-dbal) · [Proof](#proof)\n\n## ✨ Features\n\n- PSR-15 middleware: automatic HTTP cache hit/miss flow.\n- PSR-7/17 support via nyholm/psr7.\n- GET/HEAD-only caching by default, optional bypass with the X-Bypass-Cache header.\n- Cache headers: X-RediSync-Cache (HIT/MISS) and Age on hits (PSR-15 and Laravel).\n- Conditional requests: automatic ETag generation and If-None-Match → 304.\n- Cache-Control aware: respects no-store and private (won't serve/store).\n- Vary safety: bypasses cache when Authorization or Cookie exist to avoid leakage.\n- Safe caching via status whitelist (default: [200]) and Content-Type allow list.\n- TTL map by path pattern/regex for per-endpoint TTL control.\n- CLI for cache ops: clear-cache, list-keys, key-info, warmup.\n- Doctrine DBAL-based DatabaseManager with invalidation hooks.\n- Write-through DB helper: update cache immediately after successful DB writes.\n- remember() helper: compute-or-cache convenience API (vanilla and Laravel facades).\n- PSR-3 logging hooks: cache hit/miss/store, bypass reasons, DB write-through.\n\n## 🔧 Install\n\nAdd to your project:\n\n```bash\ncomposer require redisync/core\n```\n\nRequirements: PHP 8.1+, Redis (via Predis 1.x or 2.x). Optional: Doctrine DBAL and DB drivers (pdo_mysql/pdo_pgsql) if you use DatabaseManager.\n\n## ⚙️ Configuration\n\nConfigure programmatically (ENV not required):\n\n```php\nuse RediSync\\Cache\\CacheManager;\n\n$cache = CacheManager::fromConfig([\n  'host' =\u003e '127.0.0.1',\n  'port' =\u003e 6379,\n  'database' =\u003e 0,\n  'prefix' =\u003e 'redisync:'\n]);\n```\n\nDatabaseManager (optional):\n\n```php\nuse RediSync\\Database\\DatabaseManager;\n\n$db = DatabaseManager::fromDsn('mysql://user:pass@127.0.0.1:3306/app?charset=utf8mb4');\n\n// Cache invalidation after data changes\n$db-\u003eonInvalidate(function (string $sql, array $params) use ($cache) {\n  if (str_starts_with(strtoupper(ltrim($sql)), 'UPDATE USERS')\n    || str_starts_with(strtoupper(ltrim($sql)), 'DELETE FROM USERS')\n    || str_starts_with(strtoupper(ltrim($sql)), 'INSERT INTO USERS')\n  ) {\n    $cache-\u003eclearByPattern('users:*');\n  }\n});\n```\n\n## 🧩 Middleware Usage\n\n```php\nuse Nyholm\\Psr7\\Factory\\Psr17Factory;\nuse RediSync\\Middleware\\CacheMiddleware;\nuse RediSync\\Utils\\KeyGenerator;\n\n$psr17 = new Psr17Factory();\n\n$middleware = new CacheMiddleware(\n  cache: $cache,\n  keys: new KeyGenerator('http', ignoredParams: ['nonce', '_ts']),\n  ttl: 300,\n  responseFactory: $psr17,\n  streamFactory: $psr17,\n  statusWhitelist: [200],\n  allowedContentTypes: ['application/json'],\n  ttlMap: [\n    '/public/*' =\u003e 60,\n    '#^/users/\\\\d+$#' =\u003e 300,\n  ],\n);\n\n// Add it to your PSR-15 stack (Mezzio, Slim, etc.). Middleware caches only GET/HEAD by default.\n// Conditional requests: send If-None-Match; 304 is returned when ETag matches (ETag is auto-generated if missing).\n// Cache-Control: requests with no-store bypass; responses with no-store/private are not stored.\n// Vary safety: Authorization/Cookie on the request bypass the cache to protect user-specific content.\n// To force-bypass: send header X-Bypass-Cache: 1. Responses include X-RediSync-Cache: HIT|MISS and Age.\n```\n\n### HTTP semantics: ETag, 304, no-store/private, vary\n\n- ETag: If the origin response doesn't include ETag, RediSync computes one from the body. Clients sending `If-None-Match` get `304 Not Modified` when it matches.\n- no-store/private: A request with `Cache-Control: no-store` bypasses cache. A response with `no-store` or `private` is not stored by RediSync (shared cache).\n- Vary safety: Requests carrying `Authorization` or `Cookie` headers bypass cache to avoid leaking personalized content.\n- Headers: On cache HITs RediSync adds `X-RediSync-Cache: HIT` and `Age`. On MISS it sets `X-RediSync-Cache: MISS`.\n\n## 🧩 Facade usage\n\n### Vanilla PHP (framework-agnostic)\n\n```php\nuse RediSync\\Cache\\CacheManager;\nuse RediSync\\Facades\\RediSync;\n\n$cache = CacheManager::fromConfig(['host' =\u003e '127.0.0.1', 'port' =\u003e 6379, 'database' =\u003e 0, 'prefix' =\u003e 'app:']);\nRediSync::setInstance($cache);\n\n// get / set\nRediSync::set('users:1', ['id' =\u003e 1, 'name' =\u003e 'Ada'], 300);\n$data = RediSync::get('users:1');\n\n// remember (compute-or-cache)\n$user = RediSync::remember('users:1', 300, function () {\n  // expensive work or DB fetch\n  return ['id' =\u003e 1, 'name' =\u003e 'Ada'];\n});\n\n// Evict: set with null deletes the key (by design)\nRediSync::set('users:1', null); // equivalent to delete\n// Bulk invalidation example\n// $cache-\u003eclearByPattern('users:*');\n```\n\n## 📜 Logging (PSR-3)\n\nRediSync logs key events with any PSR-3–compatible logger: `cache.hit`, `cache.miss`, `cache.set`, `cache.delete`, `cache.clear_by_pattern`, `httpcache.hit|miss|store|conditional_304|not_cacheable|bypass`, `db.execute`, `db.fetch_*`, `db.write_through.cache_updated`.\n\nVanilla PHP (Monolog):\n\n```php\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse RediSync\\Cache\\CacheManager;\nuse RediSync\\Facades\\RediSync;\n\n$logger = new Logger('app');\n$logger-\u003epushHandler(new StreamHandler('php://stdout'));\n\n$cache = CacheManager::fromConfig(['host' =\u003e '127.0.0.1', 'port' =\u003e 6379]);\n$cache-\u003esetLogger($logger);\nRediSync::setInstance($cache);\nRediSync::setLogger($logger); // optional facade shortcut\n```\n\nLaravel: LoggerInterface is automatically injected from the container. The ServiceProvider forwards the framework logger to CacheManager and DatabaseManager; no extra setup required.\n\n## Write-through DB to Cache\n\nUpdate cache immediately after a successful DB write (inside a transaction):\n\n```php\nuse RediSync\\Database\\DatabaseManager;\nuse RediSync\\Cache\\CacheManager;\n\n$db = DatabaseManager::fromDsn('sqlite:///:memory:');\n// ... create table/users ...\n\n$affected = $db-\u003ewriteThrough(\n  'UPDATE users SET name = :n WHERE id = :id', ['n' =\u003e 'alice', 'id' =\u003e 1],\n  $cache,\n  // Build cache entries from the write result\n  function (int $affected, array $params, \\Doctrine\\DBAL\\Connection $conn): array {\n    if ($affected \u003e 0) {\n      return [ ['key' =\u003e \"users:{$params['id']}\", 'value' =\u003e ['id' =\u003e $params['id'], 'name' =\u003e $params['n']], 'ttl' =\u003e 300] ];\n    }\n    return [];\n  }\n);\n```\n\nShortcut: you can also pass a simple associative array as the plan and use a default TTL:\n\n```php\n$db-\u003ewriteThrough(\n  'DELETE FROM users WHERE id = :id', ['id' =\u003e 1], $cache,\n  [ 'users:1' =\u003e null ], // set null or use clearByPattern in an onInvalidate callback\n  60\n);\n```\n\n## Laravel Quickstart\n\nAuto-discovery registers a Service Provider, Facades, and `redisync.cache` middleware.\n\n- Facade (controller) using remember():\n\n```php\nuse RediSync\\Bridge\\Laravel\\Facades\\RediSync; // static facade\npublic function show(int $id) {\n  $user = RediSync::remember(\"users:$id\", 300, fn() =\u003e \\App\\Models\\User::findOrFail($id)-\u003etoArray());\n  return response()-\u003ejson($user);\n}\n```\n\n- Route cache (GET):\n\n```php\nuse Illuminate\\Support\\Facades\\Route;\nRoute::middleware('redisync.cache')-\u003eget('/api/users/{id}', [UserController::class, 'show']);\n```\n\n- HTML cache (view) via RediSyncCache (array/string payloads):\n\n```php\nuse Illuminate\\Support\\Facades\\Auth;\nuse RediSync\\Bridge\\Laravel\\Facades\\RediSyncCache as Cache;\npublic function getProfile() {\n  $u = Auth::user(); if (! $u) return redirect('404');\n  $k = \"users:profile:{$u-\u003eid}\"; if ($h = Cache::get($k)) return response($h);\n  $h = view('profile', ['user' =\u003e $u])-\u003erender(); Cache::set($k, $h, 300); return response($h);\n}\n```\n\n- Data cache (array) via RediSyncCache:\n\n```php\nuse Illuminate\\Support\\Facades\\Auth;\nuse RediSync\\Bridge\\Laravel\\Facades\\RediSyncCache as Cache;\npublic function getProfileData() {\n  $u = Auth::user(); if (! $u) return redirect('404');\n  $k = \"users:data:{$u-\u003eid}\"; $d = Cache::get($k) ?: $u-\u003etoArray();\n  if (! Cache::get($k)) Cache::set($k, $d, 300);\n  return view('profile', ['user' =\u003e $u]);\n}\n```\n\n- Invalidation (events):\n\n```php\n// app/Providers/AppServiceProvider.php\npublic function boot(\\RediSync\\Cache\\CacheManager $cache): void\n{\n  \\App\\Models\\User::saved(fn() =\u003e $cache-\u003eclearByPattern('users:*'));\n  \\App\\Models\\User::deleted(fn() =\u003e $cache-\u003eclearByPattern('users:*'));\n}\n```\n\nNotes: Uses Laravel Redis config automatically. By default, JSON 200 responses are cached for ~300s. Bypass with header `X-Bypass-Cache: 1`.\n\nHTTP semantics in Laravel middleware:\n\n- GET/HEAD cache with `X-RediSync-Cache` (HIT/MISS) and `Age` on hits.\n- `If-None-Match` supported; returns `304 Not Modified` when matching the stored ETag (computed if absent).\n- Respects `Cache-Control: no-store` on requests and `no-store`/`private` on responses (won't store).\n- Requests containing `Authorization` or cookies bypass the cache for safety.\n\n### Write-through in Laravel\n\n```php\n// In a service or controller where you have the DB connection DSN\nuse RediSync\\Bridge\\Laravel\\Facades\\RediSyncCache as Cache;\nuse RediSync\\Database\\DatabaseManager;\n\n$db = DatabaseManager::fromDsn(env('DATABASE_URL'));\n$db-\u003ewriteThrough(\n  'INSERT INTO posts (title) VALUES (:t)', ['t' =\u003e $title],\n  app(\\RediSync\\Cache\\CacheManager::class),\n  fn(int $affected, array $p, \\Doctrine\\DBAL\\Connection $c) =\u003e $affected\n    ? [ ['key' =\u003e 'posts:latest', 'value' =\u003e /* recompute */ [], 'ttl' =\u003e 120] ]\n    : []\n);\n```\n\n## 🛠️ CLI\n\nUse the bundled CLI for quick cache operations. The tool reads Redis config from `config/config.php`.\n\n```bash\nvendor/bin/redisync help\n```\n\nCommands:\n\n- clear-cache [pattern]\n  - Delete keys by pattern (default: `*`).\n  - Example: `vendor/bin/redisync clear-cache users:*`\n- list-keys [pattern] [limit]\n  - List keys (default pattern `*`, limit `100`).\n  - Example: `vendor/bin/redisync list-keys api:* 50`\n- key-info \u003ckey\u003e\n  - Show TTL/type/size/exists.\n  - Example: `vendor/bin/redisync key-info users:1`\n- warmup [ttl]\n  - Read keys from STDIN and set placeholder values with TTL (default 60).\n  - Example:\n\n```bash\nprintf \"a\\nb\\n\" | vendor/bin/redisync warmup 30\n```\n\n## 📷 Proof\n\n![RediSync usage proof](https://rffureejqjzrbqzrcyxv.supabase.co/storage/v1/object/public/images/redisync.png)\n\n## 📝 Notes\n\n- Middleware caches only GET/HEAD requests by default.\n- Use status whitelist and Content-Type filters for safe caching.\n- TTL map allows per-path TTL control.\n\n### API contracts and errors\n\n- Cache null semantics: `set($key, null)` evicts the key to avoid ambiguity with `get()` returning null.\n- Exceptions: Redis/DB errors currently bubble up from underlying libraries. There’s no wrapper exception layer in 1.x; handle with try/catch in your app as needed.\n\n## Troubleshooting installs (Laravel/Carbon and Doctrine DBAL)\n\nIf your app uses Laravel 11 + Carbon 3, you may see a conflict involving `doctrine/dbal` and `carbonphp/carbon-doctrine-types` when installing `redisync/core`.\n\nWhat changed: RediSync no longer hard-requires `doctrine/dbal`. It's optional and only needed if you plan to use `DatabaseManager`.\n\n- Install RediSync first:\n\n  ```bash\n  composer require redisync/core\n  ```\n\n- If you need DB features, require a DBAL version compatible with your stack. For example:\n\n  ```bash\n  composer require doctrine/dbal:^3.8\n  ```\n\nIf Composer still reports conflicts, align DBAL with the versions compatible with your Laravel/Carbon lock (check `composer why doctrine/dbal` and `composer why-not doctrine/dbal:^3.10`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoktugcy%2Fredisync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoktugcy%2Fredisync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoktugcy%2Fredisync/lists"}