PHPのお勉強!

PHP TOP

オーバーロード

PHP におけるオーバーロード機能は、 プロパティやメソッドを動的に 作成する ための手法です。 これらの動的エンティティは、マジックメソッドを用いて処理されます。 マジックメソッドは、クラス内でさまざまなアクションに対して用意することができます。

オーバーロードメソッドが起動するのは、 宣言されていないプロパティやメソッドを操作しようとしたときです。 また、現在のスコープからは アクセス不能な プロパティやメソッドを操作しようとしたときにも起動します。 このセクションでは、これらの (宣言されていない、 あるいは現在のスコープからはアクセス不能な) プロパティやメソッドのことを アクセス不能プロパティ および アクセス不能メソッド と表記することにします。

オーバーロードメソッドは、すべて public で定義しなければなりません。

注意:

これらのマジックメソッドの引数は、 参照渡し とすることはできません。

注意:

PHP における オーバーロード の解釈は、他の多くのオブジェクト指向言語とは異なります。 一般的に「オーバーロード」とは、 「名前は同じだけれども引数の数や型が異なるメソッドを複数用意できる」 という機能のことを指します。

変更履歴

バージョン 説明
5.3.0 __callStatic() が追加されました。 public で、かつ static でない宣言を強制するような警告が追加されました。
5.1.0 __isset()__unset() が追加されました。 __get() が private プロパティのオーバーロードに対応しました。
5.0.0 __get() が追加されました。

プロパティのオーバーロード

public __set ( string $name , mixed $value ) : void
public __get ( string $name ) : mixed
public __isset ( string $name ) : bool
public __unset ( string $name ) : void

__set() は、 アクセス不能(protected または private)または存在しないプロパティへデータを書き込む際に実行されます。

__get() は、 アクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用します。

__isset() は、 isset() あるいは empty() をアクセス不能(protected または private)または存在しないプロパティに対して実行したときに起動します。

__unset() は、 unset() をアクセス不能(protected または private)または存在しないプロパティに対して実行したときに起動します。

引数 $name は、 操作しようとしたプロパティの名前です。 __set() メソッドの引数 $value は、 $name に設定しようとした値となります。

プロパティのオーバーロードはオブジェクトのコンテキストでのみ動作します。 これらのマジックメソッドは、静的コンテキストでは起動しません。 したがって、これらのメソッドを static 宣言してはいけません。 PHP 5.3.0 以降、マジックオーバーロードメソッドを static 宣言すると警告が発生します。

注意:

__set() の返り値は無視されます。 これは、PHP が代入演算子を処理する方法によるものです。 同様に __get() は、

 $a = $obj->b = 8; 
のように代入と連結した場合には決してコールされません。

例1 __get()__set()__isset() および __unset() メソッドを使ったプロパティのオーバーロードの例

<?php
class PropertyTest
{
    
/**  オーバーロードされるデータの場所  */
    
private $data = array();

    
/**  宣言されているプロパティにはオーバーロードは起動しません */
    
public $declared 1;

    
/**  クラスの外部からアクセスした場合にのみこれがオーバーロードされます  */
    
private $hidden 2;

    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;
    }

    
/**  PHP 5.1.0 以降 */
    
public function __isset($name)
    {
        echo 
"Is '$name' set?\n";
        return isset(
$this->data[$name]);
    }

    
/**  PHP 5.1.0 以降 */
    
public function __unset($name)
    {
        echo 
"Unsetting '$name'\n";
        unset(
$this->data[$name]);
    }

    
/**  マジックメソッドではありません。単なる例として示しています  */
    
public function getHidden()
    {
        return 
$this->hidden;
    }
}


echo 
"<pre>\n";

$obj = new PropertyTest;

$obj->1;
echo 
$obj->"\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";
?>

上の例の出力は以下となります。

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 <file> on line 70 in <file> on line 29

メソッドのオーバーロード

public __call ( string $name , array $arguments ) : mixed
public static __callStatic ( string $name , array $arguments ) : mixed

