PHPのお勉強!

PHP TOP

遅延静的束縛 (Late Static Bindings)

PHP には、遅延静的束縛と呼ばれる機能が搭載されています。 これを使用すると、静的継承のコンテキストで呼び出し元のクラスを参照できるようになります。

より正確に言うと、遅延静的束縛は直近の "非転送コール" のクラス名を保存します。 staticメソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。staticメソッド以外の場合は、そのオブジェクトのクラスとなります。 "転送コール" とは、self::parent::static:: による :: 演算子を使ったコール。 あるいはクラス階層の中での forward_static_call() によるコールのことです。 get_called_class() 関数を使うとコール元のクラス名を文字列で取得できます。 static:: はこのクラスのスコープとなります。

この "遅延静的束縛" という機能名は、内部動作を考慮してつけられたものです。 "遅延束縛 (Late binding)" の由来は、メソッドを定義しているクラス名を使用しても static:: の解決ができないことによります。 その代わりに、実行時情報をもとに解決するようになります。 "静的束縛 (static binding)" の由来は、 staticメソッドのコールに使用できることによります (ただし、staticメソッド以外でも使用可能です)。

self:: の制限

self:: あるいは __CLASS__ による現在のクラスへの静的参照は、 そのメソッドが属するクラス (つまり、 そのメソッドが定義されているクラス) に解決されます。

例1 self:: の使用例

<?php
class A {
public static function
who() {
echo
__CLASS__;
}
public static function
test() {
self::who();
}
}

class
B extends A {
public static function
who() {
echo
__CLASS__;
}
}

B::test();
?>

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

A

遅延静的束縛の使用法

遅延静的束縛は、この制限を解決するためのキーワードを導入し、 実行時に最初にコールされたクラスを参照するようにしています。 このキーワードを使用すると、先ほどの例における test() から B を参照できるようになります。 このキーワードは新たに追加したものではなく、すでに予約済みである static を使用しています。

例2 static:: のシンプルな使用法

<?php
class A {
public static function
who() {
echo
__CLASS__;
}
public static function
test() {
static::
who(); // これで、遅延静的束縛が行われます
}
}

class
B extends A {
public static function
who() {
echo
__CLASS__;
}
}

B::test();
?>

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

B

注意:

静的でないコンテキストでは、呼び出されるクラスはそのオブジェクトのクラスと同じになります。 $this-> は private メソッドを同じスコープからコールしようとするので、 static:: を使うと異なる結果となります。もうひとつの相違点は、 static:: はstaticプロパティしか参照できないということです。

例3 非静的コンテキストにおける static:: の使用法

<?php
class A {
private function
foo() {
echo
"success!\n";
}
public function
test() {
$this->foo();
static::
foo();
}
}

class
B extends A {
/* foo() が B にコピーされるので、メソッドのスコープは A のままとなり、
* コールは成功します */
}

class
C extends A {
private function
foo() {
/* もとのメソッドが置き換えられるので、新しいメソッドのスコープは C となります */
}
}

$b = new B();
$b->test();
$c = new C();
$c->test(); //fails
?>

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

success!
success!
success!


Fatal error:  Call to private method C::foo() from context 'A' in /tmp/test.php on line 9

注意:

遅延静的束縛の解決は、:: を使ったコールが代替なしに完全に解決された時点で終了します。 一方、parent::self:: といったキーワードを使用するコールは、コール元の情報を転送します。

例4 転送するコールと転送しないコール

<?php
class A {
public static function
foo() {
static::
who();
}

public static function
who() {
echo
__CLASS__."\n";
}
}

class
B extends A {
public static function
test() {
A::foo();
parent::foo();
self::foo();
}

public static function
who() {
echo
__CLASS__."\n";
}
}
class
C extends B {
public static function
who() {
echo
__CLASS__."\n";
}
}

C::test();
?>

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

A
C
C

add a note

User Contributed Notes 29 notes

up
166
sergei at 2440media dot com
16 years ago
Finally we can implement some ActiveRecord methods:

<?php

class Model
{
public static function
find()
{
echo static::
$name;
}
}

class
Product extends Model
{
protected static
$name = 'Product';
}

Product::find();

?>

Output: 'Product'
up
61
mhh1422 at hotmail dot com
10 years ago
For abstract classes with static factory method, you can use the static keyword instead of self like the following:
<?php

abstract class A{

static function
create(){

//return new self(); //Fatal error: Cannot instantiate abstract class A

return new static(); //this is the correct way

}

}

class
B extends A{
}

$obj=B::create();
var_dump($obj);

?>
up
25
MelkiySoft
6 years ago
<?php

class A
{

}

class
B extends A
{
public static function
foo () {
echo
'new self: ';
var_dump(new self());
echo
'<br>new parent: ';
var_dump(new parent());
echo
'<br>new static: ';
var_dump(new static());
}
}

class
C extends B
{

}

c::foo();
===========================
output:
//new self: object(B)#1 (0) { }
//new parent: object(A)#1 (0) { }
//new static: object(C)#1 (0) { }
up
2
MikeT
2 years ago
Word of caution static::class doesn't always work as you might expect
<?php
namespace NameSpace;

class Class
{
static function
getClass()
{
return static::class;
}
}

Class::
getClass()
?>
may return \NameSpace\Class or Class depending on context
up
7
backnot
3 years ago
In the above example (#3) in order to make it work, you can change the child's method from 'private' to 'protected' (or public) and it will be called through 'static'.

<?php
class A {
private function
foo() {
echo
"success!\n";
}
public function
test() {
$this->foo();
static::
foo();
}
}

class
B extends A {
/* foo() will be copied to B, hence its scope will still be A and
* the call be successful */
}

class
C extends A {
protected function
foo() { //note the change here
echo 'hello world!';
}
}

$b = new B();
$b->test();
$c = new C();
$c->test(); // 'success' 'hello world'
?>
up
12
sskaje at gmail dot com
9 years ago
static::class and self::class can be used to get current class name,
work under 5.5 and 5.6
failed in 5.3.

<?php
class a{
function
d() {
echo
"=== self::class ===\n";
var_dump(self::class);
echo
"=== static::class ===\n";
var_dump(static::class);
}
}
class
b extends a{}
class
c extends b{}

a::d();
b::d();
c::d();

/*
Output:

=== self::class ===
string(1) "a"
=== static::class ===
string(1) "a"
=== self::class ===
string(1) "a"
=== static::class ===
string(1) "b"
=== self::class ===
string(1) "a"
=== static::class ===
string(1) "c"

*/
up
1
aabweber at gmail dot com
2 years ago
Simplest way to understand is to run this script:

<?php
class ParentClass
{
static
$A = 'ParentVariable';

static function
parentCall()
{
echo
get_called_class() . ', self: ' . self::$A . "\n";
echo
get_called_class() . ', static: ' . static::$A . "\n";
echo
"---\n";
}
}

class
ChildClass extends ParentClass
{
static
$A = 'ChildVariable';

static function
childCall()
{
echo
get_called_class() . ', self: ' . self::$A . "\n";
echo
get_called_class() . ', static: ' . static::$A . "\n";
echo
get_called_class() . ', parent: ' . parent::$A . "\n";
echo
"---\n";
}
}

echo
"Late Static Bindings:\n";
ParentClass::parentCall();
ChildClass::parentCall();
ChildClass::childCall();
?>

----
Output:

Late Static Bindings:
ParentClass, self: ParentVariable
ParentClass, static: ParentVariable
---
ChildClass, self: ParentVariable
ChildClass, static: ChildVariable
---
ChildClass, self: ChildVariable
ChildClass, static: ChildVariable
ChildClass, parent: ParentVariable
up
1
Anonymous
2 years ago
class P_Class {
public static $val = "Parent";
public static function setVal($val){
static::$val = $val;
}
public static function getVal(){
return static::$val;
}
}

class C_Class extends P_Class{}

C_Class::setVal("Child");
var_dump(C_Class::getVal());
var_dump(P_Class::getVal());

Output:
string(5) "Child"
string(5) "Child"
up
6
tyler AT canfone [dot] COM
16 years ago
@ php at mikebird

You can pass arguments to your constructor through your getInstance method, assuming you are running php5.

public static function getInstance($params = null) {
if (self::$objInstance == null) {
$strClass = static::getClass();
self::$objInstance = new $strClass($params);
}
return self::$objInstance;
}

This would pass the params to your constructor. Love for php.
up
7
steven dot karas+nospam at gmail dot com
14 years ago
This function can be used as a workaround for late static binding in PHP >= 5.1.0. There was another similar version of this function elsewhere, but used eval.

<?php

function & static_var($class, $name)
{
if (
is_object($class))
{
$class = get_class($class);
}
elseif ( !
is_string($class))
{
throw new
Exception('Must be given an object or a class name', NULL);
}

$class = new ReflectionClass($class);
return
$class->getStaticPropertyValue($name);
}

?>
up
11
tamilps2 at gmail dot com
10 years ago
I have implemented enum using late static binding.

<?php
interface IEnum {
/**
* Only concrete class should implement this function that should behave as
* an enum.
*
* This method should return the __CLASS__ constant property of that class
*
* @return string __CLASS__
*/
public static function who();
}

abstract class
Enum {

/**
* The selected value for the enum implementation
*
* @var mixed
*/
public $value;

public function
__construct($value) {
$this->value = $value;
}

/**
* The factory method that creates the corresponding enum class.
*
* @param integer $type
* @return false|\class
*/
public static function Factory($type) {
if (empty(
$type)) {
return
false;
}

// use of late static binding to get the class.
$class = static::who();

if (
array_key_exists($type, static::$_enums)) {
return new
$class($type);
}

return
false;
}

public function
getValue() {
return
$this->value;
}

public static function
getValues() {
return
array_keys(static::$_enums);
}

public function
getString() {
return static::
$_enums[$this->value];
}

public function
__toString() {
return static::
$_enums[$this->value];
}

}

class
Fruits extends Enum implements IEnum {

public static
$_enums = array(
1 => 'Apple'
2 => 'Orange'
3 => 'Banana'
)

public static function
who() {
return
__CLASS__;
}
}

// Usage

// user input from dropdown menu of fruits list
$input = 3;

$fruit = Fruits::Factory($input);

$fruit->getValue(); // 3
$fruit->getString(); // Banana
?>
up
9
jakub dot lopuszanski at nasza-klasa dot pl
14 years ago
Suprisingly consts are also lazy bound even though you use self instead of static:
<?php
class A{
const
X=1;
const
Y=self::X;
}
class
B extends A{
const
X=1.0;
}
var_dump(B::Y); // float(1.0)
?>
up
7
kx
16 years ago
At least as of PHP 5.3.0a2 there's a function get_called_class(), which returns the class on which the static method is called.

<?php

class a {
static public function
test() {
print
get_called_class();
}
}

class
b extends a {
}

a::test(); // "a"
b::test(); // "b"

?>
up
7
Andrea Giammarchi
16 years ago
About static parameters, these work as expected.
<?php
class A {
protected static
$__CLASS__ = __CLASS__;
public static function
constructor(){
return static::
$__CLASS__;
}
}

class
B extends A {
protected static
$__CLASS__ = __CLASS__;
}

echo
B::constructor(); // B
?>
up
4
adam dot prall at thinkingman dot com
14 years ago
Just a quick reminder to always check your syntax. While I love LSB, I thought it wasn't working:

static::$sKey = not set

…until I realized that I’d completely forgotten to make it a variable variable:

$sKey = 'testStaticClassVarNameThatExistsInThisClassesScope';

static::$$sKey = is set

…of course this applies anywhere in PHP, but because of the (current) newness late static bindings, I’ve seen lots of code with this particular snafu in it from others.
up
2
joost dot t dot hart at planet dot nl
15 years ago
PHP5.3 unavailable, yet in the need for 'static', I did the following.

Any objections? Personally I hate using the the eval() statement...

<?php

class mother
{
function
setStatic( $prop, $val ) {
// After this, self:: refers to mother, yet next $class refers to...
//
$class = get_class( $this );
eval(
"$class::\$$prop = \$$val;" );
}
}

class
child extends mother
{
protected static
$sProp;

function
writer( $value ) {
parent::setStatic( 'sProp', $value );
}
function
reader()
{
return
self::$sProp;
}
}

$c = new child();
$c->writer( 3 );
echo
$c->reader(); // 3

?>
up
3
Taai
12 years ago
I discovered an interesting thing. The class name string must be accessed directly from "flat" variable. Late static binding code that get's it's variable from array that is passed by class instance, throws an syntax error. Bug?

<?php
class A {

public
$metadata = array('class' => 'A');

public static function
numbers()
{
return
123;
}

}

$instance = new A();

// This throws an error
// Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
var_dump( $instance->metadata['class']::numbers() );

// Get the class name and store it in "flat" variable and now it's ok
$class_name = $instance->metadata['class'];
var_dump( $class_name::numbers() );

// Other tests -------------------------------------------

$arr = array('class' => 'A');

// This works too.
var_dump( $arr['class']::numbers() );
?>
up