Code Monkey home page Code Monkey logo

scrapbook's Introduction

Scrapbook PHP cache

Build status Code coverage Latest version Downloads total License

Documentation: https://www.scrapbook.cash - API reference: https://docs.scrapbook.cash

Table of contents

Installation & usage

Simply add a dependency on matthiasmullie/scrapbook to your composer.json file if you use Composer to manage the dependencies of your project:

composer require matthiasmullie/scrapbook

The exact bootstrapping will depend on which adapter, features and interface you will want to use, all of which are detailed below.

This library is built in layers that are all KeyValueStore implementations that you can wrap inside one another if you want to add more features.

Here's a simple example: a Memcached-backed psr/cache with stampede protection.

// create \Memcached object pointing to your Memcached server
$client = new \Memcached();
$client->addServer('localhost', 11211);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

// create stampede protector layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Scale\StampedeProtector($cache);

// create Pool (psr/cache) object from cache engine
$pool = new \MatthiasMullie\Scrapbook\Psr6\Pool($cache);

// get item from Pool
$item = $pool->getItem('key');

// get item value
$value = $item->get();

// ... or change the value & store it to cache
$item->set('updated-value');
$pool->save($item);

Just take a look at this "build your cache" section to generate the exact configuration you'd like to use (adapter, interface, features) and some example code.

Adapters

Memcached

Memcached is an in-memory key-value store for small chunks of arbitrary data (strings, objects) from results of database calls, API calls, or page rendering.

The PECL Memcached extension is used to interface with the Memcached server. Just provide a valid \Memcached object to the Memcached adapter:

// create \Memcached object pointing to your Memcached server
$client = new \Memcached();
$client->addServer('localhost', 11211);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

Redis

Redis is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs.

The PECL Redis extension is used to interface with the Redis server. Just provide a valid \Redis object to the Redis adapter:

// create \Redis object pointing to your Redis server
$client = new \Redis();
$client->connect('127.0.0.1');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

Couchbase

Engineered to meet the elastic scalability, consistent high performance, always-on availability, and data mobility requirements of mission critical applications.

The PECL Couchbase extension and couchbase/couchbase package are used to interface with the Couchbase server. Just provide valid \Couchbase\Collection, \Couchbase\Management\BucketManager and \Couchbase\Bucket objects to the Couchbase adapter:

// create \Couchbase\Bucket object pointing to your Couchbase server
$options = new \Couchbase\ClusterOptions();
$options->credentials('username', 'password');
$cluster = new \Couchbase\Cluster('couchbase://localhost', $options);
$bucket = $cluster->bucket('default');
$collection = $bucket->defaultCollection();
$bucketManager = $cluster->buckets();
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Couchbase($collection, $bucketManager, $bucket);

APCu

APC is a free and open opcode cache for PHP. Its goal is to provide a free, open, and robust framework for caching and optimizing PHP intermediate code.

With APC, there is no "cache server", the data is just cached on the executing machine and available to all PHP processes on that machine. The PECL APCu extension can be used.

// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Apc();

MySQL

MySQL is the world's most popular open source database. MySQL can cost-effectively help you deliver high performance, scalable database applications.

While a database is not a genuine cache, it can also serve as key-value store. Just don't expect the same kind of performance you'd expect from a dedicated cache server.

But there could be a good reason to use a database-based cache: it's convenient if you already use a database, and it may have other benefits (like persistent storage, replication.)

// create \PDO object pointing to your MySQL server
$client = new PDO('mysql:dbname=cache;host=127.0.0.1', 'root', '');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\MySQL($client);

PostgreSQL

PostgreSQL has a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness.

While a database is not a genuine cache, it can also serve as key-value store. Just don't expect the same kind of performance you'd expect from a dedicated cache server.

But there could be a good reason to use a database-based cache: it's convenient if you already use a database, and it may have other benefits (like persistent storage, replication.)

// create \PDO object pointing to your PostgreSQL server
$client = new PDO('pgsql:user=postgres dbname=cache password=');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\PostgreSQL($client);

SQLite

SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.

While a database is not a genuine cache, it can also serve as key-value store. Just don't expect the same kind of performance you'd expect from a dedicated cache server.

