Skip to content

Symfony Cache adapter for Google Cloud Firestore - Production-ready PSR-6 implementation with tag support, compression, and automatic pruning

License

Notifications You must be signed in to change notification settings

vascopen/symfony-firestore-cache-adapter

Symfony Firestore Cache Adapter

Build Status Coverage Status PHPStan Level License Latest Stable Version

Production-ready Symfony Cache adapter for Google Cloud Firestore. Perfect for Cloud Run, App Engine, and other stateless GCP environments where filesystem cache is not persistent.

Features

  • PSR-6 compliant - Full implementation of Psr\Cache\CacheItemPoolInterface
  • Tag-aware caching - Invalidate groups of cache entries with tags
  • Automatic pruning - Clean up expired cache entries
  • Data compression - Optional gzip compression to reduce Firestore storage costs
  • Versioning support - Automatic cache invalidation on schema changes
  • Namespace isolation - Multi-tenant support with cache namespacing
  • High performance - Batch operations and optimized Firestore queries
  • Cloud Run optimized - Works seamlessly with ephemeral containers

Installation

composer require vasco-fund/symfony-firestore-cache-adapter

Requirements

  • PHP 8.2 or higher
  • Symfony 6.4 or 7.x
  • Google Cloud Firestore credentials

Configuration

1. Basic Configuration

# config/packages/cache.yaml
framework:
    cache:
        prefix_seed: your_app_name
        pools:
            app:
                adapter: cache.adapter.firestore

services:
    cache.adapter.firestore:
        class: Vasco\FirestoreCache\FirestoreAdapter
        arguments:
            $projectId: '%env(GOOGLE_CLOUD_PROJECT)%'
            $namespace: '%kernel.environment%'
            $defaultLifetime: 3600

2. Advanced Configuration with All Features

# config/packages/cache.yaml
services:
    cache.adapter.firestore:
        class: Vasco\FirestoreCache\FirestoreAdapter
        arguments:
            $projectId: '%env(GOOGLE_CLOUD_PROJECT)%'
            $namespace: '%kernel.environment%'
            $defaultLifetime: 3600
            $options:
                # Collection name in Firestore
                collection: 'symfony_cache'
                # Enable gzip compression
                compression: true
                # Cache version for automatic invalidation
                version: 1
                # Maximum items per batch operation
                max_batch_size: 500
                # Firestore credentials path (optional if using default credentials)
                keyFilePath: '%env(GOOGLE_APPLICATION_CREDENTIALS)%'

3. Tag-Aware Cache Pool

# config/packages/cache.yaml
framework:
    cache:
        pools:
            app.cache:
                adapter: cache.adapter.firestore
                tags: true

Usage Examples

Basic Cache Operations

use Psr\Cache\CacheItemPoolInterface;

class YourService
{
    public function __construct(
        private CacheItemPoolInterface $cache
    ) {}

    public function getData(string $id): array
    {
        $item = $this->cache->getItem('data_' . $id);

        if (!$item->isHit()) {
            $data = $this->fetchDataFromDatabase($id);
            $item->set($data);
            $item->expiresAfter(3600); // 1 hour
            $this->cache->save($item);
        }

        return $item->get();
    }
}

Using Tags

use Symfony\Contracts\Cache\TagAwareCacheInterface;

class ProductService
{
    public function __construct(
        private TagAwareCacheInterface $cache
    ) {}

    public function getProduct(int $id): Product
    {
        return $this->cache->get(
            'product_' . $id,
            function (ItemInterface $item) use ($id) {
                $item->tag(['products', 'product_' . $id]);
                $item->expiresAfter(86400); // 24 hours

                return $this->repository->find($id);
            }
        );
    }

    public function invalidateProduct(int $id): void
    {
        // Invalidate specific product
        $this->cache->invalidateTags(['product_' . $id]);
    }

    public function invalidateAllProducts(): void
    {
        // Invalidate all products
        $this->cache->invalidateTags(['products']);
    }
}

Manual Pruning

use Vasco\FirestoreCache\FirestoreAdapter;

class CachePruneCommand extends Command
{
    public function __construct(
        private FirestoreAdapter $cache
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Remove all expired cache entries
        $this->cache->prune();

        return Command::SUCCESS;
    }
}

Environment Variables

# Required
GOOGLE_CLOUD_PROJECT=your-project-id

# Optional - if not using default GCP credentials
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json

Cloud Run Deployment

1. Using Workload Identity (Recommended)

# cloudrun.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: your-app
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/service-account: your-sa@project.iam.gserviceaccount.com
    spec:
      containers:
      - image: gcr.io/project/image
        env:
        - name: GOOGLE_CLOUD_PROJECT
          value: "your-project-id"

2. Grant Firestore Permissions

# Grant Firestore User role to Cloud Run service account
gcloud projects add-iam-policy-binding PROJECT_ID \
    --member="serviceAccount:SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/datastore.user"

Performance Considerations

Storage Costs

  • Firestore charges per document read/write operation
  • Enable compression to reduce storage size (typically 60-80% reduction)
  • Use appropriate TTL values to minimize storage
  • Consider namespacing for cache isolation

Latency

  • Firestore adds network latency (~10-50ms) compared to filesystem
  • Much faster than filesystem on Cloud Run (no persistent disk)
  • Use batch operations when possible (implemented internally)
  • Consider longer TTL for frequently accessed data

Best Practices

  1. Enable compression for large cache values
  2. Use tags sparingly - Each tag creates an additional document
  3. Set appropriate TTL - Don't cache forever
  4. Monitor Firestore usage in GCP Console
  5. Use namespaces for multi-environment setups

Testing

Run Tests

composer test

With Coverage

composer test:coverage

Static Analysis

composer phpstan

Code Style

composer cs:check
composer cs:fix

Full Quality Assurance

composer qa

Firestore Structure

Cache Items Collection

symfony_cache (collection)
├── {namespace}:{key} (document)
│   ├── value: string (compressed or raw)
│   ├── expiresAt: timestamp
│   ├── version: int
│   └── compressed: bool

Tags Collection

symfony_cache_tags (collection)
├── {namespace}:{tag} (document)
│   └── items: array<string> (cache keys)

Contributing

Please see CONTRIBUTING.md for details on how to contribute to this project.

Security

If you discover any security-related issues, please email remi@vasco.fund instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Roadmap

  • Support for Firestore in Datastore mode
  • Metrics and monitoring integration
  • Async operations support
  • Redis-like atomic operations
  • Multi-region replication support

Alternatives

  • Memcached/Redis on GCP - Better performance but requires managed service
  • Symfony's ChainAdapter - Combine Firestore with APCu for local cache
  • Google Cloud Storage - Cheaper but slower than Firestore

FAQ

Q: Why Firestore instead of Redis/Memcached? A: Firestore is serverless, scales automatically, and works perfectly with Cloud Run's ephemeral containers. No need to manage a separate cache cluster.

Q: What about costs? A: Firestore pricing is per operation. With compression and proper TTL, costs are typically $5-20/month for small to medium applications.

Q: Can I use this in production? A: Yes! This adapter is production-ready with comprehensive tests, strict type checking (PHPStan level 9), and follows Symfony best practices.

Q: Does it work with Symfony 6? A: Yes, it supports Symfony 6.4 LTS and Symfony 7.x.

About

Symfony Cache adapter for Google Cloud Firestore - Production-ready PSR-6 implementation with tag support, compression, and automatic pruning

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages