遅延静的束縛 (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
User Contributed Notes 29 notes
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'
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);
?>
<?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) { }
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
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'
?>
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"
*/
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
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"
@ 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.
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);
}
?>
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
?>
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)
?>
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"
?>
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
?>
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.
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
?>
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() );
?>