// create \PDO object pointing to your SQLite server
$client = new PDO('sqlite:cache.db');
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\SQLite($client);

Filesystem

While it's not the fastest kind of cache in terms of I/O access times, opening a file usually still beats redoing expensive computations.

The filesystem-based adapter uses league\flysystem to abstract away the file operations, and will work with all kinds of storage that league\filesystem provides.

// create Flysystem object
$adapter = new \League\Flysystem\Local\LocalFilesystemAdapter('/path/to/cache', null, LOCK_EX);
$filesystem = new \League\Flysystem\Filesystem($adapter);
// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\Flysystem($filesystem);

Memory

PHP can keep data in memory, too! PHP arrays as storage are particularly useful to run tests against, since you don't have to install any other service.

Stuffing values in memory is mostly useless: they'll drop out at the end of the request. Unless you're using these cached values more than once in the same request, cache will always be empty and there's little reason to use this cache.

However, it is extremely useful when unittesting: you can run your entire test suite on this memory-based store, instead of setting up cache services and making sure they're in a pristine state...

// create Scrapbook KeyValueStore object
$cache = new \MatthiasMullie\Scrapbook\Adapters\MemoryStore();

Features

In addition to the default cache functionality (like get & set), Scrapbook comes with a few neat little features. These are all implemented in their own little object that implements KeyValueStore, and wraps around a KeyValueStore. In other words, any feature can just be wrapped inside another one or on top of any adapter.

Local buffer

BufferedStore helps reduce requests to your real cache. If you need to request the same value more than once (from various places in your code), it can be a pain to keep that value around. Requesting it again from cache would be easier, but then you get some latency from the connection to the cache server.

