分类
后端

php 类和对象

1. 基本概念

自 PHP 5 起完全重写了对象模型以得到更佳性能和更多特性。这是自 PHP 4 以来的最大变化。PHP 5 具有完整的对象模型

PHP 5 中的新特性包括访问控制抽象类final 类与方法,附加的魔术方法接口对象复制类型约束

  • PHP 对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。
  • class
    每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。
  • 一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。
  • 当一个方法在类定义内部被调用时,有一个可用的伪变量 $this
  • 要创建一个类的实例,必须使用 new关键字。类应在被实例化之前定义(某些情况下则必须这样)。
  • 一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP不支持多重继承,一个类只能继承一个基类。
  • 被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
  • 当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数
  • PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。

2. 属性和类常量

类的变量成员叫做“属性”,属性声明是由关键字 publicprotected 或者 private 开头,然后跟一个普通的变量声明来组成。

  • 属性中的变量可以初始化,但是初始化的值必须是常数
  • 属性的访问:
    • 在类的成员方法里面,可以用 ->(对象运算符) ($this->property)
    • 静态属性则是用 ::(双冒号) (self::$property)
  • 当一个方法在类定义内部被调用时,有一个可用的伪变量 $this
  • heredocs 不同,nowdocs 可在任何静态数据上下文中使用,包括属性声明。

可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。

  • 常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。
  • 接口(interface)中也可以定义常量。

3. 自动加载类

在 PHP 5 中,可以定义一个 __autoload() 函数,它会在试图使用尚未被定义的类自动调用。通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

  • spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。
  • spl_autoload_register — 注册给定的函数作为 __autoload 的实现
  • 如果需要多条 autoload 函数,spl_autoload_register() 满足了此类需求。 它实际上创建了 autoload 函数的队列,按定义时的顺序逐个执行。相比之下, __autoload() 只可以定义一次。

4. 构造函数和析构函数

具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作

如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)

析构函数void __destruct ( void )会在到某个对象的所有引用都被删除或者当对象被显式销毁时执

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

5. 访问控制(可见性)

对属性或方法的访问控制(可见性)(在前面加关键字):

  • public(公有) 在任何地方被访问
  • protected(受保护) 被其自身以及其子类和父类访问
  • private(私有) 定义所在的类访问

  • 类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。

  • 类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有
  • 同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。

6. 对象继承

继承将会影响到类与类对象与对象之间的关系。
比如,当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。

除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口

7. 范围解析操作符(::)

范围解析操作符(也可称作 Paamayim Nekudotayim)或者更简单地说是一对冒号,可以用于访问静态成员类常量,还可以用于覆盖类中的属性和方法

当在类定义之外引用到这些项目时,要使用类名。
自 PHP 5.3.0 起,可以通过变量来引用类,该变量的值不能是关键字(如 self,parent 和 static)。

selfparentstatic 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。

当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。这种机制也作用于构造函数和析构函数重载以及魔术方法

8. Static(静态)关键字

声明类属性或方法为静态,就可以不实例化类而直接访问静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)。

  • 由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用
  • 静态属性不可以由对象通过 -> 操作符来访问
  • 静态方式调用一个非静态方法会导致一个 E_STRICT 级别的错误。
  • 可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象

9. 抽象类和接口

PHP 5 支持抽象类和抽象方法

  • 定义为抽象的类不能被实例化
  • 任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象
  • 被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现
  • 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样(或者更为宽松)。
  • 方法的调用方式必须匹配,即类型和所需参数数量必须一致

使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容

  • 接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的- 接口中定义的所有方法都必须是公有,这是接口的特性。
  • 要实现一个接口,使用 implements 操作符:
    • 实现多个接口时,接口中的方法不能有重名
    • 接口也可以继承,通过使用 extends 操作符。接口支持多重继承
    • 类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误。
  • 接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖

10. Traits

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait

  • Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method
  • Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
  • Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能
  • 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

**更多介绍Traits

11. 匿名类 了解

PHP 7 开始支持匿名类。 匿名类很有用,可以创建一次性的简单对象。

