DEV Community

Russell Jones
Russell Jones

Posted on • Originally published at jonesrussell.github.io on

PSR-6: Caching Interface in PHP

Ahnii!

Ever had your application slow to a crawl because of repeated database queries? Or struggled to switch between different caching libraries? Let's dive into PSR-6, the standard that makes caching in PHP predictable and swappable!

This post is part of our PSR Standards in PHP series. If you're new here, you might want to start with PSR-1 for the basics.

Prerequisites: PHP OOP (classes, interfaces). Recommended: Read PSR-4 first. See also: PSR-16 for simpler caching needs.

What Problem Does PSR-6 Solve?

Before PSR-6, every caching library had its own way of doing things. Want to switch from Memcached to Redis? Rewrite your code. Moving from one framework to another? Learn a new caching API. PSR-6 fixes this by providing a common interface that all caching libraries can implement.

Core Interfaces

Let's look at the two main players:

1. CacheItemPoolInterface

This is your cache manager. Think of it as a warehouse where you store and retrieve items:

<?php

namespace Psr\Cache;

interface CacheItemPoolInterface
{
    public function getItem($key);
    public function getItems(array $keys = array());
    public function hasItem($key);
    public function clear();
    public function deleteItem($key);
    public function deleteItems(array $keys);
    public function save(CacheItemInterface $item);
    public function saveDeferred(CacheItemInterface $item);
    public function commit();
}
Enter fullscreen mode Exit fullscreen mode

2. CacheItemInterface

This represents a single item in your cache:

<?php

namespace Psr\Cache;

interface CacheItemInterface
{
    public function getKey();
    public function get();
    public function isHit();
    public function set($value);
    public function expiresAt($expiration);
    public function expiresAfter($time);
}
Enter fullscreen mode Exit fullscreen mode

Practical Usage

Let's see how to use this in real code:

<?php

// Basic usage
$pool = new FileCachePool('/path/to/cache');

try {
    // Store a value
    $item = $pool->getItem('user.1');
    if (!$item->isHit()) {
        $userData = $database->fetchUser(1); // Your database call
        $item->set($userData)
             ->expiresAfter(3600); // 1 hour
        $pool->save($item);
    }
    $user = $item->get();
} catch (\Exception $e) {
    // Handle errors gracefully
    log_error('Cache operation failed: ' . $e->getMessage());
    $user = $database->fetchUser(1); // Fallback to database
}
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls

  1. Key Validation
   // Don't do this - using invalid characters
   $key = 'user@email.com';

   // Do this instead
   $key = 'user.' . md5('user@email.com');
Enter fullscreen mode Exit fullscreen mode
  1. Error Handling
   // Always handle cache failures gracefully
   try {
       $pool->save($item);
   } catch (CacheException $e) {
       // Log and continue with a cache miss
   }
Enter fullscreen mode Exit fullscreen mode
  1. Cache Stampede

When a popular cache key expires, every request hits the database simultaneously:

   // Bad - All requests hit the database when cache expires
   $item = $pool->getItem('popular-posts');
   if (!$item->isHit()) {
       $data = $database->getPopularPosts(); // Hundreds of requests run this at once
       $item->set($data)->expiresAfter(60);
       $pool->save($item);
   }

   // Good - Stagger expiration with random jitter
   $item = $pool->getItem('popular-posts');
   if (!$item->isHit()) {
       $data = $database->getPopularPosts();
       $jitter = random_int(0, 30);
       $item->set($data)->expiresAfter(60 + $jitter);
       $pool->save($item);
   }
Enter fullscreen mode Exit fullscreen mode

Framework Integration

Laravel

Laravel's cache system supports PSR-6 through a bridge package:

<?php

use Illuminate\Support\Facades\Cache;

// Laravel's cache can be accessed as a PSR-6 pool
$pool = app('cache.psr6');
$item = $pool->getItem('user.1');

if (!$item->isHit()) {
    $item->set($user);
    $item->expiresAfter(3600);
    $pool->save($item);
}
Enter fullscreen mode Exit fullscreen mode

Symfony

Symfony's Cache component is a native PSR-6 implementation — no bridge needed:

<?php

use Symfony\Component\Cache\Adapter\FilesystemAdapter;

// Symfony's adapters implement PSR-6 directly
$cache = new FilesystemAdapter();
$item = $cache->getItem('user.1');

if (!$item->isHit()) {
    $item->set($user);
    $item->expiresAfter(3600);
    $cache->save($item);
}
Enter fullscreen mode Exit fullscreen mode

What's Next?

Tomorrow, we'll look at PSR-7 (HTTP Message Interfaces). If you're interested in simpler caching, stay tuned for our upcoming PSR-16 (Simple Cache) article, which offers a more straightforward alternative to PSR-6.

Try It Yourself

Clone the companion repository and explore the caching examples:

git clone https://github.com/jonesrussell/php-fig-guide.git
cd php-fig-guide
composer install
composer test -- --filter=PSR6
Enter fullscreen mode Exit fullscreen mode

See src/Cache/ for the PSR-6 implementation used in the blog API.

Resources

Baamaapii

Top comments (0)