Cphalcon: [BUG]: Stream backend cache

Created on 30 Nov 2019  路  11Comments  路  Source: phalcon/cphalcon

Using stream adapter in Model cache
Config setting

'cache' => [
        'adapter' => 'stream',
        'options' => [
            'defaultSerializer' => 'Json',
            'storageDir' => BASE_PATH . '/storage/caches/',
            'lifetime' => 172800,
            'prefix' => 'data-'
        ]
    ],

In Provider

public function register(DiInterface $di): void
    {
        $di->setShared('modelsCache', function () {
            $config = $this->getShared('config');
            $cache = $config->cache->toArray();

            $serializerFactory = new SerializerFactory();
            $adapterFactory    = new AdapterFactory($serializerFactory);

            $adapter = $adapterFactory->newInstance($cache['adapter'], $cache['options']);
            return new Cache($adapter);
        });
    }

In Controller

class Post extends BaseController{
    public function index(){
        $results = Model\Post\Post::find([
            'cache' => [
                'key' => 'my-cache',
            ],
        ]);

        var_dump($this->cache->get('my-cache'));exit;

    }
}

When first run Phalcon created folder
like

storage
--caches
--data
--my
---c
--ac
--my-cache

Second run get error
Cache didn't return a valid resultset

Phalcon 4.0 rc3
Macos Catalina
Mamp Pro
PHP 7.3.7

I am ready test for redis adapter working nice!

wontfix

Most helpful comment

I also ran into this error.
I think the problem is here https://github.com/phalcon/cphalcon/blob/858d0e57ed42f0dcd8723cca64798dded84790ea/phalcon/Storage/Serializer/Json.zep#L29-L33

Before encoding in JSON, it is checked for isSerializable, but in the Storage\Serializer\Json::unserialize method, the data is returned without checking whether it is encoded or not.

https://github.com/phalcon/cphalcon/blob/858d0e57ed42f0dcd8723cca64798dded84790ea/phalcon/Storage/Serializer/Json.zep#L41

All 11 comments

I also ran into this error.
I think the problem is here https://github.com/phalcon/cphalcon/blob/858d0e57ed42f0dcd8723cca64798dded84790ea/phalcon/Storage/Serializer/Json.zep#L29-L33

Before encoding in JSON, it is checked for isSerializable, but in the Storage\Serializer\Json::unserialize method, the data is returned without checking whether it is encoded or not.

https://github.com/phalcon/cphalcon/blob/858d0e57ed42f0dcd8723cca64798dded84790ea/phalcon/Storage/Serializer/Json.zep#L41

I cannot reproduce this guys. I saw it once in my test and local environment but it was caused by a corrupted cache. Once I cleared the cache (deleted the file) it worked just fine.

This is the full build: https://travis-ci.com/niden/cphalcon/builds/138859860

I did make an enhancement and added a Json helper class to encode/decode with exceptions so that errors can be discovered earlier but with or without the new class it worked just fine.

This is the test I wrote based on the input

https://github.com/phalcon/cphalcon/blob/4.0.x/tests/integration/Mvc/Model/FindCest.php#L66

As you can see the find gets called and the cache file appears and also it exists in the cache. I get the resultset back from the cache (not calling the model find again) and it contains the same data.

Anything you guys can see and point out that I have missed please let me know. Alternatively I will just merge the branch with the new class and close this.

@niden

$data = Test::find([
    'cache' => [
        'key' => 'test-data'
    ]
]);

// The original data is an object.
var_dump($data);      //   $data is Object

// object(Phalcon\Mvc\Model\Resultset\Simple)[259]
//   protected 'activeRow' => null
//   ....


// But the data from the cache is an array
$dataFromCache = $this->modelsCache->get('test-data');
var_dump($dataFromCache);      //   $dataFromCache is Array

// array (size=2)
//   0 =>
//     object(stdClass)[240]
//       public 'id' => int 1
//       public 'email' => string '__email__' (length=13)
//       public 'fullname' => string '__fullname__' (length=56)
//   1 =>
//     object(stdClass)[241]
//       public 'id' => int 2
//       public 'email' => string '__email1__' (length=25)
//       public 'fullname' => string '__fullname__' (length=38)
//   ......

And here the object is clearly expected.
https://github.com/phalcon/cphalcon/blob/84924803960de4e62c96afd401b3c2da08ec3d5f/phalcon/Mvc/Model/Query.zep#L3678-L3685

But let result = cache->get(key); $result is an array and we get this error.

Fatal error: Uncaught Phalcon\Mvc\Model\Exception: Cache didn't return a valid resultset in phalcon/Mvc/Model/Query.zep on line 3683

OK I get what is going on right now.

The long story short is we should not use the Json serializer for models because it will lead to the error you see above.

The JSON serializer uses json_encode and json_decode to serialize and unserialize data. When we are using objects, it calls the jsonSerialize method in the object to return an array which will allow the json_encode to work as expected.

For the Simple and Complex resultsets, the jsonSerialize method will return an array which will be json_encoded. When it is retrieved back it will return the relevant data but as an array AND as stdClass objects vs the correct Simple and Row respectively.

I have not seen any implementations that could pick up an object, store it with JSON and restore it back to the original object (unless it is an array of stdClass objects).

I will need to mark this as Won't Fix/Not a bug and ensure that the documentation clearly states that for modelsCache you cannot use the JSON serializer or others that cannot store objects properly. Php should work just fine, or any other serializer that can properly serialize and unserialize objects

Igbinary works just fine with the above as well as Php

OK I get what is going on right now.

The long story short is we should not use the Json serializer for models because it will lead to the error you see above.

The JSON serializer uses json_encode and json_decode to serialize and unserialize data. When we are using objects, it calls the jsonSerialize method in the object to return an array which will allow the json_encode to work as expected.

For the Simple and Complex resultsets, the jsonSerialize method will return an array which will be json_encoded. When it is retrieved back it will return the relevant data but as an array AND as stdClass objects vs the correct Simple and Row respectively.

I have not seen any implementations that could pick up an object, store it with JSON and restore it back to the original object (unless it is an array of stdClass objects).

I will need to mark this as Won't Fix/Not a bug and ensure that the documentation clearly states that for modelsCache you cannot use the JSONserializer or others that cannot store objects properly.Php` should work just fine, or any other serializer that can properly serialize and unserialize objects

I agree! I wanted to write about it

@duongkhoangiam Thanks for reporting this.

Also can you test for folder struct ?
in phalcon 3.x the cache file generate to /storage/cache/data/my-cache

in phalcon 4

storage
--caches
--data
--my
---c
--ac
--my-cache

That mean cache created folder with key, prefix , and name .
This problem come in Stream Adapter only, another Adapter working fine !

i think problem in

directory = this->getDir(key);

@duongkhoangiam These subfolders are intentionally created like that. Using this methodology we avoid the "too many files in a folder" for busy applications that need to store a lot of cached entities.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

EquaI1ty picture EquaI1ty  路  3Comments

abcpremium picture abcpremium  路  3Comments

ismail0234 picture ismail0234  路  3Comments

hailie-rei picture hailie-rei  路  3Comments

ruudboon picture ruudboon  路  3Comments