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

Unexpected findFirst() caching

Hi all,

I'm experiencing an odd situation where it appears findFirst() is returning an existing object, even though I have no caching. I'm pretty sure this is just a brain fart on my part, but I need someone else to point it out.

Example code:

# This part works as expected - the record is created in the database
$AppPage = new AppPage();
$AppPage->save([
  'parent_id'=>2,
  'path'=>'/appPage',
  'type'=>'App',
  'title'=>'App page',
  'menu_title'=>'App page'
]);

# "foo" is not a column in the database, nor is it a property declared in the model, 
# so "foo" should only exist on this particluar object
$AppPage->foo = 'bar';

$ReloadedPage = AppPage::findFirst($AppPage->id);

echo $ReloadedPage->foo; # outputs "bar"

I'm positive I haven't set up any model caching. In fact, when I view my query log, I can see a second SELECT clause being run, corresponding to the second findFirst() call.

What am I missing?

Better use a finder to avoid confusions:

$ReloadedPage = AppPage::findFirstById($AppPage->id);

I can reproduce it, seems like a bug. I'm not good with English, so sorry if my explanation is not good enough..

When you instantiate a new object Model the constructor calls Phalcon\Mvc\Model\Manager::initialize, to apply relationships and behaviors to the model, then this model is cached in the models manager, and when querying with findFirst* etc this model is cloned and filled with the results.

What occurs here is that when creating that Model via direct instantiation the object that the models manager will hold is the same as you created, so assigning them properties causes that also cloned models having it, but when using findFirst* the original model is kept private and there's no problem.

A solution could be extend the models manager (because Model::__construct is a final method) cloning the object instead of assign the provided (and register it in the DI):

<?php

class Manager extends \Phalcon\Mvc\Model\Manager {

    public function initialize(/* ModelInterface */ $model) {
        $className      = get_class($model);
        $lowerClassName = strtolower($className);

        if (isset ($this->_initialized[$lowerClassName])) {
            return FALSE;
        }

        $this->_initialized[$lowerClassName] = clone $model;

        if (method_exists($model, 'initialize')) {
            $model->initialize();
        }

        $this->_lastInitialized = $model;

        $eventsManager = $this->_eventsManager;
        if (is_object($eventsManager)) {
            $eventsManager->fire('modelsManager:afterInitialize', $this, $model);
        }

        return TRUE;
    }

}

I think I did not missed anything, @andresgutierrez I'm right?

I'm having similar issue with using Phalcon as Gearman worker (long running process). Some of object are persisting with wrong data inside db and I'm having difficulty to recreate this on my dev machine.

Would it help to register modelManager as non shared service?

edited Apr '15

Ok I have recreated this. Problem is huge for us, but we found simple solution.

> $car = new Car();
> $car->name = "Smart"
> 
> $driver = new Driver();
> $driver->name = "John doe";
> 
> $car->driver = $driver;
> $car->save();

> $oldCar = Car::findFirstByName('toyota');  // for example there is toyota in db with driver named "Toyota Driver"
> echo $oldCar->driver->name; // Will echo John doe

and saving $oldCar will override driver.

Fix for us was first saving $driver and then using id. For example

> $driver->save();
> $car->driverId = $driver->id;
> $car->save();

This is just pseudo code and hipotetical situation. When I will find time, I'll create complete test case.

Using findFirstById had no affect.

Better use a finder to avoid confusions:

$ReloadedPage = AppPage::findFirstById($AppPage->id);