BufferedStore will keep known values (items that you've already requested or written yourself) in memory. Every time you need that value in the same request, it'll just get it from memory instead of going out to the cache server.

Just wrap the BufferedStore layer around your adapter (or other features):

// create buffered cache layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Buffered\BufferedStore($cache);

Transactions

TransactionalStore makes it possible to defer writes to a later point in time. Similar to transactions in databases, all deferred writes can be rolled back or committed all at once to ensure the data that is stored is reliable and complete. All of it will be stored, or nothing at all.

You may want to process code throughout your codebase, but not commit any changes until everything has successfully been validated & written to permanent storage.

While inside a transaction, you don't have to worry about data consistency. Inside a transaction, even if it has not yet been committed, you'll always be served the value you intend to store. In other words, when you write a new value to cache but have not yet committed it, you'll still get that value when you query for it. Should you rollback, or fail to commit (because data stored by another process caused your commit to fail), then you'll get the original value from cache instead of the one your intended to commit.

Just wrap the TransactionalStore layer around your adapter (or other features):

// create transactional cache layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Buffered\TransactionalStore($cache);

And TA-DA, you can use transactions!

// begin a transaction
$cache->begin();

// set a value
// it won't be stored in real cache until commit() is called
$cache->set('key', 'value'); // returns true

// get a value
// it won't get it from the real cache (where it is not yet set), it'll be read
// from PHP memory
$cache->get('key'); // returns 'value'

// now commit write operations, this will effectively propagate the update to
// 'key' to the real cache
$cache->commit();

// ... or rollback, to discard uncommitted changes!
$cache->rollback();

Stampede protection

A cache stampede happens when there are a lot of requests for data that is not currently in cache, causing a lot of concurrent complex operations. For example:

  • cache expires for something that is often under very heavy load
  • sudden unexpected high load on something that is likely to not be in cache

In those cases, this huge amount of requests for data that is not in cache at that time causes that expensive operation to be executed a lot of times, all at once.

StampedeProtector is designed counteract that. If a value can't be found in cache, a placeholder will be stored to indicate it was requested but didn't exist. Every follow-up request for a short period of time will find that indication and know another process is already generating that result, so those will just wait until it becomes available, instead of crippling the servers.

Just wrap the StampedeProtector layer around your adapter (or other features):

// create stampede protector layer over our real cache
$cache = new \MatthiasMullie\Scrapbook\Scale\StampedeProtector($cache);

Sharding

When you have too much data for (or requests to) 1 little server, this'll let you shard it over multiple cache servers. All data will automatically be distributed evenly across your server pool, so all the individual cache servers only get a fraction of the data & traffic.

Pass the individual KeyValueStore objects that compose the cache server pool into this constructor & the data will be sharded over them according to the order the cache servers were passed into this constructor (so make sure to always keep the order the same.)

The sharding is spread evenly and all cache servers will roughly receive the same amount of cache keys. If some servers are bigger than others, you can offset this by adding that cache server's KeyValueStore object more than once.

Data can even be sharded among different adapters: one server in the shard pool can be Redis while another can be Memcached. Not sure why you would want to do that, but you could!

Just wrap the Shard layer around your adapter (or other features):

// boilerplate code example with Redis, but any
// MatthiasMullie\Scrapbook\KeyValueStore adapter will work
$client = new \Redis();
$client->connect('192.168.1.100');
$cache1 = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

// a second Redis server...
$client2 = new \Redis();
$client2->connect('192.168.1.101');
$cache2 = new \MatthiasMullie\Scrapbook\Adapters\Redis($client);

// create shard layer over our real caches
// now $cache will automatically distribute the data across both servers
$cache = new \MatthiasMullie\Scrapbook\Scale\Shard($cache1, $cache2);

Interfaces

Scrapbook supports 3 different interfaces. There's the Scrapbook-specific KeyValueStore, and then there are 2 PSR interfaces put forward by the PHP FIG.

KeyValueStore

KeyValueStore is the cornerstone of this project. It is the interface that provides the most cache operations: get, getMulti, set, setMulti, delete, deleteMulti, add, replace, cas, increment, decrement, touch & flush.

If you've ever used Memcached before, KeyValueStore will look very similar, since it's inspired by/modeled after that API.

All adapters & features implement this interface. If you have complex cache needs (like being able to cas), this is the one to stick to.

A detailed list of the KeyValueStore interface & its methods can be found in the documentation.

psr/cache

PSR-6 (a PHP-FIG standard) is a drastically different cache model than KeyValueStore & psr/simple-cache: instead of directly querying values from the cache, psr/cache basically operates on value objects (Item) to perform the changes, which then feed back to the cache (Pool.)

It doesn't let you do too many operations. If get, set, delete (and their *multi counterparts) and delete is all you need, you're probably better off using this (or psr/simple-cache, see below) as this interface is also supported by other cache libraries.

You can easily use psr/cache by wrapping it around any KeyValueStore object:

// create Pool object from Scrapbook KeyValueStore object
$pool = new \MatthiasMullie\Scrapbook\Psr6\Pool($cache);

// get item from Pool
$item = $pool->getItem('key');

// get item value
$value = $item->get();

// ... or change the value & store it to cache
$item->set('updated-value');
$pool->save($item);

A detailed list of the PSR-6 interface & its methods can be found in the documentation.

psr/simple-cache

PSR-16 (a PHP-FIG standard) is a second PHP-FIG cache standard. It's a driver model just like KeyValueStore, and it works very much in the same way.

It doesn't let you do too many operations. If get, set, delete (and their *multi counterparts) and delete is all you need, you're probably better off using this (or psr/cache, see above) as this interface is also supported by other cache libraries.

You can easily use psr/simple-cache by wrapping it around any KeyValueStore object:

// create Simplecache object from Scrapbook KeyValueStore object
$simplecache = new \MatthiasMullie\Scrapbook\Psr16\SimpleCache($cache);

// get value from cache
$value = $simplecache->get('key');

// ... or store a new value to cache
$simplecache->set('key', 'updated-value');

A detailed list of the PSR-16 interface & its methods can be found in the documentation.

Collections

Collections, or namespaces if you wish, are isolated cache subsets that will only ever provide access to the values within that context.

It is not possible to set/fetch data across collections. Setting the same key in 2 different collections will store 2 different values that can only be retrieved from their respective collection.

Flushing a collection will only flush those specific keys and will leave keys in other collections untouched.

Flushing the server, however, will wipe out everything, including data in any of the collections on that server.

Here's a simple example:

// let's create a Memcached cache object
$client = new \Memcached();
$client->addServer('localhost', 11211);
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

$articleCache = $cache->collection('articles');
$sessionCache = $cache->collection('sessions');

// all of these are different keys
$cache->set('key', 'value one');
$articleCache->set('key', 'value two');
$sessionCache->set('key', 'value three');

// this clears our the entire 'articles' subset (thus removing 'value two'),
// while leaving everything else untouched
$articleCache->flush();

// this removes everything from the server, including all of its subsets
// ('value one' and 'value three' will also be deleted)
$cache->flush();

getCollection is available on all KeyValueStore implementations (so for every adapter & feature that may wrap around it) and also returns a KeyValueStore object. While it is not part of the PSR interfaces, you can create your collections first and then wrap all of your collections inside their own psr/cache or psr/simple-cache representations, like so:

$articleCache = $cache->collection('articles');
$sessionCache = $cache->collection('sessions');

// create Pool objects from both KeyValueStore collections
$articlePool = new \MatthiasMullie\Scrapbook\Psr6\Pool($articleCache);
$sessionPool = new \MatthiasMullie\Scrapbook\Psr6\Pool($sessionCache);

Compatibility

Scrapbook supports PHP versions 8.0 up to the current version. Differences in implementation of the cache backends across these versions & platforms will be mitigated within Scrapbook to ensure uniform behavior.

Compatibility with all of these versions & platforms can be confirmed with the provided docker-compose config for PHP versions 8.0 and onwards.

Compatibility with old software versions will not be broken easily. Not unless there is a compelling reason to do so, like security or performance implications, or compatibility with other software. Syntactic sugar alone is not a reason to break compatibility.

License

Scrapbook is MIT licensed.

scrapbook's People

Contributors

amne avatar curry684 avatar hansott avatar klermonte avatar martin-georgiev avatar matthiasmullie avatar mindplay-dk avatar nyholm avatar pascal-hofmann avatar scrutinizer-auto-fixer avatar seldaek avatar sgiehl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scrapbook's Issues

PHP 7.4 error

During a test with PHP 7.4 I got this error:

1) DeviceDetector\Tests\Cache\PSR16CacheTest::testSetNotPresent
implode(): Passing glue string after array is deprecated. Swap the parameters
/home/travis/build/matomo-org/device-detector/vendor/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php:384
/home/travis/build/matomo-org/device-detector/vendor/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php:47
/home/travis/build/matomo-org/device-detector/Tests/Cache/PSR16CacheTest.php:23