__call() は、 アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動します。

__callStatic() は、 アクセス不能メソッドを静的コンテキストで実行したときに起動します。

引数 $name は、 コールしようとしたメソッドの名前です。 引数 $arguments は配列で、メソッド $name に渡そうとしたパラメータが格納されます。

例2 __call() および __callStatic() メソッドによる、メソッドのオーバーロードの例

<?php
class MethodTest
{
    public function 
__call($name$arguments)
    {
        
// 注意: $name は大文字小文字を区別します
        
echo "Calling object method '$name' "
             
implode(', '$arguments). "\n";
    }

    
/**  PHP 5.3.0 以降 */
    
public static function __callStatic($name$arguments)
    {
        
// 注意: $name は大文字小文字を区別します
        
echo "Calling static method '$name' "
             
implode(', '$arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');

MethodTest::runTest('in static context');  // PHP 5.3.0 以降
?>

上の例の出力は以下となります。

Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
add a note add a note

User Contributed Notes 36 notes

up
1490
php_is_painful at world dot real
12 years ago
This is a misuse of the term overloading. This article should call this technique "interpreter hooks".
up
256
theaceofthespade at gmail dot com
8 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.
up
162
Anonymous
4 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.

<?php

class BadPractice {
 
// Two real properties
 
public $DontAllowVariableNameWithTypos = true;
  protected
$Number = 0;
 
// One private method
 
private function veryPrivateMethod() { }
 
// And three very magic methods that will make everything look inconsistent
  // with all you have ever learned about PHP.
 
public function __get($n) {}
  public function
__set($n, $v) {}
  public function
__call($n, $v) {}
}

// Let's see our BadPractice in a production environment!
$UnexpectedBehaviour = new BadPractice;

// No syntax highlighting on most IDE's
$UnexpectedBehaviour->SynTaxHighlighting = false;

// No autocompletion on most IDE's
$UnexpectedBehaviour->AutoCompletion = false;

// Which will lead to problems waiting to happen
$UnexpectedBehaviour->DontAllowVariableNameWithTyphos = false; // see if below

// 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);
}
?>
up
8
gabe at fijiwebdesign dot com
5 years ago
Note that you can enable "overloading" on a class instance at runtime for an existing property by unset()ing that property.

eg:

<?php
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().

<?php
$Test
= new Test();
unset(
$Test->property1); // enable overloading
echo $Test->property1; // Get called for Test->$property1
?>

Useful if you want to proxy or lazy load properties yet want to have documentation and visibility in the code and debugging compared to __get(), __isset(), __set() on non-existent inaccessible properties.
up
63
pogregoire##live.fr
4 years ago
It is important to understand that encapsulation can be very easily violated in PHP. for example :
class Object{

}

$Object = new Object();
$Objet->barbarianProperties  = 'boom';

var_dump($Objet);// object(Objet)#1 (1) { ["barbarianProperties"]=> string(7) "boom" }

Hence it is possible to add a propertie out form the class definition.
It is then a necessity in order to protect encapsulation to introduce __set() in the class :

class Objet{
    public function __set($name,$value){
        throw new Exception ('no');
    }
}
up
102
egingell at sisna dot com
13 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:
<?php

function foo($a) {
    return
$a;
}

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:
<?php

class foo {
    function new(
$args) {
       
// Do something.
   
}
}

class
bar extends foo {
    function new(
$args) {
       
// Do something different.
   
}
}

?>
up
49
Anonymous
5 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.

/**
* @property-read name
* @property-read price
*/
class MyClass
{
    private $properties = array('name' => 'IceFruit', 'price' => 2.49)
   
    public function __get($name)
    {
        return $this->properties($name);
    }
}
up
12
Ant P.
11 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.
In versions before 5.3 without __callStatic, static calls to nonexistent functions also fall through to __call!
This caused me hours of confusion, hopefully this comment will save someone else from the same.
up
8
NOTE: getter cannot call getter
15 years ago
By Design (http://bugs.php.net/bug.php?id=33998) you cannot call a getter from a getter or any function triggered by a getter:

<?php
class test
{
    protected
$_a = 6;

    function
__get($key) {
        if(
$key == 'stuff') {
            return
$this->stuff();
        } else if(
$key == 'a') {
            return
$this->_a;
        }
    }

    function
stuff()
    {
        return array(
'random' => 'key', 'using_getter' => 10 * $this->a);
    }
}

$test = new test();
print
'this should be 60: '.$test->stuff['using_getter'].'<br/>';       // prints "this should be 60: 0"
// [[ Undefined property:  test::$a ]] on /var/www/html/test.php logged.
print 'this should be 6: '.$test->a.'<br/>';                            // prints "this should be 6: 6"
?>
up
9
PHP at jyopp dotKomm
14 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.
<?php
class MethodCallLog {
    private
$callLog = array();
    private
$object;
   
    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);
?>
up
1
justmyoponion at gmail dot com
10 months ago
If you are not focused enough, then don't use it.
Otherwise it is very powerful and you can build very complex code that handle a lot of things like zend framework did.
up
23
navarr at gtaero dot net
10 years ago
If you want to make it work more naturally for arrays $obj->variable[] etc you'll need to return __get by reference.

<?php
class Variables
{
        public function
__construct()
        {
                if(
session_id() === "")
                {
                       
session_start();
                }
        }
        public function
__set($name,$value)
        {
               
$_SESSION["Variables"][$name] = $value;
        }
        public function &
__get($name)
        {
                return
$_SESSION["Variables"][$name];
        }
        public function
__isset($name)
        {
                return isset(
$_SESSION["Variables"][$name]);
        }
}
?>
up
18
jan dot machala at email dot cz
9 years ago
Example of usage __call() to have implicit getters and setters

<?php
class Entity {
    public function
__call($methodName, $args) {
        if (
preg_match('~^(set|get)([A-Z])(.*)$~', $methodName, $matches)) {
           
$property = strtolower($matches[2]) . $matches[3];
            if (!
property_exists($this, $property)) {
                throw new
MemberAccessException('Property ' . $property . ' not exists');
            }
            switch(
$matches[1]) {
                case
'set':
                   
$this->checkArguments($args, 1, 1, $methodName);
                    return
$this->set($property, $args[0]);
                case
'get':
                   
$this->checkArguments($args, 0, 0, $methodName);
                    return
$this->get($property);
                case
'default':
                    throw new
MemberAccessException('Method ' . $methodName . ' not exists');
            }
        }
    }

    public function
get($property) {
        return
$this->$property;
    }

    public function
set($property, $value) {
       
$this->$property = $value;
        return
$this;
    }

    protected function
checkArguments(array $args, $min, $max, $methodName) {
       
$argc = count($args);
        if (
$argc < $min || $argc > $max) {
            throw new
MemberAccessException('Method ' . $methodName . ' needs minimaly ' . $min . ' and maximaly ' . $max . ' arguments. ' . $argc . ' arguments given.');
        }
    }
}

class
MemberAccessException extends Exception{}

class
Foo extends Entity {
    protected
$a;
}

$foo = new Foo();
$foo->setA('some'); // outputs some
echo $foo->getA();

class
Bar extends Entity {
    protected
$a;
   
/**
     * Custom setter.
     */
   
public function setA($a) {
        if (!
preg_match('~^[0-9a-z]+$~i', $a)) {
            throw new
MemberAccessException('A can be only alphanumerical');
        }
       
$this->a = $a;
        return
$this;
    }
}

$bar = new Bar();
$bar->setA('abc123'); // ok
$bar->setA('[]/*@...'); // throws exception
?>
up
8
php at lanar dot com dot au
10 years ago
Note that __isset is not called on chained checks.
If isset( $x->a->b ) is executed where $x is a class with __isset() declared, __isset() is not called.

<?php

class demo
{
    var
$id ;
    function
__construct( $id = 'who knows' )
    {
       
$this->id = $id ;
    }
    function
__get( $prop )
    {
        echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
        return new
demo( 'autocreated' ) ; // return a class anyway for the demo
   
}
    function
__isset( $prop )
    {
        echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') instance ', $this->id ;
        return
FALSE ;
    }
}
$x = new demo( 'demo' ) ;
echo
"\n", 'Calls __isset() on demo as expected when executing isset( $x->a )' ;
$ret = isset( $x->a ) ;
echo
"\n", 'Calls __get() on demo without call to __isset()  when executing isset( $x->a->b )' ;
$ret = isset( $x->a->b ) ;
?>

Outputs

Calls __isset() on demo as expected when executing isset( $x->a )
C:\htdocs\test.php:31 demo::__isset(a) instance demo
Calls __get() on demo without call to __isset()  when executing isset( $x->a->b )
C:\htdocs\test.php:26 demo::__get(a) instance demo
C:\htdocs\test.php:31 demo::__isset(b) instance autocreated
up
11
alexandre at nospam dot gaigalas dot net
13 years ago
PHP 5.2.1

Its possible to call magic methods with invalid names using variable method/property names:

<?php

class foo
{
    function
__get($n)
    {
       
print_r($n);
    }
    function
__call($m, $a)
    {
       
print_r($m);
    }
}

$test = new foo;
$varname = 'invalid,variable+name';
$test->$varname;
$test->$varname();

?>

I just don't know if it is a bug or a feature :)
up
5
jstubbs at work-at dot co dot jp
14 years ago
<?php $myclass->foo['bar'] = 'baz'; ?>

When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to:

<?php
$tmp_array
= &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset(
$tmp_array);
?>

Therefore, the above won't do anything if your __get implementation resembles this:

<?php
function __get($name) {
    return
array_key_exists($name, $this->values)
        ?
$this->values[$name] : null;
}
?>

You will actually need to set the value in __get and return that, as in the following code:

<?php
function __get($name) {
    if (!
array_key_exists($name, $this->values))
       
$this->values[$name] = null;
    return
$this->values[$name];
}
?>
up
3
timshaw at mail dot NOSPAMusa dot com
12 years ago
The __get overload method will be called on a declared public member of an object if that member has been unset.

<?php
class c {
  public
$p ;
  public function
__get($name) { return "__get of $name" ; }
}

$c = new c ;
echo
$c->p, "\n" ;    // declared public member value is empty
$c->p = 5 ;
echo
$c->p, "\n" ;    // declared public member value is 5
unset($c->p) ;
echo
$c->p, "\n" ;    // after unset, value is "__get of p"
?>
up
5
derek-php at seysol dot com
14 years ago
Please note that PHP5 currently doesn't support __call return-by-reference (see PHP Bug #30959).

Example Code:

<?php

   
class test {
        public function &
__call($method, $params) {

           
// Return a reference to var2
           
return $GLOBALS['var2'];
        }
        public function &
actual() {

           
// Return a reference to var1
           
return $GLOBALS['var1'];
        }
    }

   
$obj = new test;
   
$GLOBALS['var1'] = 0;
   
$GLOBALS['var2'] = 0;

   
$ref1 =& $obj->actual();
   
$GLOBALS['var1']++;

    echo
"Actual function returns: $ref1 which should be equal to " . $GLOBALS['var1'] . "<br/>\n";

   
$ref2 =& $obj->overloaded();
   
$GLOBALS['var2']++;

    echo
"Overloaded function returns: $ref2 which should be equal to " . $GLOBALS['var2'] . "<br/>\n";

?>
up
6
Adeel Khan
13 years ago
Observe:

<?php
class Foo {
    function
__call($m, $a) {
        die(
$m);
    }
}

$foo = new Foo;
print
$foo->{'wow!'}();

// outputs 'wow!'
?>

This method allows you to call functions with invalid characters.