cphalcon: [BUG]: Access to undefined property in SETTER function while calling findFirst model function

Access to undefined property exception thrown in setter function when I fetch record by findFirst($id) method

The findFirst() model function internally calling custom setter functions written in the model. But it throws exception when it finds accessing other property of the same model which is $this->property_five next to $this->property_four in mysql table

-------------------------------------------
id | property_four | property_five
-------------------------------------------
1  | Phalcon 3.4.4  | PHP 7.3
-------------------------------------------
E_USER_NOTICE: Access to undefined property Example::property_five in /path/to/Example.php on line xxx

Steps to reproduce the behavior:

class Example extends \Phalcon\Mvc\Model
{
   ...

   public function setPropertyFour()
   {
      $this->property_four = $this->property_five;
   }
}
Example::findFirst($id)

Details

  • Phalcon version: 3.4.4 Web framework delivered as a C-extension for PHP phalcon => enabled Author => Phalcon Team and contributors Version => 3.4.4 Build Date => Nov 18 2019 14:21:45 Powered by Zephir => Version 0.10.16-6826149172 Directive => Local Value => Master Value phalcon.db.escape_identifiers => On => On phalcon.db.force_casting => Off => Off phalcon.orm.events => On => On phalcon.orm.virtual_foreign_keys => On => On phalcon.orm.column_renaming => On => On phalcon.orm.not_null_validations => On => On phalcon.orm.exception_on_failed_save => Off => Off phalcon.orm.enable_literals => On => On phalcon.orm.late_state_binding => Off => Off phalcon.orm.enable_implicit_joins => On => On phalcon.orm.cast_on_hydrate => Off => Off phalcon.orm.ignore_unknown_columns => Off => Off phalcon.orm.update_snapshot_on_save => On => On phalcon.orm.disable_assign_setters => Off => Off
  • PHP Version: PHP 7.3.11-1+ubuntu16.04.1+deb.sury.org+1
  • Operating System: linux mint 18.1 cinnamon 64-bit
  • Installation type: Compiling from source
  • Zephir version (if any):
  • Server: Nginx
  • Other related info (Database, table schema): MySQL

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

The issue described is not a bug. It is actually a side effect on how the whole application works. You will notice the same thing with pure PHP also.

I copied the model like so:

class PropertySetter extends Model
{
    public function initialize()
    {
        $this->setSource('co_property_setter');
    }

    public function setPropertyFour()
    {
        $this->property_four = $this->property_five;
    }
}

The table is:

create table co_property_setter
(
    id             int(10) auto_increment primary key,
    property_one   varchar(100) null,
    property_two   varchar(100) null,
    property_three varchar(100) null,
    property_four  varchar(100) null,
    property_five  varchar(100) null
);

When I get one record from the table using findFirst, Phalcon does the following:

  • Construct the PHQL statement
  • Run the SQL command against the database
  • Get the results
  • Populate/transfer the results from the db resultset to the model class.

The issue happens when the data is transferred or assigned to the model, in particular the cloneResultsetMap method.

There is a loop which traverses the database resultset, and depending on the column map (if defined) and a few other conditions, the loop will assign the data to the model

let instance->{key} = value;

When this line is executed, the __set method is immediately called. The reason is because the property does not exist in the model (see model definition above). The code in the __set method also checks if my assignment is a possible setter in the model. For the first few assignments, nothing exists so no setters are called.

Remember we are still in the loop that is assigning data to the model.

So when the loop reaches property_four it :

  • tries to assign the value
  • calls __set
  • __set checks if there is a setter and calls it
  • the setter is trying to assign property_five to property_four
  • property_five is not assigned yet so __get is called
  • the property is not found -> exception is thrown.

By the above, we have successfully created a circular reference or an impossible solution to resolve.

I experimented with assigning a temporary flag before the population from database to model happens so that I do not invoke any setters but then that stopped the setters from running. With a bit of experimentation I managed to get the temporary variable to allow or not the setters to run and then leave everything as is. This created more problems than what we are trying to solve. In addition, to make that theory work, I had to run the loop twice, one without the setters and one with them, which reduces performance.

We will not be fixing this since it does impact performance.

Potential solutions:

  • Create a column map
  • Add properties as the fields in your model
  • Change if possible the setter name and utilize the afterFetch event to manipulate property_four with property_five

Just declare it inside model class, without value

class Example extends \Phalcon\Mvc\Model
{
    public $property_three;
    public $property_four;
    public $property_five;
}