Curious Magic Issue (I think)

Hi,

I have the following:

This works, the echo/var_dumped arrays show proper data from the database


  foreach($rows as $row){
    foreach($row as $key => $value){
      switch($key){
         case 'date_entered':
         case 'date_registered':
         case 'date_modified':
           $lead_dates[$key] = $value;
           if($key == 'date_entered' || $key == 'date_registered'){
             $user_dates[$key] = $value;
            }
           break;
           default:
              if(method_exists($this, $key)){
                 $this->$key($key,$value);
              }
     }
     echo 'lead dates: '; echo var_dump($lead_dates) . '<br />';
     echo 'user dates: '; echo var_dump($user_dates) . '<br />';
}


However if I have property values $this->user_dates and $this->lead_dates and I replace the local lead_dates and user_date variables I get null values:


  foreach($rows as $row){
    foreach($row as $key => $value){
      switch($key){
         case 'date_entered':
         case 'date_registered':
         case 'date_modified':
           $this->lead_dates[$key] = $value; // this doesn't assign a key or value
           if($key == 'date_entered' || $key == 'date_registered'){
             $this->user_dates[$key] = $value; // this doesn't assign a key or value
            }
           break;
           default:
              if(method_exists($this, $key)){
                 $this->$key($key,$value);
              }
     }
     echo 'lead dates: '; echo var_dump($this->lead_dates) . '<br />';  // outputs bool false
     echo 'user dates: '; echo var_dump($this->user_dates) . '<br />'; // outputs bool false
}


This occurs even I explicitly set $this->lead_dates = array(); and $$this->user_dates. Both these property values are set using the magic __set and retrieved using __get methods.

If I use local variable and then array_merge() them with the property variables then it works. The magic __set and __get are working throughout all other methods in the Object.

I curious if anyone knows why this is working this way or if anyone else has run into this? I think this is something to do with the magic methods but am not sure? I am using PHP 5.3.6.

Thanks,
Steve

Can you show us the implementation for lead_dates and user_dates?
Were you using this method?

Hi,


 <?php
class SetGetBase {
    protected $properties;
    protected $allowed_props;
    public function __construct(){}
    // Set undeclared property. Allow only the property names
    // defined in the allowed_props array
    function __set($property, $value){
        if(in_array($property, $this->allowed_props)){
            $this->properties[$property] = $value;
        } else {
            throw new exception("The $property is not allowed");
        }
    }
    // get defined property
    function __get($property){
        if (array_key_exists($property, $this->properties)) {
            return $this->properties[$property];
        } else {
            return FALSE;
        }
    }


    protected function getAllProperities(){
        return $this->properties;
    }
}





class AggregateImportedLeads extends SetGetBase {
    public function __construct(){
        /*
         * The properties are not passed into the object
        * the magic methods __set() and __get() are handling
        * this so you need to ensure that you set the value
        * after instantiating the values like $o_CreateUser->o_SaltHash = new SaltHash();
        * or $o_CreateUser->username = 'mike.johnson';
        */
        $this->properties = array();
        $this->allowed_props = array(
            'o_Db'
            ,'o_UserManage'
            ,'o_Validate'
            ,'temp_table_name'
            ,'lead_sources'
            ,'groups'
            ,'affinities'
            ,'arrangements'
            ,'locations'
            ,'genders'
            ,'years_of_births'
            ,'married_status'
            ,'languages'
            ,'user_dates'
            ,'lead_dates'
            
        );
      }

     protected function setLookupData(){
        // Get the Lead Source Ids and Names
        $results = $this->getTable('lead_sources', 'lead_source_id,lead_source');
        $this->lead_sources = $this->createDataArray($results);
        //$this->viewList('Lead Sources',$this->lead_sources);
        
        // Get the Group Ids and Names
        $results = $this->getTable('groups', 'group_id,group_name');
        $this->groups = $this->createDataArray($results);
        //$this->viewList('Groups',$this->groups);
        
        // Get the Affinities Ids and Names
        $results = $this->getTable('lk_affinities', 'id,affinity');
        $this->affinities = $this->createDataArray($results);
        //$this->viewList('Affinities',$this->affinities);
        
        // Get the Arrangements Ids and Names
        $results = $this->getTable('arrangement_types', 'arrangement_id,arrangement_name');
        $this->arrangements = $this->createDataArray($results);
        //$this->viewList('Arrangements',$this->arrangements);
        
        // Get the Locations Ids and Names
        $results = $this->getTable('locations', 'location_id,location_name');
        $this->locations = $this->createDataArray($results);
        //$this->viewList('Locations',$this->locations);
        
        // Get the Gender Ids and Names
        $results = $this->getTable('lk_genders', 'gender_id,gender');
        $this->genders = $this->createDataArray($results);
        //$this->viewList('Genders',$this->genders);
        
        // Get the Years(of birth) Ids and Names
        $results = $this->getTable('years', 'year_id,year');
        $this->years_of_births = $this->createDataArray($results);
        //$this->viewList('Years (of Birth)',$this->years_of_births);
        
        // Get the Married Statuses Ids and Names
        $results = $this->getTable('marital_status', 'marital_status_id,marital_status_name');
        $this->married_status = $this->createDataArray($results);
        //$this->viewList('Marital Statuses',$this->married_status);
        
        // Get the Language Ids and Names
        $results = $this->getTable('lk_languages', 'language_id,language');
        $this->languages = $this->createDataArray($results);
        //$this->viewList('Languages',$this->languages);
    }

    public function aggregateData(){
        echo 'Temp Table Name: ' . $this->temp_table_name . '<br />';
        /*
         * Set all arrays of the ids and data for every lookup
         * the inserts will require to insert reference to each lookup by ID
         */
        $this->setLookupData();
        
        /*
         * Get the imported CSV data from the temporary table
         */
        $rows = $this->getAllNewlyInsertedRecords();


        /*
         * Set up variables that need their scope outside the for loops
        * This is needed if there is a sales manager but no advisor. In
        * this case, the switch sets the advisor to the same values as
        * the sales manager so the variables from the previous inside foreach
        * loop needs to be maintained to allow the values to be set.
        */
        $lead_dates = array();
        $user_dates = array();
        
        foreach($rows as $users){
            foreach($users as $key => $value){
                switch($key){
                    // match the column names returned in the queried results
                    case 'date_entered':
                    case 'date_registered':
                    case 'date_modified':
                        $lead_dates[$key] = $value;
                        if($key == 'date_entered' || $key == 'date_registered'){
                            $user_dates[$key] = $value;
                        }
                        break;
                    default:
                        if(method_exists($this, $key)){
                            $this->$key($key,$value);
                        }
                }
                echo 'lead dates: '; echo var_dump($lead_dates) . '<br />';
                echo 'user dates: '; echo var_dump($user_dates) . '<br />';
                //$this->o_UserManage;
            }
        }
    }



These methods and classes should give you a pretty good idea.

Regards,
Steve

I half wonder if due to use magic __set and __get that your multi-dimensional keys must exist in allowed_props? Have you tried adding them to allowed_props to see if they then get set?

Just to add something, if that is indeed the case, you could also try something like this:

((array)$this->lead_dates)[$key] = $value;

to ensure it isn’t trying to use SetGetBase

I added them to the allowed props and it did not help.

Just for interest (not for you but maybe for others viewing this thread), when one uses the magic set and get it allows an object to create any property dynamically. I did this to ensure I only allow it to create those properties that are in the list. One might think, why not just pass them through the constructor? I am working with a very data heavy applications and the constructors got very long unmanageable; therefore this allows a cleaner implementation. As an example, this creates and sets each property shown:


$o_ImportLeadData->temp_table_name = $o_RandomString->getRandomString('__temp_', 6);
$o_ImportLeadData->o_Db = $o_Db;
$o_ImportLeadData->importContacts();

And setting the properties inside Objects also work. In this case I am setting lead_sources for the first time to a multi-dimensional array, which is returned by the createDataArray() method:


$this->lead_sources = $this->createDataArray($results);

This did not work after I added ‘key’ and ‘value’ to the allowed_props:


foreach($rows as $users){
            foreach($users as $this->key => $this->value){
                switch($this->key){
                    case 'date_entered':
                    case 'date_registered':
                    case 'date_modified':
                        (array)$this->lead_dates[$this->key] = $this->value;
                        if($this->key == 'date_entered' || $this->key == 'date_registered'){
                            (array)$this->user_dates[$this->key] = $this->value;
                        }
                        break;
                    default:
                        if(method_exists($this, $this->key)){
                            $this->$key($this->key,$this->value);
                        }
                }
                echo 'lead dates: '; echo var_dump($this->lead_dates) . '<br />';
                echo 'user dates: '; echo var_dump($this->user_dates) . '<br />';
                //$this->o_UserManage;
            }
}

Thanks for you efforts, I can work around it, I just don’t like it - it seems buggy and unpredictable :slight_smile:

Steve

Okay, I got it working. Seems this was a bug in PHP, but it was fixed somewhere in the 5.2 version

Here is my updated code (note two important things, before copying and pasting 1) I overwrote your getAllNewlyInsertedRecords() method and I commented out your setLookupData method)

 <?php
class SetGetBase {
    protected $properties;
    protected $allowed_props;
    public function __construct(){}
    // Set undeclared property. Allow only the property names
    // defined in the allowed_props array
    function __set($property, $value){
        if(in_array($property, $this->allowed_props)){
            $this->properties[$property] = $value;
        } else {
            throw new exception("The $property is not allowed");
        }
    }
    // get defined property
    function & __get($property){
        return $this->properties[$property];
    }


    protected function getAllProperities(){
        return $this->properties;
    }
}

class AggregateImportedLeads extends SetGetBase {
    public function __construct(){
        /*
         * The properties are not passed into the object
        * the magic methods __set() and __get() are handling
        * this so you need to ensure that you set the value
        * after instantiating the values like $o_CreateUser->o_SaltHash = new SaltHash();
        * or $o_CreateUser->username = 'mike.johnson';
        */
        $this->properties = array();
        $this->allowed_props = array(
            'o_Db'
            ,'o_UserManage'
            ,'o_Validate'
            ,'temp_table_name'
            ,'lead_sources'
            ,'groups'
            ,'affinities'
            ,'arrangements'
            ,'locations'
            ,'genders'
            ,'years_of_births'
            ,'married_status'
            ,'languages'
            ,'user_dates'
            ,'lead_dates'
            
        );
      }

     protected function setLookupData(){
        // Get the Lead Source Ids and Names
        $results = $this->getTable('lead_sources', 'lead_source_id,lead_source');
        $this->lead_sources = $this->createDataArray($results);
        //$this->viewList('Lead Sources',$this->lead_sources);
        
        // Get the Group Ids and Names
        $results = $this->getTable('groups', 'group_id,group_name');
        $this->groups = $this->createDataArray($results);
        //$this->viewList('Groups',$this->groups);
        
        // Get the Affinities Ids and Names
        $results = $this->getTable('lk_affinities', 'id,affinity');
        $this->affinities = $this->createDataArray($results);
        //$this->viewList('Affinities',$this->affinities);
        
        // Get the Arrangements Ids and Names
        $results = $this->getTable('arrangement_types', 'arrangement_id,arrangement_name');
        $this->arrangements = $this->createDataArray($results);
        //$this->viewList('Arrangements',$this->arrangements);
        
        // Get the Locations Ids and Names
        $results = $this->getTable('locations', 'location_id,location_name');
        $this->locations = $this->createDataArray($results);
        //$this->viewList('Locations',$this->locations);
        
        // Get the Gender Ids and Names
        $results = $this->getTable('lk_genders', 'gender_id,gender');
        $this->genders = $this->createDataArray($results);
        //$this->viewList('Genders',$this->genders);
        
        // Get the Years(of birth) Ids and Names
        $results = $this->getTable('years', 'year_id,year');
        $this->years_of_births = $this->createDataArray($results);
        //$this->viewList('Years (of Birth)',$this->years_of_births);
        
        // Get the Married Statuses Ids and Names
        $results = $this->getTable('marital_status', 'marital_status_id,marital_status_name');
        $this->married_status = $this->createDataArray($results);
        //$this->viewList('Marital Statuses',$this->married_status);
        
        // Get the Language Ids and Names
        $results = $this->getTable('lk_languages', 'language_id,language');
        $this->languages = $this->createDataArray($results);
        //$this->viewList('Languages',$this->languages);
    }

    private function getAllNewlyInsertedRecords()
    {
        return array(
                array(
                    'date_entered' => '2012-03-04',
                    'date_registered' => '2012-03-04',
                    'date_modified' => '2012-07-01'
                ),                
                array(
                    'date_entered' => '2012-03-05',
                    'date_registered' => '2012-03-05',
                    'date_modified' => '2012-07-02'
                ),                
                array(
                    'date_entered' => '2012-03-06',
                    'date_registered' => '2012-03-06',
                    'date_modified' => '2012-07-03'
                )
            );
    }

    public function aggregateData(){
        echo 'Temp Table Name: ' . $this->temp_table_name . '<br />';
        /*
         * Set all arrays of the ids and data for every lookup
         * the inserts will require to insert reference to each lookup by ID
         */
        //$this->setLookupData();
        
        /*
         * Get the imported CSV data from the temporary table
         */
        $rows = $this->getAllNewlyInsertedRecords();


        /*
         * Set up variables that need their scope outside the for loops
        * This is needed if there is a sales manager but no advisor. In
        * this case, the switch sets the advisor to the same values as
        * the sales manager so the variables from the previous inside foreach
        * loop needs to be maintained to allow the values to be set.
        */
        //$lead_dates = array();
        //$user_dates = array();
        
        foreach($rows as $users){
            foreach($users as $key => $value){
                switch($key){
                    // match the column names returned in the queried results
                    case 'date_entered':
                    case 'date_registered':
                    case 'date_modified':
                        $this->lead_dates[$key] = $value;
                        if($key == 'date_entered' || $key == 'date_registered'){
                            $this->user_dates[$key] = $value;
                        }
                        break;
                    default:
                        if(method_exists($this, $key)){
                            $this->$key($key,$value);
                        }
                }
                echo 'lead dates: '; echo var_dump($this->lead_dates) . '<br />';
                echo 'user dates: '; echo var_dump($this->user_dates) . '<br />';
                //$this->o_UserManage;
            }
        }
    }
}

$test = new AggregateImportedLeads();
$test->aggregateData();
?>  

Okay, so the first thing I did was make __get by reference (per the URL I provided). The second thing I did was change how the __get functions, in other words I removed the IF array_key_exists check, it now just attempts to return the value at that location no matter what (doesn’t seem to throw any notices if the key doesn’t exist since it is by reference).

Hope this helps.

Brilliant @cpradio; It work well now and it very definitely helps - I’m not nervous anymore and I don’t have to hack the code to get it working:). What tipped you off the pass by reference?

Regards,
Steve

When I first ran the $this->lead_dates, I got a notice stating “Notice: Indirect modification of overloaded property AggregateImportedLeads::$lead_dates has no effect”

Searching on “Indirect modification of overloaded property” lead me to the php bug which lead me to the by reference logic. :slight_smile:

Funny I have error reporting set to ALL and I get no such Notice.

Thanks, I’ll have to look into why I’m not getting this type of Notice.

Regards,
Steve

I have mine set to

error_reporting = E_ALL | E_STRICT

Which means:

; E_ALL | E_STRICT (Show all errors, warnings and notices including coding standards.)

Yup when I added E_STRICT I can reproduce the similar notice :slight_smile: