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

non db mapped fields, to persist in model, per instance.

Hi,

I am fairly new to Phalcon so I am sure I am doing something wrong, or I have misunderstood somehting, but I have been through the available docs a few times, and I cannot quite figiure out why this is happening.

I am using Phalcon 1.3.4 on PHP 5.5.11 NTS Windows.

I have certain data that I would like to store on my entities that inherit from Model. This data is operational, and not persisted to the database. In this paticular case I noticed what I deemed as odd behaviour when lazy loading an encryption provider from an object based on model. This should be a per instance value, and this works fine on intitial creation of the objects, but after calling ::find() the instance fields behave as if statically cached, so that each subsequent object contains the instance value from the first hydrated object.

I have what I believe to be the most simple example, using an object hash.

Please consider the following simple class based on Model :

/*
 * CREATE TABLE `dumbtest` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `someData` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 */
class Simple extends \Phalcon\Mvc\Model
{
    protected $_hash = null;

    public function getSource()
    {
        return 'dumbtest';  //table based on above sql, does not contain hash
    }

    public function getPerInstanceHash()
    {
       return $this->_hash;
    }

    public function onConstruct()
    {
        $this->_hash = uniqid(true);
    }
} 

The hash is generated in onConstruct(), and on construction this works fine, each new object of Simple type contains a unique hash.

The trouble comes after calling ->create() and then in the same request calling ::find(). Now onConstruct() is not called, so a caching function is smart enough to recognise these objects, but the $_hash member now appears to behave as a singelton.

Please see following example test to indicate my experience :

class ObjTest  extends UnitTestCase
{
    public function testORMStatic()
    {
        $firstObj = new Simple();
        $firstObj->someData = 'first object';
        $firstObj->save();

        $secondObj = new Simple();
        $secondObj->someData = 'second object';
        $secondObj->save();

        //hash should be different per instance
        //this test will pass
        $this->assertTrue($firstObj->getPerInstanceHash() != $secondObj->getPerInstanceHash(), 'The hash should be different for each instance, it is a GUID generated in onConstruct()');

        //now lets fetch the objects through the results set
        $objects = Simple::find();
        $this->assertTrue($objects->count() == 2); //i.e.  no polluting data

        $firstObj = $objects[0];
        $secondObj = $objects[1];

        //this time the test will fail, both hashs are identical
        $this->assertTrue($firstObj->getPerInstanceHash() != $secondObj->getPerInstanceHash(), 'The hash should be different for each instance, it is a GUID generated in onConstruct()');

        $firstObj->delete();
        $secondObj->delete();
    }
}

And the output of the tests (with the last test failing) :

Fail: D:\Source\Repos\App\app\tests\other\ObjTest.php -> ObjTest -> testORMStatic -> The hash should be different for each instance, it is a GUID generated in onConstruct() at [D:\Source\Repos\App\app\tests\other\ObjTest.php line 33]

Could someone please help me understand what the caching mechanism here is, and how I can work around this or better acheive storing a non db persisted value in a model per instance.

Many thanks.



3.2k
edited Dec '14

After some play, I found the afterFetch() method, which I can use as a work around.


    public function afterFetch()
    {
        $this->_hash = spl_object_hash($this); //set to something unique
    }

So I can now provide a base with a setUp() method which can be called by onConstruct() and afterFetch(), allowing me to reset any operational state. Does this sound like a fair approach, or am I abusing your ORM?

Incidently, the above mentioned behavior still seems wrong to me, in so much as it seems dangerous for the same value to be repeated instance to instance, perhaps setting back to null would be safer, at least then a getter could react and reload based on the null value. I wonder if there is some way I control this behavoir?

Thanks



43.9k

perhaps setting back to null would be safer, at least then a getter could react and reload based on the null value. I wonder if there is some way I control this behavoir?

this sounds good to me !