0

I use the cache component of Symfony 3.1 to store some custom metadata for my entities. I would like to invalidate it as soon as a change is made in any file associated with these metadata.

I didn't find a way to tell Symfony or the cache component in particular to watch for changes in a specific set of files, I am missing something?

Below the code I use to create my cache item pool:

<?php
class MetadataCacheFactory implements MetadataCacheFactoryInterface
{
    const CACHE_NAMESPACE = 'my_namespace';

    /** @var string */
    protected $cacheDir;

    public function __construct(KernelInterface $kernel)
    {
        $this->cacheDir = $kernel->getCacheDir();
    }

    /**
     * {@inheritdoc}
     */
    public function create(): CacheItemPoolInterface
    {
        return AbstractAdapter::createSystemCache(self::CACHE_NAMESPACE, 0, null, $this->cacheDir);
    }
}

And the code using it:

<?php
class ExampleMetadataFactory implements MetadataFactoryInterface
{
    const CACHE_KEY = 'example_metadata';

    [...]

    /** @var ExampleMetadata */
    protected $metadata;

    public function __construct(MetadataCacheFactoryInterface $cacheFactory)
    {
        $this->cache = $cacheFactory->create();
        $this->metadata = null;
    }

    /**
     * {@inheritdoc}
     */
    public function create(): ExampleMetadata
    {
        if ($this->metadata !== null) {
            return $this->metadata;
        }
        try {
            $cacheItem = $this->cache->getItem(md5(self::CACHE_KEY));
            if ($cacheItem->isHit()) {
                return $cacheItem->get();
            }
        } catch (CacheException $e) {
            // Ignore
        }
        $this->metadata = $this->createMetadata();
        if (!isset($cacheItem)) {
            return $this->metadata;
        }
        $cacheItem->set($this->metadata);
        $this->cache->save($cacheItem);
        return $this->metadata;
    }
}

When I look at the code at runtime, the AbstractAdapter choose to give me a PhpFilesAdapter (fair enough in dev I guess).

But when I look into the code of this adapter I find this:

<?php
protected function doFetch(array $ids) {
    [...]

    foreach ($ids as $id) {
        try {
            $file = $this->getFile($id);
            list($expiresAt, $values[$id]) = include $file;
            if ($now >= $expiresAt) {
                unset($values[$id]);
            }
        } catch (\Exception $e) {
            continue;
        }
    }
}

So there is no logic at all to check for expiration except for the expiration date.

Do you know a way to invalidate the cache on file change (in dev environment of course) ? Or have I to implement it myself..?

Thanks for your help.

1 Answer 1

1

I've found a solution : ConfigCache. You can give it an array of resources to watch and it will check their last modification time each time the cache is read.

The example above becomes something like :

<?php
class MetadataCacheFactory implements MetadataCacheFactoryInterface
{
    const CACHE_NAMESPACE = 'my_namespace';

    /** @var \Symfony\Component\HttpKernel\KernelInterface */
    protected $kernel;

    public function __construct(KernelInterface $kernel)
    {
        $this->kernel = $kernel;
    }

    /**
     * {@inheritdoc}
     */
    public function create(): ConfigCache
    {
        return new ConfigCache($this->kernel->getCacheDir().'/'.self::CACHE_NAMESPACE, $this->kernel->isDebug());
    }
}

And the code using it:

<?php
class ExampleMetadataFactory implements MetadataFactoryInterface
{
    const CACHE_KEY = 'example_metadata';

    [...]

    /** @var ExampleMetadata */
    protected $metadata;

    public function __construct(MetadataCacheFactoryInterface $cacheFactory)
    {
        $this->cache = $cacheFactory->create();
        $this->metadata = null;
    }

    /**
     * {@inheritdoc}
     */
    public function create(): ExampleMetadata
    {
        if ($this->metadata !== null) {
            return $this->metadata;
        }
        try {
            if ($this->cache->isFresh()) {
                $this->metadata = unserialize(file_get_contents($this->cache->getPath()));
                return $this->metadata;
            }
        } catch (CacheException $e) {
            // Ignore
        }
        $resources = [];
        $this->metadata = $this->createMetadata($resources);
        $this->cache->write(serialize($this->metadata), $resources);
        return $this->metadata;
    }

    /**
     * Creates the metadata.
     *
     * @param array $resources
     *
     * @return ExampleMetadata
     */
    protected function createMetadata(&$resources): ExampleMetadata
    {
        $metadata = new ExampleMetadata();
        $resourcesCollection = $this->resourcesCollectionFactory->create();
        foreach ($resourcesCollection as $resourceClass => $resourcePath) {
            // The important part, it's an instance of \Symfony\Component\Config\Resource\FileResource.
            $resources[] = new FileResource($resourcePath);
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
            $metadata->registerResourceMetadata($resourceMetadata);
        }
        return $metadata;
    }
}

In the hope it can help somebody else.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.