getClient

The client property is protected in the Redis adapter, at least. It'd be nice to be able to extend the SimpleCache (PSR-16) and still be able to get the actual client.

Cache Statistics

Would it be possible to add a method to provide cache stats such as hits, misses and memory used?

Bottleneck in Couchbase Adapter

We have uncovered issue with logic that is assessing cluster health. This check is performed using regular http call which can lead to significant performance issues (higher response time). We didn't identified the root cause exactly but it can be caused by reaching of some limit (TCP connections).

$this->assertServerHealhy();

The graph below shows the impact on response time before and after the method assertServerHealhy was commented out.
response time

Transaction savepoint

Add please "transaction savepoint" for the ability to implement nested transactions
Or just implement nested transactions

Buffered cache can fill op memory

Actually, so can Memory (adapter) & Transactional cache, but there it's ok.

Buffered cache, however, should be a FIFO. When memory is filling up, it should just dispose of the least recently requested values.
Make sure it doesn't affect transactional cache, though - can't discard values that have yet to be written to cache.

MySql max size of data

Hi,
I'm trying to store data in mysql, but unfortunately I can not store everything (about 250kB) - to db saves only 64kB. What may be the problem? In sqlite it works fine.
Thanks

SimpleCache TTL

https://github.com/matthiasmullie/scrapbook/blob/master/src/Psr16/SimpleCache.php#L243-L252

/*
 * PSR-16 only accepts relative timestamps, whereas KeyValueStore
 * accepts both relative & absolute, depending on what the timestamp
 * is. We'll have to convert all ttls here to absolute, to make sure
 * KeyValueStore doesn't get confused.
 * @see https://github.com/dragoonis/psr-simplecache/issues/3
 */
if ($ttl < 30 * 24 * 60 * 60) {
    return $ttl + time();
}

