Scrapbook
GitHub Overview
matthiasmullie/scrapbook
PHP cache library, with adapters for e.g. Memcached, Redis, Couchbase, APC(u), SQL and additional capabilities (e.g. transactions, stampede protection) built on top.
Topics
Star History
Cache Library
Scrapbook
Overview
Scrapbook is a PHP cache library that supports multiple adapters including Memcached, Redis, Couchbase, APC(u), and SQL, providing PSR-6 and PSR-16 compliant caching functionality.
Details
Scrapbook is a PHP cache library that provides a unified interface to various cache backends. It's designed with a layered architecture where all components are KeyValueStore implementations that can be wrapped around each other. Built-in advanced features like transactions, stampede protection, and buffering enhance robustness, development ease, and scalability. It complies with both PSR-6 (Cache) and PSR-16 (SimpleCache) standards, making integration with existing PHP applications easy. It supports a wide range of storage backends including Memcached, Redis, SQLite, MySQL, APC, and Couchbase.
Pros and Cons
Pros
- Multi-Adapter: Rich backends including Memcached, Redis, SQL, APC
- PSR Compliant: Fully compliant with PSR-6 and PSR-16 standards
- Advanced Features: Transactions, stampede protection, buffering
- Layered Architecture: Build flexible cache systems by combining features
- Type Safe: Robust type checking and error handling
- Composer Ready: Easy installation and dependency management
Cons
- PHP Only: Cannot be used with other languages
- Learning Curve: Understanding required for advanced features
- Backend Dependent: Subject to chosen storage limitations
- Configuration Complexity: Multiple layer combinations can be complex
Key Links
Usage Examples
Basic Usage
<?php
use MatthiasMullie\Scrapbook\Adapters\MemoryStore;
use MatthiasMullie\Scrapbook\Adapters\Memcached;
// Memory store (for development/testing)
$cache = new MemoryStore();
// Using Memcached
$memcached = new \Memcached();
$memcached->addServer('localhost', 11211);
$cache = new Memcached($memcached);
// Basic operations
$cache->set('key', 'value');
$value = $cache->get('key'); // returns 'value'
$success = $cache->delete('key');
Using SQLite
<?php
use MatthiasMullie\Scrapbook\Adapters\SQLite;
// SQLite database connection
$pdo = new PDO('sqlite:cache.db');
$cache = new SQLite($pdo);
// Basic cache operations
$cache->set('user:123', ['name' => 'John', 'email' => '[email protected]']);
$user = $cache->get('user:123');
// Store with TTL
$cache->set('session:abc', $sessionData, time() + 3600); // Expires in 1 hour
Using Redis
<?php
use MatthiasMullie\Scrapbook\Adapters\Redis;
// Redis connection
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$cache = new Redis($redis);
// Set multiple values at once
$cache->setMultiple([
'product:1' => ['name' => 'Laptop', 'price' => 999],
'product:2' => ['name' => 'Mouse', 'price' => 25],
]);
// Get multiple values at once
$products = $cache->getMultiple(['product:1', 'product:2']);
Using PSR-6 Cache
<?php
use MatthiasMullie\Scrapbook\Psr6\Pool;
use MatthiasMullie\Scrapbook\Adapters\Redis;
// Create cache pool
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$cache = new Redis($redis);
$pool = new Pool($cache);
// PSR-6 interface operations
$item = $pool->getItem('expensive_operation');
if (!$item->isHit()) {
$result = performExpensiveOperation();
$item->set($result);
$item->expiresAfter(3600); // Expires in 1 hour
$pool->save($item);
}
$data = $item->get();
Using PSR-16 SimpleCache
<?php
use MatthiasMullie\Scrapbook\Psr16\SimpleCache;
use MatthiasMullie\Scrapbook\Adapters\Memcached;
// Create SimpleCache
$memcached = new \Memcached();
$memcached->addServer('localhost', 11211);
$cache = new Memcached($memcached);
$simpleCache = new SimpleCache($cache);
// Simple cache operations
$value = $simpleCache->get('key', 'default_value');
$simpleCache->set('key', 'new_value', 300); // Cache for 5 minutes
// Multiple operations
$values = $simpleCache->getMultiple(['key1', 'key2'], 'default');
$simpleCache->setMultiple(['key1' => 'value1', 'key2' => 'value2'], 600);
Buffering Feature
<?php
use MatthiasMullie\Scrapbook\Buffered\BufferedStore;
use MatthiasMullie\Scrapbook\Adapters\Redis;
// Redis + Buffering
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$cache = new Redis($redis);
$buffered = new BufferedStore($cache);
// Buffered operations
$buffered->set('key1', 'value1');
$buffered->set('key2', 'value2');
$buffered->delete('key3');
// Execute all operations at once
$buffered->commit();
Transaction Feature
<?php
use MatthiasMullie\Scrapbook\Adapters\MemoryStore;
$cache = new MemoryStore();
// Start transaction
$transaction = $cache->getCollection('transaction');
try {
$transaction->set('user:123', $userData);
$transaction->set('session:abc', $sessionData);
// Commit only if all operations succeed
$success = $transaction->commit();
if (!$success) {
throw new Exception('Transaction failed');
}
} catch (Exception $e) {
// Rollback on error
$transaction->rollback();
throw $e;
}
Stampede Protection
<?php
use MatthiasMullie\Scrapbook\Adapters\Redis;
class ExpensiveDataService {
private $cache;
public function __construct() {
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$this->cache = new Redis($redis);
}
public function getExpensiveData($key) {
// Try to get from cache
$data = $this->cache->get($key);
if ($data === false) {
// Get lock to prevent stampede
$lock = $this->cache->get($key . ':lock');
if ($lock === false) {
// Set lock (short duration)
$this->cache->set($key . ':lock', true, 30);
try {
// Perform expensive operation
$data = $this->performExpensiveOperation();
// Cache result (long duration)
$this->cache->set($key, $data, 3600);
} finally {
// Release lock
$this->cache->delete($key . ':lock');
}
} else {
// Wait if another process is working
sleep(1);
return $this->getExpensiveData($key);
}
}
return $data;
}
}