PHP面向对象

PHP支持完整的面向对象模型,大部分特性与Java的对象模型基本一致,不支持多重继承。此笔记仅记录PHP特有的规则。

创建PHP对象可以用值为类名的变量,如 $className=‘FOO’; $instance=new $className(); 在类定义内部,可以用new self和new parent以及new static创建新对象。

1
2
3
4
5
6
7
8
class Test {
    static public function getNew() {
        return new static;
    }
}
$obj1 = new Test();
$obj2 = new $obj1;
$obj3 = Test::getNew();

PHP用extends继承另外一个类的方法和属性,PHP不支持多重继承。如果父类定义了final方法,此方法不可覆盖。如果类被声明为final则不能被继承。通过parent::来访问被覆盖的方法或属性。被覆盖的方法参数数目必须保持一致,但可以额外定义父类没有的参数。从PHP5.5起ClassName::class可以获取类的全限定名称字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class SimpleClass {
    public $var = 'a default value';
    public function displayVar() {
        echo $this->var;
    }
}
class ExtendClass extends SimpleClass {
    function displayVar() {
        echo "Extending class\n";
        parent::displayVar();
    }
}
$extend = new ExtendClass();
$extend->displayVar();

属性:属性中的变量初始化值必须是常数。非静态属性用$this->property访问,静态属性用self::$property访问,静态属性只能被初始化为文字或常量,不能使用表达式。非静态属性的声明不是必需的,但明确声明属性是一个好的习惯,访问一个未定义的属性,会优先调用__get()或者__set()方法。

类常量:类中定义常量用const关键字,并且不用$符号,常量必须是一个定值,不能是变量,类属性或者数学运算的结果。用className::CONSTANT访问,类内部用self::CONSTANT访问。在PHP5.3.x后,可以用变量来动态调用类$className::CONSTANT。

1
2
3
4
5
6
7
8
9
class MyClass {
    const constant = 'constant value';
    function showConstant() {
        echo self::constant . "\n";
    }
}
echo MyClass::constant . "\n";
$classname = "MyClass";
echo $classname::constant . "\n";

类自动加载:在PHP5中,spl_autoload_register()函数可以注册任意数量的自动加载器,当使用尚未定义的类(class)和接口(interface)时自动去加载。

1
2
3
spl_autoload_register(function($class_name) {
    require_once $class_name . '.php';
});

构造函数:形如function __construct() {}称为构造函数。构造函数不会隐式调用父类构造函数,要执行父类构造函数,要在子类的构造函数中调用parent::__construct(),如果子类没有定义构造函数则会继承父类的构造函数。旧式类用与类同名的方法作为构造函数。PHP同时支持析构函数 __destruct() 与构造函数一样不会隐式调用父类析构函数。析构函数一般很少使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class BaseClass {
    function __construct() {
        print "In BaseClass constructor\n";
    }
}
class SubClass extends BaseClass {
    function __construct() {
        parent::__construct();
        print "In SubClass constructor\n";
    }
}

类中的属性必须使用修饰符,兼容PHP4可以用var修饰,被视为公有。对于方法如果没有设置修饰符,则认为是公有方法。

对象继承:除非使用了自动加载,否则一个类必须在使用之前定义。父类或接口必须在子类之前被声明。子类覆盖父类方法的访问控制必须是与父类一样或者更为宽松。

抽象类和抽象方法都必须加abstract关键字,可以定义没有任何抽象方法的抽象类。PHP支持接口(interface),接口可以被子类实现(implements),子类实现多个接口,用逗号分隔多个接口名称,实现的多个接口中的方法不能有重载的方法(名字相同而参数不同)。接口可以被子接口继承(exntends),子接口可以继承多个接口,接口可以定义常量,但不能被子类或子接口覆盖。

1
2
3
4
5
6
abstract class AbstractClass {
    abstract public function getValue();
    public function printOut() {
        print $this->getValue() . "\n";
    }
}