The logic is backwards
If I pass a TTL of 60 days 60 * 24 * 60 * 60... the above code will NOT add time()... it will then get passed to KeyValueStore unmodified. KeyValueStore will interpret it as an absolute timestamp (1970-03-02 00:00:00)... which is expired

Per the block comment: "PSR-16 only accepts relative timestamps... We'll have to convert all ttls here to absolute,"... the code then proceeds to only convert some TTLs.

Since a relative TTL is always passed to SimpleCache, the 30-days condition isn't necessary... time() should always be added to absolutify (unless $ttl === 0... which is already being checked for)

Should simply be:

// if ($ttl < 30 * 24 * 60 * 60) {
    return $ttl + time(); // always convert relative TTL to absolute
// }

MemoryStore size/limit with collections

MemoryStore limit enforcement goes out the window when collections are created

$adapter = new MemoryStore(1000);
$collectionFoo = $adapter->getCollection('foo');
$collectionBar = $adapter->getCollection('bar');

// $adapter, $collectionFoo, & $collectionBar all have independent limits of 1000
// we requested a limit of 1000... but we can go 3 times over
  • Spawned collections get independent limit vs shared limit
  • "Parent" adapter doesn't track the size of spawned collections. Spawned collections are oblivious of parent and each other.
  • "Least Recently Used" ignores collections
    • items in child collections should be fair game when evicting
    • items in ancestor collections should probably not be fair game

Stampede protection with "stale-while-revalidate" strategy?

I'm fetching data from an external API, and these requests can take 3-5 seconds to complete.

The information I'm getting from this API is secondary content for the page - so it's okay to cache this probably for 5 minutes before pulling updated data from the API.

The stampede protection strategy will take care of the problem of making too many requests to this external API.

Being cached for 5 minutes, it's also not crucial to have the latest information within a 5-minute window, and so I'm looking for a means of implementing a "stale while revalidate" pattern.

That is, I don't want to block the rendering of a page for any user while fetching an updated version of the cached content - if the content has expired, I'd like to trigger an update in the background (e.g. via register_shutdown_handler()) and just return what we have.

Would it make sense to expand the stampede protection decorator with a feature like that?

Or is it best left to a separate decorator?

Is this feature useful/common enough that you might want it in your library?

PSR-6 incompatibility

hi @matthiasmullie ,

thank you for this great package. i was able to use this to test the feasibility of switching Memcache with Couchbase in my application.

in an effort to slowly roll out this change to sets of users, i integrated cache/chain-adapter to dual-store some data in both pools for safe roll-out and roll-back.

unfortunately it appears that this package enforces the use of \MatthiasMullie\Scrapbook\Psr6\Item instead of \Psr\Cache\CacheItemInterface in \MatthiasMullie\Scrapbook\Psr6\Pool::save and \MatthiasMullie\Scrapbook\Psr6\Pool::saveDeferred, which means this solution is not truly interoperable with other PSR-compatible cache solutions, at least not in a Chain.

is there any plan to depend exclusively on the PSR interface(s) instead of concretes?

Compression

Is there an easy way to implement data compression before storing?

I am thinking I can create new methods that would compress before and decompress after. But, I am looking for a simpler method.

Thanks

tests cache/integration-tests dependency

This may fall under "question", and if so, I apologize...

tests/Docker/build-php.sh
calls
composer require league/flysystem

when/how does
composer require --dev cache/integration-tests:dev-master
get called (via travis)?

make test

There were 2 incomplete tests:

  1. MatthiasMullie\Scrapbook\Tests\Psr6\Integration\IntegrationPoolTest::testIncomplete
    Missing dependencies. Please run: composer require --dev cache/integration-tests:dev-master

/var/www/tests/bootstrap.php:42

  1. MatthiasMullie\Scrapbook\Tests\Psr16\Integration\IntegrationTest::testIncomplete
    Missing dependencies. Please run: composer require --dev cache/integration-tests:dev-master

/var/www/tests/bootstrap.php:57

Stampede not working for phpredis > 4.0.0

Hi!

There seems to be an issue with the StampedeProtector since phpredis 4.0.0.
The exists method now returns an int instead of TRUE/FALSE: https://github.com/phpredis/phpredis#exists

image

