Hướng dẫn dùng método trong PHP
Overloading in PHP provides means to dynamically create properties and methods. These dynamic entities are processed via magic methods one can establish in a class for various action types. The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope. The rest of this section will use the terms inaccessible properties and inaccessible methods to refer to this combination of declaration and visibility. All overloading methods must be defined as public.
Property overloadingpublic __set(string $name, mixed $value): void public __get(string $name): mixed public __isset(string $name): bool public __unset(string $name): void __set() is run when writing data to inaccessible (protected or private) or non-existing properties. __get() is utilized for reading data from inaccessible (protected or private) or non-existing properties. __isset() is triggered by calling isset() or empty() on inaccessible (protected or private) or non-existing properties. __unset() is invoked when unset() is used on inaccessible (protected or private) or non-existing properties. The $name argument is the name of the property being interacted with. The __set() method's $value argument specifies the value the $name'ed property should be set to. Property overloading only works in object context. These magic methods will not be triggered in static context. Therefore these methods should not be declared static. A warning is issued if one of the magic overloading methods is declared static.
Example #1 Overloading properties via the __get(), __set(), __isset() and __unset() methods
class PropertyTest public function __set($name, $value){ echo "Setting '$name' to '$value'\n"; $this->data[$name] = $value; } public function __get($name){ echo "Getting '$name'\n"; if (array_key_exists($name, $this->data)) { return $this->data[$name]; }$trace = debug_backtrace(); trigger_error( 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } public function __isset($name){ echo "Is '$name' set?\n"; return isset($this->data[$name]); } public function __unset($name){ echo "Unsetting '$name'\n"; unset($this->data[$name]); }/** Not a magic method, just here for example. */ public function getHidden() { return $this->hidden; } } echo "\n";$obj = new PropertyTest;$obj->a = 1; echo $obj->a . "\n\n";var_dump(isset($obj->a)); unset($obj->a); var_dump(isset($obj->a)); echo "\n"; echo $obj->declared . "\n\n";echo "Let's experiment with the private property named 'hidden':\n";echo "Privates are visible inside the class, so __get() not used...\n"; echo $obj->getHidden() . "\n"; echo "Privates not visible outside of class, so __get() is used...\n"; echo $obj->hidden . "\n"; ?> The above example will output: Setting 'a' to '1'
Getting 'a'
1
Is 'a' set?
bool(true)
Unsetting 'a'
Is 'a' set?
bool(false)
1
Let's experiment with the private property named 'hidden':
Privates are visible inside the class, so __get() not used...
2
Privates not visible outside of class, so __get() is used...
Getting 'hidden'
Notice: Undefined property via __get(): hidden in Method overloadingpublic __call(string $name, array $arguments): mixed public static __callStatic(string $name, array $arguments): mixed __call() is triggered when invoking inaccessible methods in an object context. __callStatic() is triggered when invoking inaccessible methods in a static context. The $name argument is the name of the method being called. The $arguments argument is an enumerated array containing the parameters passed to the $name'ed method. Example #2 Overloading methods via the __call() and __callStatic() methods
class MethodTest public static function __callStatic($name, $arguments){ // Note: value of $name is case sensitive. echo "Calling static method '$name' " . implode(', ', $arguments). "\n"; } }$obj = new MethodTest; $obj->runTest('in object context');MethodTest::runTest('in static context'); ?> The above example will output: Calling object method 'runTest' in object context Calling static method 'runTest' in static context theaceofthespade at gmail dot com ¶ 10 years ago A word of warning! It may seem obvious, but remember, when deciding whether to use __get, __set, and __call as a way to access the data in your class (as opposed to hard-coding getters and setters), keep in mind that this will prevent any sort of autocomplete, highlighting, or documentation that your ide mite do. Furthermore, it beyond personal preference when working with other people. Even without an ide, it can be much easier to go through and look at hardcoded member and method definitions in code, than having to sift through code and piece together the method/member names that are assembled in __get and __set. If you still decide to use __get and __set for everything in your class, be sure to include detailed comments and documenting, so that the people you are working with (or the people who inherit the code from you at a later date) don't have to waste time interpreting your code just to be able to use it. Anonymous ¶ 6 years ago First off all, if you read this, please upvote the first comment on this list that states that “overloading” is a bad term for this behaviour. Because it REALLY is a bad name. You’re giving new definition to an already accepted IT-branch terminology. Second, I concur with all criticism you will read about this functionality. Just as naming it “overloading”, the functionality is also very bad practice. Please don’t use this in a production environment. To be honest, avoid to use it at all. Especially if you are a beginner at PHP. It can make your code react very unexpectedly. In which case you MIGHT be learning invalid coding! And last, because of __get, __set and __call the following code executes. Which is abnormal behaviour. And can cause a lot of problems/bugs. class BadPractice { // Get, Set and Call anything you want! $UnexpectedBehaviour->EveryPosibleMethodCallAllowed(true, 'Why Not?');// And sure, why not use the most illegal property names you can think off$UnexpectedBehaviour->{'100%Illegal+Names'} = 'allowed';// This Very confusing syntax seems to allow access to $Number but because of // the lowered visibility it goes to __set() $UnexpectedBehaviour->Number = 10;// We can SEEM to increment it too! (that's really dynamic! :-) NULL++ LMAO $UnexpectedBehaviour->Number++;// this ofcourse outputs NULL (through __get) and not the PERHAPS expected 11 var_dump($UnexpectedBehaviour->Number);// and sure, private method calls LOOK valid now! // (this goes to __call, so no fatal error) $UnexpectedBehaviour->veryPrivateMethod();// Because the previous was __set to false, next expression is true // if we didn't had __set, the previous assignment would have failed // then you would have corrected the typho and this code will not have // been executed. (This can really be a BIG PAIN) if ($UnexpectedBehaviour->DontAllowVariableNameWithTypos) { // if this code block would have deleted a file, or do a deletion on // a database, you could really be VERY SAD for a long time! $UnexpectedBehaviour->executeStuffYouDontWantHere(true); } ?> egingell at sisna dot com ¶ 14 years ago Small vocabulary note: This is *not* "overloading", this is "overriding". Overloading: Declaring a function multiple times with a different set of parameters like this: function foo($a, $b) {return $a + $b; } echo foo(5); // Prints "5"echo foo(5, 2); // Prints "7"?> Overriding: Replacing the parent class's method(s) with a new method by redeclaring it like this: class foo { function new($args) { // Do something. } } class bar extends foo {function new($args) { // Do something different. } }?> pogregoire##live.fr ¶ 5 years ago
It is important to understand that encapsulation can be very easily violated in PHP. for example : } $Object = new Object(); var_dump($Objet);// object(Objet)#1 (1) { ["barbarianProperties"]=> string(7) "boom" } Hence it is possible to add a propertie out form the class definition. class Objet{ Anonymous ¶ 7 years ago Using magic methods, especially __get(), __set(), and __call() will effectively disable autocomplete in most IDEs (eg.: IntelliSense) for the affected classes. To overcome this inconvenience, use phpDoc to let the IDE know about these magic methods and properties: @method, @property, @property-read, @property-write. /** public function __get($name) cottton at i-stats dot net ¶ 7 years ago
Actually you dont need __set ect imo. Ant P. ¶ 13 years ago
Be extra careful when using __call(): if you typo a function call somewhere it won't trigger an undefined function error, but get passed to __call() instead, possibly causing all sorts of bizarre side effects. gabe at fijiwebdesign dot com ¶ 7 years ago Note that you can enable "overloading" on a class instance at runtime for an existing property by unset()ing that property. eg: class Test { public $property1;public function __get($name){ return "Get called for " . get_class($this) . "->\$$name \n"; } } ?>The public property $property1 can be unset() so that it can be dynamically handled via __get(). $Test = new Test(); navarr at gtaero dot net ¶ 12 years ago If you want to make it work more naturally for arrays $obj->variable[] etc you'll need to return __get by reference.
class Variables
jstubbs at work-at dot co dot jp ¶ 15 years ago
->foo['bar'] = 'baz'; ?>
$tmp_array = &$myclass->foo;
function __get($name) {
function __get($name) {
justmyoponion at gmail dot com ¶ 2 years ago
If you are not focused enough, then don't use it. PHP at jyopp dotKomm ¶ 16 years ago Here's a useful class for logging function calls. It stores a sequence of calls and arguments which can then be applied to objects later. This can be used to script common sequences of operations, or to make "pluggable" operation sequences in header files that can be replayed on objects later. If it is instantiated with an object to shadow, it behaves as a mediator and executes the calls on this object as they come in, passing back the values from the execution. This is a very general implementation; it should be changed if error codes or exceptions need to be handled during the Replay process. public function __construct($object = null) {$this->object = $object; } public function __call($m, $a) { $this->callLog[] = array($m, $a); if ($this->object) return call_user_func_array(array(&$this->object,$m),$a); return true; } public function Replay(&$object) { foreach ($this->callLog as $c) { call_user_func_array(array(&$object,$c[0]), $c[1]); } } public function GetEntries() { $rVal = array(); foreach ($this->callLog as $c) { $rVal[] = "$c[0](".implode(', ', $c[1]).");"; } return $rVal; } public function Clear() { $this->callLog = array(); } }$log = new MethodCallLog(); $log->Method1(); $log->Method2("Value"); $log->Method1($a, $b, $c); // Execute these method calls on a set of objects... foreach ($array as $o) $log->Replay($o); ?> php at lanar dot com dot au ¶ 12 years ago
Note that __isset is not called on chained checks. class demo Calls __isset() on demo as expected when executing isset( $x->a ) matthijs at yourmediafactory dot com ¶ 14 years ago While PHP does not support true overloading natively, I have to disagree with those that state this can't be achieved trough __call. Yes, it's not pretty but it is definately possible to overload a member based on the type of its argument. An example: public function __call ($member, $arguments) {if(is_object($arguments[0])) $member = $member . 'Object'; if(is_array($arguments[0])) $member = $member . 'Array'; $this -> $member($arguments); } private function testArray () {echo "Array."; } private function testObject () {echo "Object."; } } class B {} $class = new A; $class -> test(array()); // echo's 'Array.' $class -> test(new B); // echo's 'Object.' ?> Of course, the use of this is questionable (I have never needed it myself, but then again, I only have a very minimalistic C++ & JAVA background). However, using this general principle and optionally building forth on other suggestions a 'form' of overloading is definately possible, provided you have some strict naming conventions in your functions. It would of course become a LOT easier once PHP'd let you declare the same member several times but with different arguments, since if you combine that with the reflection class 'real' overloading comes into the grasp of a good OO programmer. Lets keep our fingers crossed! alexandre at nospam dot gaigalas dot net ¶ 15 years ago PHP 5.2.1 Its possible to call magic methods with invalid names using variable method/property names: class foo timshaw at mail dot NOSPAMusa dot com ¶ 14 years ago The __get overload method will be called on a declared public member of an object if that member has been unset. class c { Nanhe Kumar ¶ 8 years ago
//How can implement __call function you understand better protected $_name;protected $_email; protected $_compony; public function __call($name, $arguments) {$action = substr($name, 0, 3); switch ($action) { case 'get': $property = '_' . strtolower(substr($name, 3)); if(property_exists($this,$property)){ return $this->{$property}; }else{ $trace = debug_backtrace(); trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } break; case 'set': $property = '_' . strtolower(substr($name, 3)); if(property_exists($this,$property)){ $this->{$property} = $arguments[0]; }else{ $trace = debug_backtrace(); trigger_error('Undefined property ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } break; } } } $s = new Employee();$s->setName('Nanhe Kumar'); $s->setEmail(''); echo $s->getName(); //Nanhe Kumar echo $s->getEmail(); // $s->setAge(10); //Notice: Undefined property setAge in ?> Adeel Khan ¶ 15 years ago Observe: class Foo { daevid at daevid dot com ¶ 13 years ago Here's a handy little routine to suggest properties you're trying to set that don't exist. For example: Attempted to __get() non-existant property/variable 'operator_id' in class 'User'. checking for operator and suggesting the following:
* id_operator
enjoy.
/**
echo 'checking for '.implode(', ',$parts)." and suggesting the following:\n"; echo "
foreach($this as $key => $value) foreach($parts as $p) if (stripos($key, $p) !== false) print ' echo ""; } just put it in your __get() or __set() like so: public function __get($property){ echo " Attempted to __get() non-existant property/variable '" .$property."' in class '".$this->get_class_name()."'.\n" ;$this->suggest_alternative($property); exit; } ?> Daniel Smith ¶ 11 years ago Be careful of __call in case you have a protected/private method. Doing this: class TestMagicCallMethod { public function __call($method, $args){ echo __METHOD__.PHP_EOL; if(method_exists($this, $method)) { $this->$method(); } } protected function bar(){ echo __METHOD__.PHP_EOL; } private function baz(){ echo __METHOD__.PHP_EOL; } }$test = new TestMagicCallMethod(); $test->foo(); /** * Outputs: * TestMagicCallMethod::foo */$test->bar(); /** * Outputs: * TestMagicCallMethod::__call * TestMagicCallMethod::bar */$test->baz(); /** * Outputs: * TestMagicCallMethod::__call * TestMagicCallMethod::baz */ ?> ..is probably not what you should be doing. Always make sure that the methods you call in __call are allowed as you probably dont want all the private/protected methods to be accessed by a typo or something. dans at dansheps dot com ¶ 11 years ago Since this was getting me for a little bit, I figure I better pipe in here... For nested calls to private/protected variables(probably functions too) what it does is call a __get() on the first object, and if you return the nested object, it then calls a __get() on the nested object because, well it is protected as well.
EG:
public function __get($variable){ echo "Class A::Variable " . $variable . "\n\r"; $retval = $this->{$variable}; return $retval; } } class B{ protected $val public function __construct() { $this->val = 1; } public function __get($variable){ echo "Class B::Variable " . $variable . "\n\r"; $retval = $this->{$variable}; return $retval; } } $A = new A(); echo "Final Value: " . $A->B->val;?> That will return something like...
Class A::Variable B
It seperates the calls into $A->B and $B->val Hope this helps someone Marius ¶ 17 years ago
for anyone who's thinking about traversing some variable tree by the way, this seems like a bug to me, will have to report it. strata_ranger at hotmail dot com ¶ 12 years ago Combining two things noted previously: 1 - Unsetting an object member removes it from the object completely, subsequent uses of that member will be handled by magic methods. This means that if an object member has been unset(), it IS possible to re-declare that object member (as public) by creating it within your object's __set() method, like this: class Foo // $foo now contains one member var_dump($foo);// Won't call __set() because 'bar' is now declared$foo->bar = 'other thing';?> Also be mindful that if you want to break a reference involving an object member without triggering magic functionality, DO NOT unset() the object member directly. Instead use =& to bind the object member to any convenient null variable. php at sleep is the enemy dot co dot uk ¶ 15 years ago Just to reinforce and elaborate on what DevilDude at darkmaker dot com said way down there on 22-Sep-2004 07:57. The recursion detection feature can prove especially perilous when using __set. When PHP comes across a statement that would usually call __set but would lead to recursion, rather than firing off a warning or simply not executing the statement it will act as though there is no __set method defined at all. The default behaviour in this instance is to dynamically add the specified property to the object thus breaking the desired functionality of all further calls to __set or __get for that property. Example: class TestClass{ public $values = array();public function __get($name){return $this->values[$name]; } public function __set($name, $value){$this->values[$name] = $value; $this->validate($name); } public function validate($name){/* __get will be called on the following line but as soon as we attempt to call __set again PHP will refuse and simply add a property called $name to $this */ $this->$name = trim($this->$name); } }$tc = new TestClass();$tc->foo = 'bar'; $tc->values['foo'] = 'boing'; echo '$tc->foo == ' . $tc->foo . ''; echo '$tc ' . (property_exists($tc, 'foo') ? 'now has' : 'still does not have') . ' a property called "foo" ';/* OUPUTS: $tc->foo == bar $tc now has a property called "foo" */?> johannes dot kingma at gmail dot com ¶ 11 months ago One interesting use of the __get function is property / function colaescence, using the same name for a property and a function. Example: public function __get( $property ) {if( property_exists( $this, $property ) ){ return $this-> $property; } throw new Exception( "no such property $property." ); } public function prop() { return 456; } }$o = new prop_fun(); echo $o-> prop . '' . PHP_EOL; echo $o-> prop() . ' ' . PHP_EOL; ?> This will output 123 and 456. This does look like a funy cludge but I used it of a class containing a date type property and function allowing me to write class date_class { public function __get( $property ) {if( property_exists( $this, $property ) ){ return $this-> $property; } throw new Exception( "no such property $property." ); } public function the_date( $datetime ) { return strtotime( $datetime, $this-> the_date ); } public function __construct() {$this-> the_date = time(); } }$date_object = new date_class();$today = $date_object-> the_date; $nextyear = $date_object-> the_date("+1 year"); echo date( "d/m/Y", $today) . ''; echo date( "d/m/Y", $nextyear ); ?> Which I like because its self documenting properties. I used this in a utility class for user input. turabgarip at gmail dot com ¶ 1 year ago I concur that "overloading" is a wrong term for this functionality. But I disagree that this functionality is completely wrong. You can do "bad practice" with right code too. For example __call() is very well applicable to external integration implementations which I am using to relay calls to SOAP methods which doesn't need local implementation. So you don't have to write "empty body" functions. Consider the SOAP service you connect has a "stock update" method. All you have to do is passing product code and stock count to SOAP. class Inventory { public __construct() {// configure and connect to SOAP service $this->soap = new SoapClient(); } public __call($soapMethod, $params) {$this->soap->{$soapMethod}(params); } }// Now you can use any SOAP method without needing a wrapper $stock = new Inventory(); $stock->updatePrice($product_id, 20); $stock->saveProduct($product_info);?> Of course you'd need a parameter mapping but it's in my honest opinion a lot better then having a plenty of mirror methods like: class Inventory { public function updateStock($product_id, $stock) {$soapClient->updateStock($product_id, $stock; } public function updatePrice($product_id, $price) { $soapClient->updateStock($product_id, $price; } // ... }?> DevilDude at darkmaker dot com ¶ 17 years ago Php 5 has a simple recursion system that stops you from using overloading within an overloading function, this means you cannot get an overloaded variable within the __get method, or within any functions/methods called by the _get method, you can however call __get manualy within itself to do the same thing. |