目录

很多人都停留在php是过程语言,不能面向对象,php已经8了,在5就已经逐步与面向对象接轨了。

PHP 具有完整的对象模型。特性包括: 访问控制,抽象类和 final 类与方法,附加的魔术方法,接口,对象复制。

对于面向对象的基本知识,不需要我们介绍了,大家都懂。

PHP: OOP 变更日志 - Manual

final

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

注意: 属性不能被定义为 final,只有类和方法才能被定义为 final。

基本的知识,但很少人能用上,也许是场景不需要用到,但也许哪天就真香,时刻牢记。

var

虽然有些版本允许使用var定义属性,会被视为public,但我们不建议使用。

const常量

常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。

要区别于外部const/define定义的常量。

PHP 7.1.0 开始,类的常量可以定义为公有、私有或受保护。如果没有设置这些关键字,则该常量默认为公有,也就是之前的版本只能使用const关键字定义常量,默认这些常量是公开可访问的,7.1之后允许public、protect、private的访问控制关键字。

类自动加载

现代php框架都引入自动加载机制了,特别是composer第三方库使用。

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

不建议使用__autoload函数,后续版本会被弃用。

spl_autoload_register函数用于注册触发加载类,提供回调加载,达到按需加载。

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

$obj  = new MyClass1();
$obj2 = new MyClass2();

构造函数与析构函数

  • 子类的构造函数不会默认或隐性的调用父类构造函数,有需要时,要手动调用父类构造函数。

  • 析构函数即使在使用 exit() 终止脚本运行时也会被调用。但在析构函数中调用 exit() 将会中止其余关闭操作的运行。

  • 在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。

范围解析操作符

范围解析操作符 ::

只是认识下这个符号叫:范围解析操作符

在访问类常量、静态属性、静态方法,我们常会用到双冒号,也就是范围解析操作符。

关于抽象类和接口

如果对抽象类和接口有不清楚的可以看官方文档介绍

PHP: 抽象类 - Manual

PHP: 对象接口 - Manual

Trait

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

  • 优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。

    
    class Base {
        public function sayHello() {
            echo 'Hello ';
        }
    }
    
    trait HelloWorld {
        public function sayHello() {
            echo 'Hello World!';
        }
    }
    
    class MyHelloWorld extends Base {
        use HelloWorld;
    }
    
    
    $o = new MyHelloWorld();
    $o->sayHello();
    //输出:Hello World!
    //因为trait覆盖了基类的方法
    
    class TheWorldIsNotEnough {
        use HelloWorld;
        public function sayHello() {
            echo 'Hello Universe!';
        }
    }    
    
    $o = new TheWorldIsNotEnough();
    $o->sayHello();    
    
    //输出:Hello Universe!
    //因为当前类覆盖trait的方法
    
  • 一个类中支持use多个trait,用逗号隔开

  • 方法冲突解决

    如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。

    为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。

    以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。

    use A, B {
        B::smallTalk insteadof A;//相当于排除A中的smallTalk
        A::bigTalk insteadof B;
        B::bigTalk as talk;//定义了talk别名
    }
    
  • 修改use trait中的方法访问控制

    直接看范例:

    
    // 修改 sayHello 的访问控制
    class MyClass1 {
        use HelloWorld { sayHello as protected; }
    }
    
    // 给方法一个改变了访问控制的别名
    // 原版 sayHello 的访问控制则没有发生变化
    class MyClass2 {
        use HelloWorld { sayHello as private myPrivateHello; }
    }
    
  • trait组合

    trait和class一样也可以组合其他多个trait。

  • trait支持抽象方法,用于强制实体类实现trait的抽象方法

  • trait支持静态方法

  • trait支持属性,但注意冲突,一般是不能定义同样名称的属性。我们建议避免trait和class出现相同名称的属性,即使访问可见性、初始值都一样(允许的),我也不建议,会导致混乱。

匿名类

php7开始支持匿名类,用于创建一次性简单对象。

$util->setLogger(new class {
    public function log($msg)
    {
        echo $msg;
    }
});
//也就是不需要先定义一个logger的对象,再new给setLogger方法。

魔术方法

PHP: 魔术方法 - Manual

PHP所提供的重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的。

PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

PHP: 重载 - Manual

clone

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。

当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。如果有少量及简单的引用属性,可以在魔术方法__clone中手动实现引用属性的复制。

class AA{
    public $object1;
    function __clone()
    {
    
        // 强制复制一份this->object, 否则仍然指向同一个对象
        $this->object1 = clone $this->object1;
    }
}

$a = new AA();

$a2 = clone $a;//通过__clone方法,达到对$a对象的深拷贝。

static::后期静态绑定

self、parent、static

我们都知道parent的用法,就是从当前类(子类)往上追溯基类的存在的这个方法;static则是从当前类(父类、基类)往下追溯至当前执行实例(子类)中存在的方法;

class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // 后期静态绑定从这里开始,这里会执行调用test方法的实例的who方法,也就是B类实例的who方法。
        self::who();//这里只会调用当前类中的who方法,如果有的话,也就是当前代码行所在类A
        parent::who();
    }
}

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

B::test();//输出:BA

对象与引用及传址赋值与引用赋值

在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。

PHP 的引用是别名,就是两个不同的变量名字指向相同的内容。

在 PHP 5以后,一个对象变量已经不再保存整个对象的值。而是保存一个标识符来访问真正的对象内容。

当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

这标识符我们可以理解为存储地址,所以php的对象正常情况下的复制、传递都是通过传址的方式,而不是引用,虽然效果是一样的。

class A {
    public $foo = 1;
}  

$a = new A;
$b = $a;     // $a ,$b都是同一个标识符的拷贝,意味着a、b指向同一个地址
            // ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";//输出:2


$c = new A;
$d = &$c;    // $c ,$d是引用,d是c的一个别名,其实他们还是一样的
            // ($c,$d) = <id>

$d->foo = 2;
echo $c->foo."\n";//输出:2


$e = new A;

function foo($obj) {//e和obj都是同一个标识符的拷贝
    // ($obj) = ($e) = <id>
    $obj->foo = 2;
}

foo($e);
echo $e->foo."\n";//输出:2