基本概念
类
世间万物都具有其自身的属性和方法,有的事物之间拥有相似的属性和方法,比如人都有身高和体重的属性,又有跑步的方法,但是每个人的这些属性又不尽相同。
类是具有相似属性和方法的一类事物的抽象模型。
根据研究领域的不同,抽象的层次不同:人类学家研究人,人属于人类;动物学家研究动物,人和狗都是动物类,生物学家研究生物,人、狗、草就是生物类;
对象
对象是类进行实例化后的产物。
类的定义
每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的常量、变量(称为属性,也叫成员变量)和函数(称为方法)
类名
一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线,但不能是 PHP 保留字
常量
可以把在类中始终保持不变的值定义为常量。
使用 const
关键字定义一个常量,例如:const name = 'value'
常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用
需要公开且静态可用但又不允许客户端代码改变的属性,通过常量属性来实现
属性
在类中定义的变量称为属性(也叫成员变量)
属性声明由一个普通的变量声明来完成。属性中的变量可以初始化,但是初始化的值必须是常数(PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值)
方法
方法声明是由一个普通的函数声明来完成。
构造函数
__construct ([ mixed $args [, $... ]] ) : void
具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作
析构函数
__destruct ( void ) : void
析构函数会在到某个对象的所有引用都被删除,或者当对象被显式销毁,或者脚本停止运行(异常终止或 exit
显式终止)时执行
在析构函数中调用 exit() 将会中止其余关闭操作的运行。
析构函数如果在脚本关闭时调用,此时所有的 HTTP 头信息已经发出,脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同,详见 PHP 脚本关闭时的工作目录
访问控制(可见性)
对常量、属性和方法的访问控制,是通过在前面添加关键字 public
(公有),protected
(受保护)或 private
(私有)来实现的。
被定义为公有的类成员可以在任何地方被访问
被定义为受保护的类成员则可以被其自身以及其子类和父类访问
被定义为私有的类成员则只能被其定义所在的类访问
如果没有设置这些关键字,则默认为公有
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
<?php
class Personal
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
private function bar()
{
echo 'Accessed the private method.';
}
public function baz(Personal $other)
{
// We can change the private property:
$other->foo = 'hello';
var_dump($other->foo);
// We can also call the private method:
$other->bar();
}
}
$test = new Personal('Personal');
$test->baz(new Personal('other'));
静态属性和静态方法
通过 static 关键字来定义类的静态属性和静态方法
声明类属性或方法为静态,就可以不实例化类而直接访问。
重写与重载
重写(覆盖)是指子类继承父类的方法后,重新定义与父类同名的方法,直接调用时调用的是子类的方法
重载是指一个标识符被用做多个函数名,通过参数个数及类型将这些同名函数区分开来,调用时不会发生混淆。
PHP
并不直接支持重载,PHP
的重载是利用魔术方法动态的创建类的属性和方法从而达到重载的目的
当调用当前环境下未定义或不可见的类属性或方法时,以下重载方法会被调用。
__set
对未定义的属性进行赋值时
__get
访问未定义的属性时
__isset
对未定义的属性使用
isset()
时__unset
对未定义的属性使用
unset()
时__call
通过非静态方式调用不存在的方法时
__callStatic
通过静态方式调用不存在的方法时
魔术方法
魔术方法包括:
构造方法和析构方法
__construct
__destruct
重载方法 -
__set
__get
__isset
__unset
__call
__callStatic
__sleep
和__wakeup
如果
__sleep()
存在,serialize()
执行序列化前会先调用__sleep()
,并序列化__sleep
的返回值。此功能可以用于清理对象中不希望被序列化的内容。如果该方法未返回任何内容,则NULL
被序列化,并产生一个E_NOTICE
级别的错误。与之相反,如果存在
__wakeup()
方法,unserialize()
执行反序列化前会先调用__wakeup()
方法,预先准备对象需要的资源(比如建立数据库连接或执行其他初始化操作)。__toString
当一个类被当成字符串时,触发调用
__toString()
。__invoke
当尝试以调用函数的方式调用一个对象时,
__invoke()
方法会被自动调用__set_state
当调用
var_export()
导出类时,此 静态 方法会被调用__debugInfo
当调用
var_dump()
打印类的对象时,此 静态 方法会被调用__clone
当复制对象完成时,如果定义了
__clone()
方法,则新创建的对象(复制生成的对象)中的__clone()
方法会被调用。
继承
继承对于功能的设计和抽象是非常有用的,而且对于类似的对象增加新功能就无须重新再写这些公用的功能。
一个类可以在声明中用 extends
关键字继承另一个类的方法和属性
PHP不支持多重继承,一个类只能继承一个基类。
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数。
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
final
如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。
类的使用
类的实例化
在 new 之后跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称。
在类定义内部,可以用 new self
(当前类) 和 new parent
(父类) new static
(继承链中最初调用的类) 创建新对象
从
PHP5
起,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容
访问属性和方法
new ClassName
是一个实例化的对象,在类的外部通过->
访问实例属性、实例方法$this
是一个到主叫对象的引用,在类的内部通过->
访问实例属性和实例方法static
(继承链中最初调用的类)、self
(当前类)、parent
(当前类的父类),在类的内部通过::
访问常量、静态属性、静态方法,也可以通过::class
获取类的完全限定名称,另外static
和parent
还可以访问继承时被覆盖的父类的实例方法。不能通过::
访问实例属性关键字 static 表示后期静态绑定的功能,用于在继承范围内引用静态调用的类。”后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的
className::
在类的外部(内部也可以)访问常量、静态属性、静态方法
用静态方式调用一个非静态属性或非静态方法会导致一个 E_STRICT 级别的错误。
但是可以通过一个类已实例化的对象(或 $this
),使用 ::
访问静态属性,使用 ->
或 ::
访问静态方法。
对象复制
在多数情况下,我们并不需要完全复制一个对象来获得其中属性。但有一个情况下确实需要:如果你有一个 GTK 窗口对象,该对象持有窗口相关的资源。你可能会想复制一个新的窗口,保持所有属性与原来的窗口相同,但必须是一个新的对象(因为如果不是新的对象,那么一个窗口中的改变就会影响到另一个窗口)。还有一种情况:如果对象 A 中保存着对象 B 的引用,当你复制对象 A 时,你想其中使用的对象不再是对象 B 而是 B 的一个副本,那么你必须得到对象 A 的一个副本。
对象复制通过 clone 关键字完成:
$copy_of_object = clone $object;
当对象被复制后,PHP 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性仍然会是一个指向原来的变量的引用
当复制完成时,如果定义了 clone() 方法,则新创建的对象(复制生成的对象)中的 clone() 方法会被调用,可用于修改属性的值
对象比较
两个对象变量是同一个类的实例,且属性和属性值相同,则两个对象变量相等(==);
两个对象变量指向同一个实例,则两个变量全等(===)
抽象类和接口
抽象类和接口在语法上的相同点
都不能被直接实例化
都能定义类常量
都能定义抽象方法(抽象方法不能提供具体的实现)
继承抽象类的子类必须实现抽象类的所有抽象方法,实现接口的子类也必须实现接口的所有抽象方法
抽象类和接口在语法上的不同点
通过
extends
继承抽象类,通过implements
实现接口一个类可以实现多个接口,但只能直接继承一个抽象类
抽象类可以定义属性和非抽象方法,避免在子类中重复定义,而接口不能
抽象类和接口的本质区别
接口是对动作的抽象,而抽象类是对根源的抽象
抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类。例如猫、狗可以抽象成一个动物类抽象类,具备四条腿的属性,叫的方法。实际应用中比如:可以抽象一个数据库操作类,具有主机名、端口、用户名、密码等属性,连接数据库、增删改查数据等方法。
接口抽象的是动作行为,实现它的子类可以不存在任何关系,共同之处。鸟、飞机可以实现飞Fly接口,具备飞的行为,鸟、飞机显然不能抽象成一个类吧!
所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的规则而已。
所以一个类只能继承一个抽象类(因为你不可能同时是生物又是非生物)。但是一个类可以同时实现多个接口,比如开车接口,滑冰接口,啪啪啪接口,踢足球接口,游泳接口。
接口更多时候是为了规范行为,抽象类除了可以规范行为还可以实现代码复用
trait
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化
通过逗号分隔,在 use 声明列出多个 trait,插入到一个类中。
优先级
从基类继承的方法会被 trait 插入的方法所覆盖,而当前类的方法又会覆盖 trait 插入的方法
多个 trait 的方法冲突
如果两个 trait
都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
使用 insteadof
操作符来明确指定使用冲突方法中的哪一个
此外还可以使用 as
操作符将其中一个冲突的方法以另一个名称来保留
<?php
Trait A
{
public function small()
{
var_dump('a');
}
public function big()
{
var_dump('A');
}
}
Trait B
{
public function small()
{
var_dump('b');
}
public function big()
{
var_dump('B');
}
}
class C
{
use A, B {
A::big insteadof B;//A的big方法代替B的
B::small insteadof A;//B的small方法代替A的
A::small as smallA;//A的small重命名
}
}
修改方法的访问控制
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// 修改 sayHello 的访问控制
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
类与 trait 的属性冲突
Trait 定义了一个属性后,类就不能定义同样名称的属性,否则会产生 fatal error。 有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。
<?php
trait PropertiesTrait {
public $same = true;
public $different = false;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true; // PHP 7.0.0 后没问题,之前版本是 E_STRICT 提醒
public $different = true; // 致命错误
}
Trait 支持定义抽象方法
trait 支持定义抽象方法,使用的类必须实现引入的 trait 中的所有抽象方法
匿名类
PHP 7 开始支持匿名类。 匿名类很有用,可以创建一次性的简单对象。
可以传递参数到匿名类的构造器,也可以扩展(extend)其他类、实现接口(implement interface),以及像其他普通的类一样使用 trait
<?php
// PHP 7 之前的代码
class Logger
{
public function log($msg)
{
echo $msg;
}
}
new Logger();
// 使用了 PHP 7+ 后的代码
new class {
public function log($msg)
{
echo $msg;
}
};
匿名类被嵌套进普通 Class 后,不能访问这个外部类(Outer class)的 private(私有)、protected(受保护)方法或者属性。