可以传递参数到匿名类的构造器,也可以扩展(extend)其他类实现接口(implement interface),以及像其他普通的类一样使用 trait

匿名类被嵌套进普通 Class 后,不能访问这个外部类(Outer class)的 private(私有)、protected(受保护)方法或者属性。
- 为了访问外部类(Outer class)protected 属性或方法,匿名类可以 extend(扩展)此外部类。 - 为了使用外部类(Outer class)的 private 属性,必须通过构造器传进来

12. 重载

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

  • 当调用当前环境下未定义不可见的类属性或方法时,重载方法会被调用
  • 所有的重载方法都必须被声明为 public
  • 这些魔术方法的参数都不能通过引用传递
  • PHP中的"重载"与其它绝大多数面向对象语言不同传统的"重载"是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

属性重载

魔术方法 说明
public void __set ( string $name , mixed $value ) 在给不可访问属性赋值时,__set() 会被调用。
public mixed __get ( string $name ) 读取不可访问属性的值时,__get() 会被调用。
public bool __isset ( string $name ) 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public void __unset ( string $name ) 当对不可访问属性调用 unset() 时,__unset() 会被调用。

参数 $name 是指要操作的变量名称。__set() 方法的 $value 参数指定了 $name **变量的值**。

  • 属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为 static
  • 因为 PHP 处理赋值运算的方式,__set() 的返回值将被忽略。类似的, 这样的链式赋值中,__get() 不会被调用:$a = $obj->b = 8;
  • 在除 isset() 外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。

方法重载

魔术方法 说明
public mixed __call ( string $name , array $arguments ) 在对象中调用一个不可访问方法时,__call() 会被调用
public static mixed __callStatic ( string $name , array $arguments ) 用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。

$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。

12. 遍历对象

PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach 语句。默认情况下,所有可见属性都将被用于遍历

更进一步,可以实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。

可以用 IteratorAggregate 接口以替代实现所有的 Iterator 方法。IteratorAggregate 只需要实现一个方法 IteratorAggregate::getIterator(),其应返回一个实现了 Iterator 的类的实例。

13. 魔术方法

魔术方法大集合 说明
__construct() 构造函数
__destruct() 析构函数
__call() 见上
__callStatic() 见上
__get() 见上
__set() 见上
__isset() 见上
__unset() 见上
__sleep() 常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。
__wakeup() 常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
__toString() 用于一个类被当成字符串时应怎样回应。
__invoke() 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
__set_state() 自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。
__clone() 复制对象完成时,如果定义了__clone() 方法,则新创建的对象中的__clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。
__debugInfo() 这种方法是通过的var_dump()倾倒对象时来获取应显示的属性调用。
- 了解详情,点我

PHP 将所有以__(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。

14. Final 关键字

PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承

15. 对象复制和对象比较

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

  • 当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用
  • 当复制完成时,如果定义了 __clone() 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值(如果有必要的话)。

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

而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。

16. 类型约束

PHP 5 可以使用类型约束。函数的参数可以指定必须为对象(在函数原型里面指定类的名字),接口数组(PHP 5.1 起)或者 callable(PHP 5.4 起)。不过如果使用 NULL 作为参数的默认值,那么在调用函数的时候依然可以使用 NULL 作为实参
- 如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此。
- 类型约束不能用于标量类型如 intstringTraits 也不允许。
- 类型约束不只是用在类的成员函数里,也能使用在函数里:
- 类型约束允许 NULL 值:

17. 后期静态绑定 了解

自 PHP 5.3.0 起,PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类

后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类

转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::parent::static:: 以及 forward_static_call()。可用 get_called_class() 函数来得到被调用的方法所在的类名,static:: 则指出了其范围。

详情,点我

18. 对象和引用

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

  • php的引用是别名,就是两个不同的变量名字指向相同的内容
  • 在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

19. 对象序列化

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。

序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字

为了能够unserialize()一个对象,这个对象的类必须已经定义过

发表评论

电子邮件地址不会被公开。 必填项已用*标注