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

Created on 18 Nov 2019  路  17Comments  路  Source: phalcon/cphalcon

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
not a bug wontfix

All 17 comments

@manojwp Did you setup column map?

@manojwp Did you setup column map?

No. Is it not created automatically ? Both column names and model property names are same.
Is there a way to disable setter functions to be called only while fetching records like findFirst ?

No, because you are working inside raw PHP Object class, if you didn't declared it, then it will throw errors/notices. Magic happens after, with findFirst(). which picks Model class and add all necessary properties.

No, because you are working inside raw PHP Object class, if you didn't declared it, then it will throw errors/notices. Magic happens after, with findFirst(). which picks Model class and add all necessary properties.

it's not throwing exception on all setters, it does only when it finds accessing other properties inside setter function..


// This setter works
public function setPropertyThree()
{
      $this->property_three = 'some value';
}

// This one fails
public function setPropertyFour()
{
      $this->property_four = $this->property_five; 
// here $this->property_five is next to property_four in mysql table. 
// Able to access properties only which are prior to property_four and whatever comes after property_four are not accessible
}

// This one works 
public function setPropertyFive()
{
      $this->property_five= $this->property_four;
}

Just declare it inside model class, without value

class Example extends \Phalcon\Mvc\Model
{
    public $property_three;
    public $property_four;
    public $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;
}

I've too many columns in a table can't declare all. Moved property_five prior to property_four in the table and it worked. Not sure why property_five was not set in the model anyways it auto-sets those properties, it should auto-set all the properties before calling setter functions of the model that was the expected behaviour

You can generate models with properties with https://github.com/phalcon/phalcon-devtools

You can generate models with properties with https://github.com/phalcon/phalcon-devtools

I am migrating phalcon 1.3.4 to 3.4.4 so not sure this tool would help

Might help, as it supports by Phalcon versions sinse 1.3.x

image

Might help, as it supports by Phalcon versions sinse 1.3.x

image

Is it possible to declare properties for existing models by command ?

Yes, it will generate similar class as this one:
https://github.com/phalcon/phalcon-devtools/blob/4.0.x/tests/_data/console/app/models/files/TestModel.php

It was working with old version of phalcon 1.3.4, with latest it fails, anyways thank you so much for your help appreciate it

So if you access this property outside of $this context it works? Looks like maybe the __get is not called when accessing property from $this context in zephir but this would need to be checked, but i doubt this is an issue.

So if you access this property outside of $this context it works? Looks like maybe the __get is not called when accessing property from $this context in zephir but this would need to be checked, but i doubt this is an issue.

No, because you are working inside raw PHP Object class, if you didn't declared it, then it will throw errors/notices. Magic happens after, with findFirst(). which picks Model class and add all necessary properties.

it's not throwing exception on all setters, it does only when it finds accessing other properties inside setter function..


// This setter works
public function setPropertyThree()
{
      $this->property_three = 'some value';
}

// This one fails
public function setPropertyFour()
{
      $this->property_four = $this->property_five; 
// here $this->property_five is next to property_four in mysql table. 
// Able to access properties only which are prior to property_four and whatever comes after property_four are not accessible
}

// This one works 
public function setPropertyFive()
{
      $this->property_five= $this->property_four;
}

@Jurigag this is the case

Just declare it inside model class, without value

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

I've too many columns in a table can't declare all. Moved property_five prior to property_four in the table and it worked. Not sure why property_five was not set in the model anyways it auto-sets those properties, it should auto-set all the properties before calling setter functions of the model that was the expected behaviour

@Jurigag this is what i tried

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

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

Why not put the values stored on the attributes in the model into a private attributes array?
__get reads from attributes
__set writes to attributes

Was this page helpful?
0 / 5 - 0 ratings