We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Bug with reusable models and cache

This problem is kinda hard to explain so bare with me, but I've got 3 models: User, Product, and ProductStatus. Each Product has one Status, and a User can have many Products.

    class User extends \My\Models { //which extends \Phalcon\Mvc\Model
        public function initialize() {
            $this->hasMany("user_id", "Product", "user_id", array('alias'=>'products'));
        }
    }

    class Product extends \My\Models {
        public function initialize() {
            $this->belongsTo("user_id", "User", "user_id", array('alias'=>'user'));
            $this->hasOne("product_id", "ProductStatus", "product_id", array('alias'=>'status', 'reusable' => true));
        }
    }

    class ProductStatus extends \My\Models {
        public static $cacheable = true; //switch I added to turn off caching, not relevant but shown for completeness sake
        public function initialize() {
            $this->hasOne("product_id", "Product", "product_id", array('alias'=>'product'));
        }
    }

I'm also using a custom modelsManager to save models to a redis cache using the Backend\Redis class from Phalcon Incubator.

    //In my setup
    $di->setShared('modelsCache', function() {
        $redis = new Redis();
        $redis->connect(....);
        $frontCache = new \Phalcon\Cache\Frontend\Data(array(
            "lifetime" => 86400
        ));
        $cache = new \Phalcon\Cache\Backend\Redis($frontCache, array(
            'redis' => $redis
        ));
        return $cache;
    });

    //mananger class (This is literally all it is, mostly copied from "Caching in the ORM" doc)
    class Manager extends Phalcon\Mvc\Model\Manager {

        public function getReusableRecords($modelName, $key) {
            $cache = $this->getDI()->get('modelsCache');
            if($search_result = $cache->exists($key) {
                return $cache->get($key);
            }

            return parent::getReusableRecords($modelName, $key);
        }

        public function setReusableRecords($modelName, $key, $record) {
            if(is_subclass_of($records,'\My\Models') && $records::$cacheable == true) {
                $cache = $this->getDI()->get('modelsCache');
                $cache->save($save_key, $records, 3600);
            }
            return parent::setReusableRecords($modelName, $key, $records);
        }
    }

So here's where it gets weird. Lets say $user->products = (Product id #5, #12, #27) and Product #5 is saved in cache from an earlier page load. And then I do this to update a user's statuses:

    foreach($user->products as $p) {
        $status = $p->status;
        $status->date_updated = date('Y-m-d');
        $status->save();
    }

Every loop generates the exact same update sql:

    UPDATE product_status VALUES (...) WHERE product_status_id = 5

What's happening is that on the first loop it finds the key in cache and returns. On the next loop though it MISSES CACHE of course and goes to parent::getReusableRecords(). Phalcon does whatever it does there, then it comes back to setReusableRecords(). However, if I var_dump the $record that's getting passed back to there, it looks like this:

    My\Model\Product {
        ... bunch of other di stuff ...
        [_uniqueKey:protected] => `product_status_id` = ?
        [_uniqueParams:protected] => Array
            (
                [0] => 5
            )
        [product_status_id] => 12
        [user_id] => 80112
        [date_updated] => 2014-10-10
        ...
    }

It seems as if it's reused the internals of the previous object to build the next one. This only happens with models marked as 'reusable' => true, anything else is fine. Doesn't seem to be a problem with redis either, the only thing in there is a serialized version of the model. Any ideas?



98.9k

The key you're using to store the cache is an undefined variable $save_key:

public function setReusableRecords($modelName, $key, $record) {
    if (is_subclass_of($records,'\My\Models') && $records::$cacheable == true) {
                $cache = $this->getDI()->get('modelsCache');
                $cache->save($save_key, $records, 3600);
    }
    return parent::setReusableRecords($modelName, $key, $records);
}

The key you're using to store the cache is an undefined variable $save_key:

public function setReusableRecords($modelName, $key, $record) {
  if (is_subclass_of($records,'\My\Models') && $records::$cacheable == true) {
               $cache = $this->getDI()->get('modelsCache');
               $cache->save($save_key, $records, 3600);
  }
  return parent::setReusableRecords($modelName, $key, $records);
}

Sorry, about that. I just typed it wrong in the post. It is actually $key