Another issue I found is with the TTL of the stampede protect. We set it in milliseconds, in the constructor, but when we do: $success[$key] = $this->cache->add($this->stampedeKey($key), '', $this->sla); (in StampedeProtector@protect) is set in seconds.

I will open a pull request to fix/discuss these issues.

Thanks!

(Request) Setting a default TTL value

It would be a really nice feature if you could override the standard TTL value for any adapter.
Because my TTLs are static across my whole application it's not very comfortable to always provide them in the method calls, but leaving them out results in an infinite TTL due to implementation.

Redis wrong default value

I'm using Redis adapter with SimpleCache implementation and I guess there is a bug here.

$testStore = new SimpleCache(new Redis($client)); var_dump($testStore->get('missedKey'));

For the key missed I'm expecting the $default value defined by this method.

`
public function get($key, $default = null)
{
$this->assertValidKey($key);

    // KeyValueStore::get returns false for cache misses (which could also
    // be confused for a `false` value), so we'll check existence with getMulti
    $multi = $this->store->getMulti(array($key));

    return isset($multi[$key]) ? $multi[$key] : $default;
}

`

The result for the key missed is instead the boolean "false" when the default is "null".

undefined method: collection

Hi!
I am trying to run a simple example and I get the error:

Fatal error: Uncaught Error: Call to undefined method MatthiasMullie\Scrapbook\Adapters\Memcached::collection() in test.php:8 Stack trace: #0 {main} thrown in test.php on line 8

Apache 2.4, PHP 7.2, Memcached server 1.5,
PHPINFO Memcached section:
Version | 3.1.4
libmemcached version | 1.0.18

<?php
require_once 'vendor/autoload.php';

$client = new \Memcached();
$client->addServer('localhost', 11211);
$cache = new \MatthiasMullie\Scrapbook\Adapters\Memcached($client);

$articleCache = $cache->collection('articles');
$sessionCache = $cache->collection('sessions');

$cache->set('key', 'value one');
$articleCache->set('key', 'value two');
$sessionCache->set('key', 'value three');

$articleCache->flush();

$cache->flush();

Maybe I was wrong about something?

psr6 problems

just installed the package, used the default memory store and wrapped it with a psr6 implementation, the tried this:

$pool = $app->get(\Psr\Cache\CacheItemPoolInterface::class);//aliased to \MatthiasMullie\Scrapbook\Psr6\Pool(\MatthiasMullie\Scrapbook\Adapters\MemoryStore())

        $item = $pool->getItem('widget_list'); // this is all thats needed to cause the error
        if (!$item->isHit()) {
            $value = ['array', 'of', 'values'];
            $item->set($value);
            $pool->save($item);
        }
        return $item->get();

this is what i got back:

Fatal Error: Class MatthiasMullie\Scrapbook\Psr6\Item contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Psr\Cache\CacheItemInterface::exists, Psr\Cache\CacheItemInterface::setExpiration) in ...

[Redis] TTL gets set while add() fails.

Hi!

I ran into an issue this morning with mutex keys. It might be that its a product of our code and the way it works with Redis, but I found some code in this library unlogical from my point of view :)

I'm talking about src/Adapters/Redis.php:add(), which ends with this:

$this->client->multi();
$this->client->setnx($key, $value);
$this->client->expire($key, $ttl);

/** @var bool[] $return */
$return = (array) $this->client->exec();

return !in_array(false, $return, true);

My problem here is that both command (setnx and expire) get executed. The first command fails when the key already exists, the second command succeeds anyway. This results in $return being [false, true]. This in its turn makes the return false because there is a false in the array.

In my code I get a failure adding the key and make the assumption that the key already exists and the program will try again a bit later. But as the TTL gets updated on the failing add() call it will be there forever while scripts check the key periodically.

The solution I found is largely the one you mentioned in the comments above the code. Except for the use of xx, which means 'Only set the key if it already exists.' while it should be nx 'Only set the key if it does not already exist.'. The nx is also in accordance with the use of setnx() as is the current situation. This results in this simple line:

return $this->client->set($key, $value, ['ex' => $ttl, 'nx']);

Again, it might be that my use case is slightly incompatible with your library... but I find it strange that a function that fails still mutates silently.

I also tried to use set() but the 3rd parameter does not accept arrays.

Thanks

