Scrapbook

Cache LibraryPHPPSR-6PSR-16Multi-Adapter

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.

Stars317
Watchers9
Forks27
Created:September 3, 2015
Language:PHP
License:MIT License

Topics

apccachecachingmemcachedphppsr-16psr-6redistransactions

Star History

matthiasmullie/scrapbook Star History
Data as of: 10/22/2025, 08:07 AM

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;
    }
}