Trait:Trait是为类似PHP这样的单继承语言准备的Mixin机制,trait使得水平组合多个特性成为可能,与继承的垂直方式互为补充,让开发者在不同层次结构内自由复用代码。trait自身不能实例化。继承与trait的优先级是来自当前类的成员覆盖了trait的方法,而trait则覆盖被继承的方法。多个trait在use声明中用逗号分隔,可以都插入到一个类中。如果两个trait都插入一个同名方法,必须使用 insteadof操作符来明确使用哪一个方法,或者用as给方法引入别名,as还可以用来调整方法的访问控制。trait除了可以插入到类中,也可以插入到其它trait中。trait支持抽象方法、静态方法、静态属性、非静态属性,但不能定义常量。Trai定义了一个属性之后,类中不能定义同名的属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Base {
    public function sayHello() {
        echo 'Hello';
    }
}
trait SayHello {
    public function sayHello() {
        parent::sayHello();
        echo "World!";
    }
}
class MyHelloWorld extends Base {
    use SayHello;
}
$o = new MyHelloWorld();
$o->sayHello();

//冲突解决
trait A {
    public function smallTalk() { echo 'a'; }
    public function bigTalk() { echo 'A'; }
}
trait B {
    public function smallTalk() { echo 'b'; }
    public function bigTalk() { echo 'B'; }
}
class Talker {
    use A, B {
        B::smallTalk insteadOf A;
        A::bigTalk insteadOf B;
        B::bigTalk as talk;
    }
}

//as访问控制
trait HelloWorld {
    public function sayHello() { echo 'Hello World!'; }
}
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

PHP支持匿名类(anonymous class),用new class {}语法格式,嵌套匿名类不能访问外部类的私有、受保护的方法或属性。匿名类是有名称的,而且是通过引擎生成的,用户代码不应该依赖于此实现细节。

重载:PHP提供的重载(overloading)与传统重载是不一样的,PHP通过魔法方法在类属性、方法不存在或不可见时依然可以访问。这些魔法方法都必须声明为public。

  • __set($name, $value) 当给不可访问的属性赋值时,__set() 会被调用
  • __get($name) 当读取不可访问属性时,__get()会被调用
  • __isset($name) 当给不可访问属性调用isset()或empty()时调用
  • __unset($name) 当对不可访问属性调用unset()时调用
  • __call($name, $arguments) 当调用不可访问方法时调用,$arguments是参数数组
  • __callStatic($name, $argument)当在静态上下文中调用一个不可访问的方法时调用,__callStatic需要声明为static。

用foreach语句默认可以遍历类的所有可见属性。foreach($class as $key => $value) 在类内部可以访问所有包括protected和private属性 foreach($this as $key => $value),还可以实现Iterator接口foreach($it as $a => $b)

魔法方法:所有魔法方法都必须是public的。__sleep() __wakeup() 用于序列化和反序列化对象。__toString()用于将对象转为字符串,在任何字符串环境生效。__invoke()当尝试以调用函数的方式调用对象时被调用。 __set_state()自PHP 5.1.0起,当调用var_export()导出类时,此静态方法会被调用。 __debugInfo()当调用var_dump()时调用。__clone方法在调用clone时当复制完对象后,用来修改属性值。

相等比较:当使用比较运算符(==)时,如果两个对象的属性和属性值都相等,而且两个对象是同一个类的实例,那么对象相等。当使用全等运算符(===)时,一定要两个对象变量指向同一个实例。

PHP中self::,parent::,static::,foreward_static_call()成为转发调用(forwarding call)。后期静态绑定(Late static binding)的意思是说static::不再被解析为定义当前方法所在的类,而是实际运行时计算的。self::或者CLASS则被严格限定在定义方法的类中。后期静态绑定跟C++中的虚函数的延迟绑定非常类似,后期静态绑定会一直解析到在继承栈中最靠近当前调用类的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 */

在PHP5中,一个对象变量不保存整个对象的值,而是保存一个标识符来访问真正的对象内容,所有变量都是保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。这与&引用有着本质的区别。