Incompatibility with Memcached 3

I am checking if a non-existing key is set in Memcached. When I use SimpleCache::has I get the following error:

Warning: Memcached::getMulti() expects parameter 2 to be integer, array given in .../matthiasmullie/scrapbook/src/Adapters/Memcached.php on line 72

In Memcached v3 (the only version properly supported for PHP 7) the signature of getMulti() changed and now expects 2 arguments instead of the 3 before.
This means that any usage of getMulti() with the Memcached adapter will result in error.
This should be handled in a graceful manner, so both v2 and v3 of the Memcached API are supported.
The code causing headaches is on this line in Adapters\Memcached.

Scrapbook version: 1.4
PHP version: 7.1
Memcached version: 3.0

Getting warning while get from APC

Warning became to appear on PHP 7.2

src/Adapters/Apc.php:86

$values = $this->apcu_fetch($keys);

$tokens = array();
foreach ($values as $key => $value) {
       $tokens[$key] = serialize($value);
}

Warning on foreach line: Invalid argument supplied for foreach()

$values at this point equals false. $keys is array with one string.

Scrapbook v 1.4.5
PHP 7.2

Update an existing value in cache causes the expire time (TTL) to be reset

Hi,

When setting a new value in cache with specific expire time (TTL) then trying to update the existing value using one of the methods ( Increment, Decrement, REPLACE ) the expire time (TTL) will get overridden and set to 0 ( which is infinite ) and this is so problematic as in some cases I need to update existing cache values without edit or touch their expire time.

Until now I verified this problem on three adapters ( Flysystem, SQLite, APC )

Code Example:
$cache->set( 'test-issue', 5, 3600 );
$cache->increment( 'test'issue' ); OR $cache->decrement( 'test-issue' );

Expected Result:
The 'test-issue' value only should be updated without any change to expire time

Actual Result:
The 'test-issue' value updated and expire time changed to 0 ( which makes the value endless )

Thanks.

Minor issue in SimpleCache::ttl() comment

Sorry, I didn't notice this when with my prev SimpleCache::ttl() issue

https://github.com/matthiasmullie/scrapbook/blob/master/src/Psr16/SimpleCache.php#L235-L237

/*
 * PSR-16 specifies that if `0` is provided, it must be treated as
 * expired, whereas KeyValueStore will interpret 0 to mean "expires
 * this second, but not at this exact time" (could vary a few ms).
 */

KeyValueStore interprets 0 as no-expiration, not expire-now

Also... PSR-16 doesn't specify, but I'm wondering if it'd make sense to throw InvalidArgumentException rather than \TypeError for invalid TTL ?

Ie, the specification only documents

/**
 * @throws \Psr\SimpleCache\InvalidArgumentException
 */

nothing about potentially throwing some other exception.

Fatal error: Class MatthiasMullie\Scrapbook\Psr6\Taggable\Pool contains 4 abstract methods

Hello @matthiasmullie.

I'm using version 1.1.0 of scrapbook.
When I'm trying to use the taggable pool, I'm getting a PHP Fatal, saying that the Pool class contains 4 abstract methods, as following:

PHP Fatal error:  Class MatthiasMullie\Scrapbook\Psr6\Taggable\Pool contains 4 abstract methods and must therefore be declared abstract or implement the remaining methods (MatthiasMullie\Scrapbook\Psr6\Taggable\Pool::getList, MatthiasMullie\Scrapbook\Psr6\Taggable\Pool::removeList, MatthiasMullie\Scrapbook\Psr6\Taggable\Pool::appendListItem, ...) in /project/vendor/matthiasmullie/scrapbook/src/Psr6/Taggable/Pool.php on line 209

What I'm trying to achieve is to simply test the Taggable pool implementation under PSR-6 test suite from PhpCache (https://github.com/php-cache/integration-tests).

Seems that those 4 missing methods are coming from Cache\Taggable\TaggablePoolTrait and your Pool implementation is missing them.

psr/simple-cache dependency

psr/simple-cache is now available on Packagist as 0.1.0 - you can now remove the built-in copy of the interfaces, extra autoload config, etc.

If you'd like me to PR this change, let me know? I have some free time tomorrow :-)

Trouble testing couchbase

make test PHP=5.6 ADAPTER=Couchbase
W: GPG error: http://packages.couchbase.com/ubuntu trusty InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY A3FAA648D9223EDA
W: The repository 'http://packages.couchbase.com/ubuntu trusty InRelease' is not signed.
The following packages have unmet dependencies:
 libcouchbase2-libevent : Depends: libcouchbase2-core (= 2.9.3-1) but it is not going to be installed
E: Unable to correct problems, you have held broken packages
There were 637 errors:

1) MatthiasMullie\Scrapbook\Tests\AdapterTest::testGetAndSet
MatthiasMullie\Scrapbook\Exception\Exception: ext-couchbase is not installed.

