类常量、类属性(静态)和类函数(静态)都可以共享同一个名称,并且可以使用双冒号访问。
<?php
class A {
public static $B = '1'; # 静态类变量。
const B = '2'; # 类常量。
public static function B() { # 静态类函数。
return '3';
}
}
echo A::$B . A::B . A::B(); # 输出:123
?>
作用域解析运算符(也称为 Paamayim Nekudotayim)或更简单的说法,双冒号,是一个标记,允许访问类的 常量、静态 属性或 静态 方法,或其父类之一。此外,静态属性或方法可以通过 延迟静态绑定 覆盖。
在类定义之外引用这些项时,请使用类的名称。
可以使用变量引用类。变量的值不能是关键字(例如 self
、parent
和 static
)。
Paamayim Nekudotayim 乍一看似乎是为双冒号命名一个奇怪的选择。然而,在编写 Zend 引擎 0.5(为 PHP 3 提供支持)时,Zend 团队决定将其命名为 Paamayim Nekudotayim。它实际上确实意味着双冒号——用希伯来语!
示例 #1 :: 从类定义之外
<?php
class MyClass {
const CONST_VALUE = 'A constant value';
}
$classname = 'MyClass';
echo $classname::CONST_VALUE;
echo MyClass::CONST_VALUE;
?>
三个特殊关键字 self、parent 和 static 用于从类定义内部访问属性或方法。
示例 #2 :: 从类定义内部
<?php
class OtherClass extends MyClass
{
public static $my_static = 'static var';
public static function doubleColon() {
echo parent::CONST_VALUE . "\n";
echo self::$my_static . "\n";
}
}
$classname = 'OtherClass';
$classname::doubleColon();
OtherClass::doubleColon();
?>
当扩展类覆盖父类的某个方法的定义时,PHP 不会调用父类的方法。是否调用父类方法取决于扩展类。这也适用于 构造函数和析构函数、重载 和 魔术 方法定义。
示例 #3 调用父类的方法
<?php
class MyClass
{
protected function myFunc() {
echo "MyClass::myFunc()\n";
}
}
class OtherClass extends MyClass
{
// 覆盖父类的定义
public function myFunc()
{
// 但仍然调用父函数
parent::myFunc();
echo "OtherClass::myFunc()\n";
}
}
$class = new OtherClass();
$class->myFunc();
?>
另请参见 有关静态调用技巧的一些示例。
类常量、类属性(静态)和类函数(静态)都可以共享同一个名称,并且可以使用双冒号访问。
<?php
class A {
public static $B = '1'; # 静态类变量。
const B = '2'; # 类常量。
public static function B() { # 静态类函数。
return '3';
}
}
echo A::$B . A::B . A::B(); # 输出:123
?>
在 PHP 中,您使用 self 关键字来访问静态属性和方法。
问题是,无论 method() 是静态声明还是非静态声明,您都可以用 self::method() 替换 $this->method(),无论在何处。那么您应该使用哪一个呢?
请考虑以下代码
class ParentClass {
function test() {
self::who(); // 将输出 'parent'
$this->who(); // 将输出 'child'
}
function who() {
echo 'parent';
}
}
class ChildClass extends ParentClass {
function who() {
echo 'child';
}
}
$obj = new ChildClass();
$obj->test();
在这个示例中,self::who() 将始终输出 ‘parent’,而 $this->who() 将取决于对象的类。
现在我们可以看到,self 指的是调用它的类,而 $this 指的是当前对象的类。
因此,您应该仅在 $this 不可用时,或当您不想允许子类覆盖当前方法时使用 self。
似乎您可以使用比类名更多的内容来引用类定义之外的类定义的静态变量、常量和静态函数,使用 :: 。该语言似乎允许您使用对象本身。
例如
class horse
{
static $props = {'order'=>'mammal'};
}
$animal = new horse();
echo $animal::$props['order'];
// 返回 'mammal'
这似乎没有记录,但我认为这在语言中是一个重要的便利性。我希望看到它被记录并作为有效的支持。
如果没有正式支持,另一种方法似乎会很混乱,类似于这样
$animalClass = get_class($animal);
echo $animalClass::$props['order'];
刚刚发现使用类名也可以调用祖先类的类似函数。
<?php
类 Anchestor {
public $Prefix = '';
private $_string = 'Bar';
public function Foo() {
return $this->Prefix.$this->_string;
}
}
类 MyParent extends Anchestor {
public function Foo() {
$this->Prefix = null;
return parent::Foo().'Baz';
}
}
类 Child extends MyParent {
public function Foo() {
$this->Prefix = 'Foo';
return Anchestor::Foo();
}
}
$c = new Child();
echo $c->Foo(); //return FooBar, 因为 Prefix 在 Anchestor::Foo() 中被使用,所以 MyParent::Foo() 不会执行
?>
Child 类调用了 Anchestor::Foo(),因此 MyParent::Foo() 永远不会被执行。
嗯,几行代码像瑞士军刀一样调用父类方法。唯一的限制是不能用在 "按引用" 的参数上。
主要优势是您不需要知道父类的 "实际" 签名,您只需要知道需要哪些参数。
<?php
类 someclass extends some superclass {
// 可用于构造函数
函数 __construct($ineedthisone) {
$args=func_get_args();
/* $args 将包含传递给 __construct 的任何参数。
* 您的形式参数不会影响 func_get_args() 的工作方式
*/
call_user_func_array(array('parent',__FUNCTION__),$args);
}
// 但这不仅仅用于 __construct
函数 anyMethod() {
$args=func_get_args();
call_user_func_array(array('parent',__FUNCTION__),$args);
}
// 注意:php 5.3.0 甚至允许您执行
函数 anyMethod() {
// 需要 php >=5.3.x
call_user_func_array(array('parent',__FUNCTION__),func_get_args());
}
}
?>
从 php 5.3.0 开始,您可以使用 'static' 作为作用域值,如下例所示(与 'self' 关键字相比,为继承机制增加了灵活性...)。
<?php
类 A {
const C = 'constA';
public function m() {
echo static::C;
}
}
类 B extends A {
const C = 'constB';
}
$b = new B();
$b->m();
// 输出:constB
?>
一个小技巧,可以绕过 php 严格标准...
函数调用者会找到调用它的对象,以便静态方法可以修改它,在静态函数中用它替换 $this,但不会出现严格警告 :)
<?php
error_reporting(E_ALL + E_STRICT);
函数 caller () {
$backtrace = debug_backtrace();
$object = isset($backtrace[0]['object']) ? $backtrace[0]['object'] : null;
$k = 1;
while (isset($backtrace[$k]) && (!isset($backtrace[$k]['object']) || $object === $backtrace[$k]['object']))
$k++;
return isset($backtrace[$k]['object']) ? $backtrace[$k]['object'] : null;
}
类 a {
public $data = 'Empty';
函数 set_data () {
b::set();
}
}
类 b {
static 函数 set () {
// $this->data = 'Data from B !';
// 在静态函数中使用 this 会抛出警告...
caller()->data = 'Data from B !';
}
}
$a = new a();
$a->set_data();
echo $a->data;
?>
输出:Data from B !
没有警告或错误!
对于 '后期静态绑定' 主题,我发布了以下代码,它演示了一种技巧,说明如何在后期类中设置变量值,并在父类(或父类的父类等)中打印该值。
<?php
class cA
{
/**
* 使用直接默认值的测试属性
*/
protected static $item = 'Foo';
/**
* 使用间接默认值的测试属性
*/
protected static $other = 'cA';
public static function method()
{
print self::$item."\r\n"; // 它总是打印 'Foo' ... :(
print self::$other."\r\n"; // 我们认为,这个只会打印 'cA',但... :)
}
public static function setOther($val)
{
self::$other = $val; // 在此作用域内设置一个值。
}
}
class cB extends cA
{
/**
* 重新定义默认值的测试属性
*/
protected static $item = 'Bar';
public static function setOther($val)
{
self::$other = $val;
}
}
class cC extends cA
{
/**
* 重新定义默认值的测试属性
*/
protected static $item = 'Tango';
public static function method()
{
print self::$item."\r\n"; // 它总是打印 'Foo' ... :(
print self::$other."\r\n"; // 我们认为,这个只会打印 'cA',但... :)
}
/**
* 现在我们不再重新声明 setOther() 方法,只是为了好玩,使用带有 'self::' 的 cA。
*/
}
class cD extends cA
{
/**
* 重新定义默认值的测试属性
*/
protected static $item = 'Foxtrot';
/**
* 现在我们不再重新声明所有方法来完成这个问题。
*/
}
cB::setOther('cB'); // 这是 cB::method()!
cB::method(); // 这是 cA::method()!
cC::setOther('cC'); // 这是 cA::method()!
cC::method(); // 这是 cC::method()!
cD::setOther('cD'); // 这是 cA::method()!
cD::method(); // 这是 cA::method()!
/**
* 结果: ->
* Foo
* cB
* Tango
* cC
* Foo
* cD
*
* 这是什么鬼? :)
*/
?>
值得注意的是,提到的变量也可以是对象实例。这似乎是从实例的角度来看,引用尽可能高的继承层次结构中的静态函数的最简单方法。我在非静态方法中使用 static::something() 时遇到了一些奇怪的行为。
请看下面的示例代码
<?php
class FooClass {
public function testSelf() {
return self::t();
}
public function testThis() {
return $this::t();
}
public static function t() {
return 'FooClass';
}
function __toString() {
return 'FooClass';
}
}
class BarClass extends FooClass {
public static function t() {
return 'BarClass';
}
}
$obj = new BarClass();
print_r(Array(
$obj->testSelf(), $obj->testThis(),
));
?>
输出结果
<pre>
Array
(
[0] => FooClass
[1] => BarClass
)
</pre>
正如你所见,__toString 对此没有任何影响。以防你好奇这是否是实现方式。
你使用 'self' 来访问当前类,使用 'parent' 来访问父类,那么如何访问父类的父类呢?或者如何访问深层类层次结构的根类?答案是使用类名。这将像 'parent' 一样工作。以下代码示例解释了我的意思。以下代码
<?php
class A
{
protected $x = 'A';
public function f()
{
return '['.$this->x.']';
}
}
class B extends A
{
protected $x = 'B';
public function f()
{
return '{'.$this->x.'}';
}
}
class C extends B
{
protected $x = 'C';
public function f()
{
return '('.$this->x.')'.parent::f().B::f().A::f();
}
}
$a = new A();
$b = new B();
$c = new C();
print $a->f().'<br/>';
print $b->f().'<br/>';
print $c->f().'<br/>';
?>
将输出
[A] -- {B} -- (C){C}{C}[C]
<?php
/**
* 从类外部访问常量
*/
class Foo{
public const A = "Constant A";
}
echo Foo::A;
echo "\n";
/**
* 在其自身类中访问常量
*/
class Bar{
public const A = "Constant A";
public function abc(){
echo self::A;
echo "\n";
}
}
$obj = new Bar;
$obj->abc();
/**
* 在其子类中访问常量
*/
class Baz extends Bar{
public function abc(){
echo parent::A;
}
}
$obj = new Baz;
$obj->abc();
// 静态属性和静态方法也遵循此原则。