Jump to content

Confused about how __set() and __get() methods work


eldan88

Recommended Posts

Hey guys. I know I asked a similar question before, but I still feel a little confused based on this video tutorial I am following. On the tutorial the author created a protected class named $_postal_code. He used the magic __set() and __get() method to access the postal_code since it can't be accessed when its protected. I have been  following the tutorial but still can't seem to __set() and __get() the value of the postal_code.

The author created a function names postal_code_guess() which  returns a string name "LOOK UP" as a temporary place holder, for the actual code that will return a postal code from a database.

Below is the class Address class he created.
 

<?php

class Address {
    //Street address

public $street_address_1;
public $street_address_2;

//Name of the city
public $city_name;

// Name of the subdivision

public $subdivision_name;

//Potal Code
    protected $_postal_code;

//Name of the country
public $country_name;

//Primary key of an address
    protected $_address_id;
    
 //When the record was created and last updated
 protected $_time_created;
 protected $_time_updated;

 //Magic __get();
 //@param string $name
 //@return mixed
 
 function __get($name) {
 //Postal code look up if unset
     if(!$this->_postal_code) {
      $this->_postal_code = $this->_postal_code_guess();   
     }
     
     //Attempt to return a protected property name
     $protected_property_name = "_" . $name;
     if(property_exists($this,$protected_property_name)) {
      return $this->$protected_property_name;
     }
     
     //Unable to access _property; trigger error
     trigger_error('Undefined Property via __get:' . $name);
     
     
 }
//@param string $name
 //@param mixed $value
 function __set($name, $value) {
//Allow anything to set the postal code
     if('postal_code' == 'name' ) {
      $this->$name = $value;
      return;
         
     }
     
}
 
 
 
 
 //Guess the postal code given the subdivision and city name
 //@todo replace with a database lookup
 //@return string
 
 public function _postal_code_guess() {
  return "LOOKUP";   
   }
 




//Display address in HTML
//@return string
public function display() {
    $output = "";
    $output = $this->street_address_1;
    if($this->street_address_2) {
     $output .= "<br>" . $this->street_address_2;   
          }// End of  if($this->street_address_2)
             
      
        // City, Subdivision Postal     
      $output .= "</br>";
     $output .= $this->city_name . ',';
             return $output;
       }// End of display
    
       
// End of class
 

}

And here is the demo.php file where we call the postal code.

 

echo "<h2> setting properties</h2>";
$address->street_address_1 = "555 Fake Street";
$address->city_name = "Townseville";
$address->subdivision_name = "State";
$address->postal_code = "12345";
$address->country_name = "United States of America";


echo "Displaying Address";
echo $address->display();

echo "<h2>Displaying Address</h2>";
unset($address->postal_code);
echo $address->display();

echo "<h2> Testing magic __get() and __set() </h2>";
unset($address->postal_code);
echo $address->display();

Here is what I don't understand
1) Why is he unsetting "postal_code" when it wasn't set in the first place, and its a protected property.
2) How come my code is not showing the "LOOK UP" string when I call the display() method on last <h2> tag

echo "<h2> Testing magic __get() and __set() </h2>";

3) On the tutorial he actual got the postal code before unsetting the value? How is that possible when its protected.

 

Maybe im non getting something. But if you guys can help me out here I would really appreciate it. Thanks :happy-04:

Link to comment
Share on other sites

 

//@param string $name
 //@param mixed $value
 function __set($name, $value) {
//Allow anything to set the postal code
     if('postal_code' == 'name' ) {
      $this->$name = $value;
      return;
     }
}

 

That is 100% wrong. Either you transcribed it from the tutorial incorrectly, or the tutorial is wrong. The string 'postal_code' will never equal the string 'name'. 

 

1) Why is he unsetting "postal_code" when it wasn't set in the first place, and its a protected property.

Unset() does not work with __get/__set, so either they do that to highlight the fact that it doesn't work, or it is a mistake.

 

As for the other two questions, fix the class and re-try.

Edited by kicken
Link to comment
Share on other sites

__get and __set are "magic" hook methods, they are called whenever a property is accessed on an instance of a class, and whenever a property is assigned a value on an instance of a class that implement them respectively.

 

They can be used to control how a property is inserted into or retrieved from an object, I.e. they allow you to run post and/or pre-processing tasks to control the data that the object contains. Take the Address example:

 

abstract class FilterableProperties { // this would be better as a trait  public function __get($name) {    $property     = $this->_get_property($name);    $beforeFilter = "_before_get_{$name}";    if ( method_exists($this, $beforeFilter) )      return call_user_func(array($this, $beforeFilter), $this->property);    return $this->$property;  }  public function __set($name, $value) {    $property     = $this->_get_property($name);    $beforeFilter = "_before_set_{$name}";    if ( method_exists($this, $beforeFilter) ) {      $this->$property = call_user_func(array($this, $beforeFilter), $value);    } else {      $this->$property = $value;    }  }  protected function _get_property($name) {    $property = "_{$name}";    if ( !property_exists($this, $property) )      throw new Exception(sprintf('Property %s does not exist on %s', $name, get_class($this)));    return $property;  }}class Address extends FilterableProperties {  protected $_property_name_or_number = null;  protected $_street = null;  protected $_town = null;  protected $_city = null;  protected $_county = null;  protected $_postcode = null;  protected $_country = null;  protected $_country_code = null;   protected function _before_set_postcode($newPostcode) {    $newPostcode = strtoupper(preg_replace('/\s+/', '', $newPostcode));    if ( !preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $newPostcode) &&      !preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $newPostcode) ) {      throw new Exception('Invalid postcode');    } else {      return substr($newPostcode, 0, -3) .' '. substr($newPostcode, -3);    }  }}

 

 

Now you are prevented from updating an instance to contain an incorrectly formatted postal code.

 

You could also do things like set a country code from an associative array when a country gets set, or create a white-list of allowed values and throw an exception if the value is not contained.

 

They are also used for creating storage objects, like so:

 

class Storage { // this would be better as a trait again
  protected $_data = array();
 
  public function __get($key) {
    if ( !isset($this->_data[$key]) ) return false;
    return $this->_data[$key];
  }
  public function __set($key, $value) {
    $this->_data[$key] = $value;
  }
}

 

It is actually better to write the first example in a style similar to this, it gives us control over which properties can be accessed.

 

abstract class FilterableProperties { // this would be better as a trait   protected $_data = array();   public function __set($key, $value) {    if ( !isset($this->_data[$key]) )      return null;    $beforeFilter = "_before_set_{$key}";    if ( method_exists($this, $beforeFilter) ) {      $this->_data[$key] = call_user_func(array($this, $beforeFilter), $value);    } else {      $this->_data[$key] = $value;    }  }  public function __get($name) {    return isset($this->_data[$key]) ? $this->_data[$key] : null;  }}class Address extends FilterableProperties {  protected $_data = array('property_name_or_number', 'street', 'town', 'city', 'county', 'postcode', 'country');  // now these properties can not be accessed and are actually protected  protected $_country_code  = null;  protected $_country_codes = array(    'United Kingdom' => 'UK',    'United States'  => 'USA',    'France'         => 'FR',    'Germany'        => 'GER'    // ...  );   protected function _before_set_postcode($newPostcode) {    $newPostcode = strtoupper(preg_replace('/\s+/', '', $newPostcode));    if ( !preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $newPostcode) &&      !preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $newPostcode) )       throw new Exception('Invalid postcode');    return substr($newPostcode, 0, -3) .' '. substr($newPostcode, -3);  }  protected function _before_set_country($newCountry) {    if ( !in_array($newCountry, array_keys($this->_country_codes)) )      throw new Exception('Invalid country');    $this->_country_code = $this->_country_codes[$newCountry];    return $newCountry;  }}
 
Hope that helps.
Link to comment
Share on other sites

__get()  and __set() magic methods provide a means of manipulating object properties at run-time that are not predefined members of the object's class.

 

Edit: There are also __unset() and __isset() magic methods that can also be defined to handle unset() and isset() calls on non-predefined properties.

<?php

     class myClass {
          public $aPredefinedProperty;
          private $arrayForNonPredefinedProperties = array();

          // __set will be called when we attempt to assign a value to non predefined property.
          function __set($name, $value){          
               $this->arrayForNonPredefinedProperties[$name] = $value;
          }
          // __get will be called when we attempt to access a non predefined property
          function __get($name){
                return (isset($this->arrayForNonPredefinedProperties[$name])) ? $this->arrayForNonPredefinedProperties[$name] : null;
          }

     }