thoughts?
am I doing something wrong?
have dependencies changed?

Issue with Couchbase Adapter incompatibility (PHP 5.3)

Hello, we have uncovered an issue while implementing package into our code, which is run on legacy PHP 5.3 (which is suppose to be supported based on documentation).
The issue is that PHP 5.3 is not able to process 'new' type of syntax for arrays $var = [];
This issue is located in Adapters/Couchbase.php in method setMulti, but might also be located in other adapters.

Is there a possibility to ask for fix?

PHP 5.3
Scrapbook 1.4.6

Thank you.

Kind regards,
D.K.

League Filesystem 2.0

League/Filesystem is on 2.0-RC and the 2.0 version breaks the latest version of Scrapbook. Are there plans to update the integration in the near future to support 2.0 or will this library stick with the 1.0 version for the foreseeable future?

Collections\Utils\PrefixReset::flush : outta-sight-outta-mind

Couchbase & Memcached collections extend PrefixReset...

flush() is implemented via " just start a new collection" methodology...
This serves to invalidate all keys from the perspective of the collection... it doesn't actually do any trash collection.. the collection could be full of keys set with no expiry (a memory/resource leak)

One use-case for performing a flush may be to free said resources immediatelyrather than rely on the server's LRU mechanism...

PSR-16 defines the clear method as

Wipes clean the entire cache's keys

Which doesn't explicitly state that the resources are freed

Perhaps not necessarily a bug, but something that needs to be documented?

Invalid key: test:string. Contains (a) character(s) reserved for future extension:

$backend = new \Redis();
$backend->connect('localhost');
$backend->select(1);

$cache = new \MatthiasMullie\Scrapbook\Adapters\Redis( $backend );
$pool = new \MatthiasMullie\Scrapbook\Psr6\Pool($cache);

$item = $pool->getItem('test:string');

var_dump($item->get());

return:

PHP Fatal error:  Uncaught MatthiasMullie\Scrapbook\Psr6\InvalidArgumentException: Invalid key: test:string. Contains (a) character(s) reserved for future extension: {}()/\@: in .../vendor/matthiasmullie/scrapbook/src/Psr6/Pool.php:247
Stack trace:
#0 .../vendor/matthiasmullie/scrapbook/src/Psr6/Pool.php(58): MatthiasMullie\Scrapbook\Psr6\Pool->assertValidKey()
#1 .../test_redis_scrapbook.php(14): MatthiasMullie\Scrapbook\Psr6\Pool->getItem()
#2 {main} thrown in .../vendor/matthiasmullie/scrapbook/src/Psr6/Pool.php on line 247

but Redis allowed this key:

root@quartz:/etc/redis# redis-cli --version
redis-cli 6.2.6

root@quartz:/etc/redis# redis-cli

127.0.0.1:6379> set test:string 'foobar'
OK
127.0.0.1:6379> get test:string
"foobar"
127.0.0.1:6379> 

WTF?

Collection or server?

Hi Matthias,

Please take a look at this comment I posted for the PSR-16 review.

I have the same issue/question regarding Scrapbook.

For example, Scrapbook's Memcached-adapter takes a Memcached instance via the constructor - and while I could use Memcached::OPT_PREFIX_KEY with setOption() prior to constructing the adapter, the implementation calls Memcached::flush(), which would flush the entire server, not just a single collection, so while the issue of key-collisions could be addressed that way, this approach does not effectively result in truly having multiple collections.

Of course there are cases where you wish to clear the entire cache (typically on deployment) but there are certainly cases where you wish to clear only a collection.

Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.