一、对象和引用
对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。下面通过一些例子来说明。
PHP 的引用是别名,就是两个不同的变量名字指向相同的内容。在 PHP 5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
class A {
public $foo = 1;
}
$a = new A;
$b = $a; // $a ,$b都是同一个标识符的拷贝
// ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";//2
$c = new A;
$d = &$c; // $c ,$d是引用
// ($c,$d) = <id>
$d->foo = 2;
echo $c->foo."\n";//2
$e = new A;
function foo($obj) {
// ($obj) = ($e) = <id>
$obj->foo = 2;
}
foo($e);
echo $e->foo."\n";//2
二、对象比较
当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
三、后期静态绑定
参考:https://learnku.com/laravel/t/3844/understanding-php-delayed-static-binding-late-static-bindings
通过官方说明我们知道后期静态绑定的范围是继承范围内,也就是我们看到static::foo();时想要确定static是谁就从调用这个方法的类的继承链去找。
简单理解PHP延迟静态绑定
“后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用。这个东西可以实现在父类中能够调用子类的方法和属性。当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类。
使用(static)关键字来表示这个别名,和静态方法,静态类没有半毛钱的关系,static::不仅支持静态类,还支持对象(动态类)。
后期静态绑定的功能,用于在继承范围内引用静态调用的类。
1、self的限制
基本上,它归结为self
关键字不遵循继承规则的事实。 self
总是解决它使用它的类。这意味着如果你在父类中创建一个方法并从一个子类调用该方法,self
则不会像你期望的那样引用。
延迟静态绑定引入了static
关键字的一个新用法,它解决了这个特定的缺点。当你使用static
它时,它代表你第一次使用它的类,即。它'绑定'到运行时类。
由于继承的关系,那么B类会从A类中继承test()方法,由于在调用test()方法时,其环境为A,那么最终会执行A类的who()函数,而不是B类的who()函数。
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,调用的是A类中的who方法
2、后期静态绑定的用法
后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地来说,在出现这种情况时,我们要想办法“绕”过这个A环境,去执行B类中的who()方法,也就是说,哪个类在调用,就执行哪个类的方法,从而引入了static::
关键字来作为后期静态绑定。
如下代码:B调用test方法,test方法调用who方法。而B类中重写了who方法,则实际调用的是B中的who方法,所以输出的__CLASS__是B 这就是说 static 调用的静态方法会在运行时自动判断调用谁的方法。而self则只会调用定义它时所在的类中的方法。这是static的一个区别于其它调用的一个新特性。而static调用非静态方法时则不会达到这种后期绑定的效果,所以叫后期静态绑定。
还有就是 static只能调用静态属性。
总结:
综上所述,static关键字可以实现以下功能:
1 调用类的静态方法 有后期静态绑定效果;
2 调用类的静态属性 有后期静态绑定效果;
3 调用类的非静态方法 没有后期静态绑定效果;
4 强调一下 不可以调用非静态属性;
建议,以后不要再使用self::,使用static::
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,调用的是B中的who方法
3、非静态环境下使用 static::
注:在非静态环境下,所调用的类即为该对象实例所属的类。由于 $this-> 会在同一作用范围内尝试调用私有方法,而 static:: 则可能给出不同结果。另一个区别是 static:: 只能用于静态属性。
- 首先分析$b->test(); 它成功输出两个AAB,当 $b调用test方法时,首先查找类B,它没有test方法,但它继承自类A,类A中有test方法,此时执行类A的test方法,所以$this是指向A的(执行环境是A),$this->foo(),输出AAB,对于static::foo因为A中的foo方法是私有的,所以类B无法继承,所以此时执行的仍然是类A的foo方法,输出AAB。
- 其次分析$c->test();当 $c调用test方法时,首先查找类C,它没有test方法,但它继承自类A,类A中有test方法,此时执行类A的test方法,所以$this是指向A的,$this->foo(),输出AAC,又因为C中有自己的foo方法,且是私有的,又因为A和C中都有foo方法,根据static的静态绑定,此时会执行类C的foo方法,由于此时的环境上下文为类A,它无法访问类C的私有方法,所以此时会报错。如果此时将C类中的foo()方法限定符改为public或protected,那么函数会成功执行!因为public允许任何类访问,同理,即使在A类的环境中,仍然能访问C类的public方法。
class A {
private function foo() {
echo __CLASS__;
echo get_class();
echo get_called_class();
}
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 {
private function foo() {
/* original method is replaced; the scope of the new one is C */
}
}
$b = new B();
$b->test();//AAB AAB
$c = new C();
$c->test(); // AAC static 的那个 fails:Call to private method C::foo() from context 'A' 如果重写的方法是
//公有或受保护的,则static::foo()会访问C中的foo()方法
4、转发和非转发调用
转发调用:所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。可用 get_called_class() 函数来得到被调用的方法所在的类名,static:: 则指出了其范围。
非转发调用:那么非转发调用其实就是明确指定类名的静态调用(foo::bar())和非静态调用($foo->bar())
后期静态绑定原理
后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。
注:后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
解析1.当执行C::test();,因为C中没有test方法且C继承了B,所以此时会去执行B中的test方法,当执行A::foo();此时调用A的who方法,当执行parent::foo();self::foo();根据php手册的解释,parent和self会转发此调用,自我理解为,此时parent和self只起到调用foo()方法的作用,即执行两次static::who(),所以输出两个C 。
解析2.例子中C类调用test方法,test方法又调用三次foo方法,foo方法中出现了static调用,所以static需要在A、B、C中找。例子中的执行结果是A、C、C。
为什么是这个结果?就需要理解官方解释了,“后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名”,“转发调用”指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。除此之外的调用都是非转发调用,如foo::bar()或$foo->bar();
调用1 : C::test() ----->非转发
调用2_1 : A::foo() ------>非转发
static::who()的上一个非转发调用就是A::foo(),它的类名是A。
调用2_2 : parent::foo(); ---------->转发调用
static::who()的上一个非转发调用就是C::test(),它的类名是C。
调用2_3 : self::foo(); ------------->转发调用
static::who()的上一个非转发调用就是C::test(),它的类名是C。
所以例子的结果是A、C、C。
class A {
public static function foo() {
echo 1;
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();//1A 1C 1C
使用场景举例
class Model
{
public static function find()
{
echo static::$name;
}
}
class Product extends Model
{
protected static $name = 'Product';
}
Product::find(); //Product