eldan88 Posted September 2, 2013 Share Posted September 2, 2013 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 understand1) 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 Quote Link to comment Share on other sites More sharing options...
kicken Posted September 2, 2013 Share Posted September 2, 2013 (edited) //@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 September 2, 2013 by kicken Quote Link to comment Share on other sites More sharing options...
Andy-H Posted September 2, 2013 Share Posted September 2, 2013 __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. Quote Link to comment Share on other sites More sharing options...
objnoob Posted September 3, 2013 Share Posted September 3, 2013 (edited) __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 September 3, 2013 by objnoob Quote Link to comment Share on other sites More sharing options...
Hall of Famer Posted September 3, 2013 Share Posted September 3, 2013 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. Quote Link to comment Share on other sites More sharing options...
Andy-H Posted September 3, 2013 Share Posted September 3, 2013 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 Quote Link to comment Share on other sites More sharing options...
objnoob Posted September 3, 2013 Share Posted September 3, 2013 i never knew __get() and __set() were called when the properties exist but are not accessible. that's interesting. Quote Link to comment Share on other sites More sharing options...
eldan88 Posted September 4, 2013 Author Share Posted September 4, 2013 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() Quote Link to comment Share on other sites More sharing options...
eldan88 Posted September 4, 2013 Author Share Posted September 4, 2013 __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. Quote Link to comment Share on other sites More sharing options...
eldan88 Posted September 4, 2013 Author Share Posted September 4, 2013 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!!! Quote Link to comment Share on other sites More sharing options...
Hall of Famer Posted September 4, 2013 Share Posted September 4, 2013 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? Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.