     $objInstance = new myClass();

     $objInstance->aPredefinedProperty = 'a value'; # __set is not called, property is predefined
     echo $objInstance->aPredefinedProperty; # __get is not called, property is predefined

     $objInstance->magicTime = 'woo. thank goodness we have __set'; # __set is called, property is not predefined
     echo $objInstance->magicTime . ' and __get magic methods in our class'; # __get is called, property is not predefined

Edited by objnoob
Link to comment
Share on other sites

Actually __get() can be used to simulate a 'readonly' property in C#, although it wont be as powerful. One drawback of magic methods is that they are extraordinarily slow compared to other methods, you only use them unless you have a very good reason. For me, I've only found __call() and __toString() to be practically useful, but of course it may depend on your own application.

 

I see that you like to ask OOP related questions, you can add me to your contact if you want to learn OOP and wish to know more about it. I'd always love to help out with OO enthusiasts.

Link to comment
Share on other sites

Sorry, just tested those examples, fixed a mistake:

 

<?phpabstract class FilterableProperties {   protected $_data = array();   public function __set($key, $value) {    if ( !in_array($key, array_keys($this->_data)) )      return null;    $beforeFilter = "_before_set_{$key}";    if ( method_exists($this, $beforeFilter) ) {      $this->_data[$key] = call_user_func(array($this, $beforeFilter), $value);    } else {      $this->_data[$key] = $value;    }  }  public function __get($key) {    return isset($this->_data[$key]) ? $this->_data[$key] : null;  }}class Address extends FilterableProperties {  protected $_data = array('property_name_or_number', 'street', 'town', 'city', 'county', 'postcode', 'country');  // now these properties can not be accessed and are actually protected  protected $_country_code  = null;  protected $_country_codes = array(    'United Kingdom' => 'UK',    'United States'  => 'USA',    'France'         => 'FR',    'Germany'        => 'GER'    // ...  );   protected function _before_set_postcode($newPostcode) {    $newPostcode = strtoupper(preg_replace('/\s+/', '', $newPostcode));    if ( !preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $newPostcode) &&      !preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $newPostcode) )       throw new Exception('Invalid postcode');    return substr($newPostcode, 0, -3) .' '. substr($newPostcode, -3);  }  protected function _before_set_country($newCountry) {    if ( !in_array($newCountry, array_keys($this->_country_codes)) )      throw new Exception('Invalid country');    $this->_country_code = $this->_country_codes[$newCountry];    return $newCountry;  }} $Address = new Address();$Address->postcode = 'S K 1 51 T W';var_dump($Address->postcode); // SK15 1TW

 
Still, I guess one mistake in untested code at 00:30 isn't too bad lol
Link to comment
Share on other sites

That is 100% wrong. Either you transcribed it from the tutorial incorrectly, or the tutorial is wrong. The string 'postal_code' will never equal the string 'name'. 

 

Unset() does not work with __get/__set, so either they do that to highlight the fact that it doesn't work, or it is a mistake.

 

As for the other two questions, fix the class and re-try.

Thanks  Kicken. I will revist my class, and read more about __get() and __set()

Link to comment
Share on other sites

 

__get and __set are "magic" hook methods, they are called whenever a property is accessed on an instance of a class, and whenever a property is assigned a value on an instance of a class that implement them respectively.

 

They can be used to control how a property is inserted into or retrieved from an object, I.e. they allow you to run post and/or pre-processing tasks to control the data that the object contains. Take the Address example:

 

abstract class FilterableProperties { // this would be better as a trait  public function __get($name) {    $property     = $this->_get_property($name);    $beforeFilter = "_before_get_{$name}";    if ( method_exists($this, $beforeFilter) )      return call_user_func(array($this, $beforeFilter), $this->property);    return $this->$property;  }  public function __set($name, $value) {    $property     = $this->_get_property($name);    $beforeFilter = "_before_set_{$name}";    if ( method_exists($this, $beforeFilter) ) {      $this->$property = call_user_func(array($this, $beforeFilter), $value);    } else {      $this->$property = $value;    }  }  protected function _get_property($name) {    $property = "_{$name}";    if ( !property_exists($this, $property) )      throw new Exception(sprintf('Property %s does not exist on %s', $name, get_class($this)));    return $property;  }}class Address extends FilterableProperties {  protected $_property_name_or_number = null;  protected $_street = null;  protected $_town = null;  protected $_city = null;  protected $_county = null;  protected $_postcode = null;  protected $_country = null;  protected $_country_code = null;   protected function _before_set_postcode($newPostcode) {    $newPostcode = strtoupper(preg_replace('/\s+/', '', $newPostcode));    if ( !preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $newPostcode) &&      !preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $newPostcode) ) {      throw new Exception('Invalid postcode');    } else {      return substr($newPostcode, 0, -3) .' '. substr($newPostcode, -3);    }  }}

 

 

Now you are prevented from updating an instance to contain an incorrectly formatted postal code.

 

You could also do things like set a country code from an associative array when a country gets set, or create a white-list of allowed values and throw an exception if the value is not contained.

 

They are also used for creating storage objects, like so:

 

class Storage { // this would be better as a trait again
  protected $_data = array();
 
  public function __get($key) {
    if ( !isset($this->_data[$key]) ) return false;
    return $this->_data[$key];
  }
  public function __set($key, $value) {
    $this->_data[$key] = $value;
  }
}

 

It is actually better to write the first example in a style similar to this, it gives us control over which properties can be accessed.

 

abstract class FilterableProperties { // this would be better as a trait   protected $_data = array();   public function __set($key, $value) {    if ( !isset($this->_data[$key]) )      return null;    $beforeFilter = "_before_set_{$key}";    if ( method_exists($this, $beforeFilter) ) {      $this->_data[$key] = call_user_func(array($this, $beforeFilter), $value);    } else {      $this->_data[$key] = $value;    }  }  public function __get($name) {    return isset($this->_data[$key]) ? $this->_data[$key] : null;  }}class Address extends FilterableProperties {  protected $_data = array('property_name_or_number', 'street', 'town', 'city', 'county', 'postcode', 'country');  // now these properties can not be accessed and are actually protected  protected $_country_code  = null;  protected $_country_codes = array(    'United Kingdom' => 'UK',    'United States'  => 'USA',    'France'         => 'FR',    'Germany'        => 'GER'    // ...  );   protected function _before_set_postcode($newPostcode) {    $newPostcode = strtoupper(preg_replace('/\s+/', '', $newPostcode));    if ( !preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $newPostcode) &&      !preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $newPostcode) )       throw new Exception('Invalid postcode');    return substr($newPostcode, 0, -3) .' '. substr($newPostcode, -3);  }  protected function _before_set_country($newCountry) {    if ( !in_array($newCountry, array_keys($this->_country_codes)) )      throw new Exception('Invalid country');    $this->_country_code = $this->_country_codes[$newCountry];    return $newCountry;  }}
 
Hope that helps.

 

 

 

Thank you very much for that info. It did help me a lot. Just had to go over it couple times. :happy-04:

Link to comment
Share on other sites

Actually __get() can be used to simulate a 'readonly' property in C#, although it wont be as powerful. One drawback of magic methods is that they are extraordinarily slow compared to other methods, you only use them unless you have a very good reason. For me, I've only found __call() and __toString() to be practically useful, but of course it may depend on your own application.

 

I see that you like to ask OOP related questions, you can add me to your contact if you want to learn OOP and wish to know more about it. I'd always love to help out with OO enthusiasts.

Yes indeed!! I love working with OOP and I am just trying to learn every drip of it. I will add you now. We should def talk more about it!!!

Link to comment
Share on other sites

Yes indeed!! I love working with OOP and I am just trying to learn every drip of it. I will add you now. We should def talk more about it!!!

 

Great, glad to have you on my contact. The first motto you should keep in mind for an object oriented programmer is that 'in a perfect script everything is an object'. Its impossible to write purely object oriented code(especially on PHP due to the limitation in the language itself), but you can always make your program more object oriented with a little bit more work. More object oriented code = More professional code. Just like in energy industry you can never design an 100% efficient system, but you can always improve its efficienty beyond what has already been achieved. Does this analogy make sense?

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.