Logo

dev-resources.site

for different kinds of informations.

PSR-6: Caching Interface in PHP

Published at
1/11/2025
Categories
php
standards
phpfig
Author
jonesrussell
Categories
3 categories in total
php
open
standards
open
phpfig
open
Author
12 person written this
jonesrussell
open
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.

What Problem Does PSR-6 Solve? (2 minutes)

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 (5 minutes)

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

Real-World Implementation (10 minutes)

Here’s a practical example from our repository:

1. Cache Item Implementation

<?php

namespace JonesRussell\PhpFigGuide\PSR6;

use Psr\Cache\CacheItemInterface;
use DateTimeInterface;
use DateInterval;
use DateTime;

class CacheItem implements CacheItemInterface
{
    private $key;
    private $value;
    private $isHit;
    private $expiration;

    public function __construct(string $key)
    {
        $this->key = $key;
        $this->isHit = false;
    }

    public function getKey(): string
    {
        return $this->key;
    }

    public function get()
    {
        return $this->value;
    }

    public function isHit(): bool
    {
        return $this->isHit;
    }

    public function set($value): self
    {
        $this->value = $value;
        return $this;
    }

    public function expiresAt(?DateTimeInterface $expiration): self
    {
        $this->expiration = $expiration;
        return $this;
    }

    public function expiresAfter($time): self
    {
        if ($time instanceof DateInterval) {
            $this->expiration = (new DateTime())->add($time);
        } elseif (is_int($time)) {
            $this->expiration = (new DateTime())->add(new DateInterval("PT{$time}S"));
        } else {
            $this->expiration = null;
        }
        return $this;
    }

    // Helper method for our implementation
    public function getExpiration(): ?DateTimeInterface
    {
        return $this->expiration;
    }

    // Helper method for our implementation
    public function setIsHit(bool $hit): void
    {
        $this->isHit = $hit;
    }
}

Enter fullscreen mode Exit fullscreen mode

2. Cache Pool Implementation

<?php

namespace JonesRussell\PhpFigGuide\PSR6;

use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\CacheItemInterface;
use RuntimeException;

class FileCachePool implements CacheItemPoolInterface
{
    private $directory;
    private $deferred = [];

    public function __construct(string $directory)
    {
        if (!is_dir($directory) && !mkdir($directory, 0777, true)) {
            throw new RuntimeException("Cannot create cache directory: {$directory}");
        }
        $this->directory = $directory;
    }

    public function getItem($key): CacheItemInterface
    {
        $this->validateKey($key);

        if (isset($this->deferred[$key])) {
            return $this->deferred[$key];
        }

        $item = new CacheItem($key);
        $path = $this->getPath($key);

        if (file_exists($path)) {
            try {
                $data = unserialize(file_get_contents($path));
                if (!$data['expiration'] || $data['expiration'] > new DateTime()) {
                    $item->set($data['value']);
                    $item->setIsHit(true);
                    return $item;
                }
                unlink($path);
            } catch (\Exception $e) {
                // Log error and continue with cache miss
            }
        }

        return $item;
    }

    public function getItems(array $keys = []): iterable
    {
        $items = [];
        foreach ($keys as $key) {
            $items[$key] = $this->getItem($key);
        }
        return $items;
    }

    public function hasItem($key): bool
    {
        return $this->getItem($key)->isHit();
    }

    public function clear(): bool
    {
        $this->deferred = [];
        $files = glob($this->directory . '/*.cache');

        if ($files === false) {
            return false;
        }

        $success = true;
        foreach ($files as $file) {
            if (!unlink($file)) {
                $success = false;
            }
        }
        return $success;
    }

    public function deleteItem($key): bool
    {
        $this->validateKey($key);
        unset($this->deferred[$key]);

        $path = $this->getPath($key);
        if (file_exists($path)) {
            return unlink($path);
        }
        return true;
    }

    public function deleteItems(array $keys): bool
    {
        $success = true;
        foreach ($keys as $key) {
            if (!$this->deleteItem($key)) {
                $success = false;
            }
        }
        return $success;
    }

    public function save(CacheItemInterface $item): bool
    {
        $path = $this->getPath($item->getKey());
        $data = [
            'value' => $item->get(),
            'expiration' => $item->getExpiration()
        ];

        try {
            if (file_put_contents($path, serialize($data)) === false) {
                return false;
            }
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function saveDeferred(CacheItemInterface $item): bool
    {
        $this->deferred[$item->getKey()] = $item;
        return true;
    }

    public function commit(): bool
    {
        $success = true;
        foreach ($this->deferred as $item) {
            if (!$this->save($item)) {
                $success = false;
            }
        }
        $this->deferred = [];
        return $success;
    }

    private function getPath(string $key): string
    {
        return $this->directory . '/' . sha1($key) . '.cache';
    }

    private function validateKey(string $key): void
    {
        if (!is_string($key) || preg_match('#[{}()/@:\\\\]#', $key)) {
            throw new InvalidArgumentException(
                'Invalid key: ' . var_export($key, true)
            );
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Practical Usage (5 minutes)

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 (3 minutes)

  1. Key Validation

  2. Error Handling

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.

Resources

php Article's
30 articles in total
Favicon
The Importance of Writing Meaningful Code and Documentation
Favicon
Filling a 10 Million Image Grid with PHP for Internet History
Favicon
Code Smell 286 - Overlapping Methods
Favicon
Example of using Late Static Binding in PHP.
Favicon
How to Resolve the 'Permission Denied' Error in PHP File Handling
Favicon
2429. Minimize XOR
Favicon
The Ultimate PHP QR Code Library
Favicon
Understanding PHP Development and Why It’s Still Relevant Today
Favicon
2657. Find the Prefix Common Array of Two Arrays
Favicon
Php Base64 encode/decode – best practices and use cases
Favicon
Laravel 11.30: A Leap Forward in Testing, Model IDs, and Authorization
Favicon
How to Effectively Manage Laravel Request Validation?
Favicon
3223. Minimum Length of String After Operations
Favicon
Author Bio Box CSS in WordPress
Favicon
[Boost]
Favicon
How to Image Upload with CKeditor in Laravel 11 Tutorial
Favicon
How to Install and Use Trix Editor in Laravel 11
Favicon
Testing Temporary URLs in Laravel Storage
Favicon
2116. Check if a Parentheses String Can Be Valid
Favicon
API Vulnerabilities in Laravel: Identify & Secure Your Endpoints
Favicon
Enforcing Strong Passwords in Laravel
Favicon
"PHP is dead⚰️" .. what's next? Is Laravel worth it? 😎
Favicon
LTS as a Business: How an Old Project Can Become the Foundation for a New Business Model
Favicon
Php
Favicon
How to Fix the "PHP Not Found" Error on macOS After Installing XAMPP
Favicon
The Hidden Bug That Crashed a Satellite: Lessons for Every Developer 🚀
Favicon
Sending logs to Telegram. Module for Laravel
Favicon
Reflecting on 2024: From CodeIgniter to Laravel and Building Integrated Solutions
Favicon
Host Header Injection in Laravel: Risks and Prevention
Favicon
CodeIgniter Monitoring Library – Born from Understanding Real Developer Needs

Featured ones: