一、类
PHP5重写了对象模型,提高了性能且引入了更多特性,其中新特性包括
访问控制
、抽象类
、final类和方法
、魔术方法
、接口
、对象复制
、类型约束
。PHP对待对象的方式是引用
,即每个变量都持有的是对象的引用,而非拷贝
1、类的创建,如下基本形式。类可以拥有常量
、变量(称为属性)
、函数(称为方法)
class {
// 常量、属性声明
// 方法声明
}
2、类的实例化,使用new
关键字,如$instance = new SomeClass();
,也可以使用字符串的方式来实例化一个类(动态特性,但是当使用到命名空间的时候,需要使用完整名称),如:
$className = "A";
$p = new $className(); // 相当于 new A();
在类的内部,有一个$this
指针,来代表创建出的对象
3、对类的赋值,是引用赋值,而不是拷贝。如果需要拷贝赋值,则应当使用克隆
4、可以使用ClassName::class
来获得一个类的类名(包含命名空间),如Apple::class
返回“NS\Apple”
5、定义构造函数可以使用function __construct(){ }
。请注意,当子类中定义了构造函数后,将不会调用父类的构造函数,如果要执行父类构造函数,需要在子类的构造函数中加上parent::__construct()
;如果子类没有定义构造函数,则会继承父类构造函数(除非父类构造函数定义为private),而且会得到执行
6、定义析构函数使用function __destruct()
7、访问控制,使用public
(公共访问,类的内外均可访问),private
(私有访问,类本身可以访问),protected
(保护访问,类本身及子类内可以访问)
8、范围解析操作符(::
),用于访问静态成员、类常量,还可以用于覆盖类中的属性和方法。类外访问静态成员和类常量的时候,要使用类名,类内使用self::
。覆盖父类的方法,默认不会再执行父类的方法,如果需要执行,则需要子类自己指定,如:
class A {
protected function foo() {
echo "Hello, ";
}
}
class B extends A {
public function foo() {
parent::foo(); // 要再使用父类的方法,需要子类使用`parent::`指明
echo "world!";
}
}
$p = new B();
$p->foo();
9、使用static
声明静态属性、静态方法。静态属性和静态方法属于类自身,只能通过::
来访问,外部使用类名::
前缀,内部使用self::
10、对象的比较:
- 使用
==
的比较规则:两个对象的属性和属性值都相等,且是同一个类的实例,则返回true - 使用
===
的比较规则:两个对象的引用地址相等,引用向同一块内存单元
11、可以使用serialize
序列化来保存一个对象,但是保存的仅仅是类名
和属性值
,而在反序列化的时候,需要使用unserialize
,然后同时引入类名对应的类,便可继续使用,如:
// save.php
require_once "SomeClass.php";
$p = new SomeClass();
$p->setSomeVar('5');
file_put_contents("fileSave", serialize($p));
// use.php
require_once "SomeClass.php";
$p = file_get_contents("fileSave");
$p = unserialize($p);
echo $p->getSomeVar(); // 5
二、属性
1)属性以public
、private
、protected
开头,接着一个普通变量而成,如public $name;
,为了向后兼容,也支持var
方式,当时一般不建议再使用var
。属性可以有默认值,但必须是 常量值(也可以是nowdoc)
2)访问属性:类的成员方法内部使用$this->property
形式访问非静态属性,使用self::$property
形式访问静态属性。类外使用$obj->property
访问public的非静态属性
三、继承
1、PHP不支持多重继承,一个类只能继承一个基类,继承使用extends
关键字
2、PHP支持重写,使用和父类一样的属性名或者方法名,即可重写属性或者方法。但如果定义方法的时候,使用了final
,则该方法不能被重写。可以使用parent::
来访问被覆盖的方法或者属性
四、常量
1、类和接口中可以声明常量值(也可以是nowdoc),表示固定不变的数值,用const
关键字声明,无需$
符,如:
class MyClass {
const CONST_VAR = 'xxx';
}
2、从PHP5.3.0开始,可以用一个变量来动态调用类,如:
$className = "MyClass";
$className::CONST_VAR;
五、自动加载
可以使用spl_autoload_register()
来注册一个自动加载函数,当使用尚未定义的类或者接口的时候,将得到一次处理,加载得到所需类的机会。如:
spl_autoload_register(function($className) {
require_once $className . ".php";
});
$obj = new A;
六、抽象类
抽象类只能被继承,不能被实例化
1、抽象类采用abstract
声明,如abstract class Demo { }
2、当子类继承一个抽象类的时候,必须实现抽象类中的所有抽象方法
(抽象类中也可以有普通方法,普通方法可以不实现),而且方法的访问控制
必须和父类一致或者更宽松(如protected的父类方法,子类中可以为protected或者public,不能为更严格的private)
3、抽象类中可以没有抽象方法
4、抽象方法不能有方法体,只能是类似于以下形式:
abstract class A {
abstract protected function foo();
}
七、接口
1、接口中的所有方法必须是共有的(接口的特性),定义接口用interface
关键字,如:
interface iTemplate {
public function setVar($name, $var);
public function getHtml($template);
}
2、实现接口使用implements
,如class A implements B { }
,但是需要注意:
- 可以实现多个接口,用
,
分隔,实现多个接口的时候,接口中的方法不能有重名
3、接口可以继承,使用extends
,如:
interface A {
}
interface B {
}
interface C extends A, B {
}
继承后,加入现在有个类X实现接口C,则X应该实现接口A、B、C中的所有方法
4、接口中可以定义常量,如:
interface A {
const FOO = "Hello";
}
class Demo implements A {
function foo() {
echo A::FOO;
}
}
$p = new Demo();
$p->foo();
八、trait
trait是从PHP5.4起引入的新特性,是一种代码复用的方法,允许无需通过继承来复用代码。trait本身不能实例化,它只能是作为
组件
来组合功能,是一种水平上的组合
1、声明一个trait
用trait
关键字,如:
trait Greet {
public function SayHello() {
echo "Hello";
}
}
2、在类中使用一个trait,用use
,可以同时使用多个trait,如:
class Demo {
use SomeTrait1, SomeTrait2;
/*
也可以是:
use SomeTrait1;
use SomeTrait2;
*/
}
3、同名方法优先级顺序:父类方法 < Trait方法 < 类自身方法
4、Trait冲突方法的解决:使用insteadof
指明使用哪个,使用as
指定别名,如:
trait SayHello {
public function hello() {
echo "Hello ";
}
}
trait SayHello2 {
public function hello() {
echo "Hello 2 ";
}
}
trait SayHello3 {
public function hello() {
echo "Hello 3 ";
}
}
class A {
use SayHello, SayHello2, SayHello3 {
SayHello::hello insteadof SayHello2, SayHello3;
SayHello2::hello as speak;
}
}
$p = new A();
$p->hello(); // 输出“Hello”
$p->speak(); // 输出“Hello 2”
5、as
关键字还可以用来修改访问控制(但是这种情况下,原版的名字不能变),如:
trait SayHello {
public function hello() {
echo "Hello";
}
}
class Demo {
use SayHello {
hello as protected; // 这时候hello方法变成了protected
}
}
// 但如果是以下的情况:
class Demo {
use SayHello {
hello as protected hey;
// 那么hello方法还是public,只是新增了protected的hey方法
}
}
6、一个trait也可以使用其他的trait,如:
trait C {
use A, B;
}
7、trait中可以包括抽象方法,来强制要求类定义该方法,如:
trait A {
abstract public function foo();
}
8、trait中也可以包含静态的方法,也可以定义属性(但是定义了属性后,类中不能定义同名属性,除非类中定义的同名属性和trait中是同样的访问权限和同样的默认值)
九、匿名类
1、PHP7开始支持匿名类,匿名类具有和普通类几乎一致的功能,如:
callDemo(new class {
public function foo() {
echo "Hello";
}
});
2、可以给构造函数传值,也可以继承、实现接口、使用trait,如:
callDemo(new class($params) extends Super implements SomeInterface {
public function __construct($params) {
}
use SomeTrait;
});
3、可以让匿名的嵌套类访问嵌套类外的属性和方法(使用extends
),但是访问private属性需要将属性传入构造器,如:
class Outer {
private $prop = 1;
protected $prop2 = 2;
protected function func1() {
return 3;
}
public function func2() {
return new class($this->prop) extends Outer {
private $prop3;
public function __construct($prop) {
$this->prop3 = $prop;
}
public function func3() {
return $this->func1();
}
}
}
}
4、声明的同一个匿名类,所创建的对象都是这个类的实例。匿名类的名称是引擎赋予的:
function anonymous_class() {
return new class {};
}
get_class(annoymous_class()) === get_class(annoymous_class()); // true
十、重载
PHP提供的重载,是指动态地“创建”类属性和方法,通过魔术方法来实现
1、当调用未定义
和不可见
的属性、方法时,重载方法会被调用,所有的重载方法应该被声明为public
2、魔术方法的参数不能通过引用传递,即function __set(&$var)
是不允许的
3、属性重载:
public function __set($name, $value)
,给不可访问属性赋值时调用
注意:__set()
的返回值将被忽略,所以不能实现如$a = $obj->b = 8
形式的链式调用public function __get($name)
,访问不可访问属性时调用public function __isset($name)
,对不可访问属性调用isset()
或者empty()
时调用public function __unset($name)
,对不可访问属性调用unset()
时调用public function __set($name, $value)
,给不可访问属性赋值时调用
4、静态方法中,魔术方法不会被调用,所以这些方法都不能声明为static
5、方法重载:
public __call($name, $arguments)
调用不可访问方法时候,__call()
会被调用public __callStatic($name, $arguments)
静态上下文中调用不可访问方法的时候被调用,如Class::runSomeMethod()
时,runSomeMethod
不存在,则__callStatic()
被调用
十一、遍历对象
1、可以使用foreach
来遍历每个可见对象属性,如:
class Demo {
private $a = 1;
public $b = 2;
public $c = 3;
public function foo(){}
}
$p = new Demo();
foreach($p as $key => $val) {
echo $key, "=>", $val, PHP_EOL;
}
/*
执行结果:
b=>2
c=>3
*/
class Demo {
private $a = 1;
public $b = 2;
public $c = 3;
public function foo(){
foreach($this as $key => $val) {
echo $key, "=>", $val, PHP_EOL;
}
}
}
$p = new Demo();
$p->foo();
/*
执行结果:
a=>1
b=>2
c=>3
*/
2、可以实现Iterator接口
,来决定如何遍历以及每次遍历的时候哪些值可用,其实就是迭代器模式
,Iterator接口如下:
interface Iterator {
public function rewind();
public function current();
public function key();
public function next();
public function valid();
}
实例如:
class MyIterator implements Iterator {
private $var = array();
public function __construct($array) {
if(is_array($array)) {
$this->var = $array;
}
}
public function rewind() {
echo "Rewiding\n";
reset($this->var);
}
public function current() {
$var = current($this->var);
echo "Current: {$var}\n";
return $var;
}
public function key() {
$var = key($this->var);
echo "Key: {$var}\n";
return $var;
}
public function next() {
$var = next($this->var);
echo "Next: {$var}\n";
return $var;
}
public function valid() {
$var = $this->current() !== false;
echo "Valid: {$var}\n";
return $var;
}
}
$it = new MyIterator([1, 2, 3]);
foreach($it as $key => $val) {
echo "[$key: $val]\n";
}
将输出:
Rewiding
Current: 1
Valid: 1
Current: 1
Key: 0
[0: 1]
Next: 2
Current: 2
Valid: 1
Current: 2
Key: 1
[1: 2]
Next: 3
Current: 3
Valid: 1
Current: 3
Key: 2
[2: 3]
Next:
Current:
Valid:
可见,当一个对象实现了Iterator接口的时候,使用foreach
就会按照实现的Iterator方法来遍历对象。foreach首先调用rewind()
方法,将指针指向数组头部,然后判断是否达到数组末尾(调用valid()
判断),然后调用current()
获得值,调用key()
获得键,然后使用next()
迭代到下一个元素
十二、魔术方法
PHP中所有以__
开头的方法名保留作魔术方法,除了之前介绍的魔术方法外,还有以下的魔术方法:
1、__sleep()和__wakeup()
__sleep()
用于确定serialize()
需要序列化哪些属性,如:
class Demo {
public $a = 1;
public $b = 2;
function __sleep() {
return array('a');
}
}
$a = new Demo();
echo serialize($a); // 返回 O:4:"Demo":1:{s:1:"a";i:1;}
__wakeup()
则会在 unserialize()
调用前执行
2、__toString()
用于指明在将对象当做字符串的时候,应该这么处理,如:
class Demo {
public $a = 1;
public $b = 2;
function __toString() {
return "Hello !!";
}
}
$a = new Demo();
echo $a; // 输出"Hello !!"
注意: 不能在__toString()
中抛出异常,否则会引发致命错误
3、__invoke()
当尝试以函数的形式来使用对象的时候,该方法就会被调用,如:
class Demo {
public $a = 1;
public $b = 2;
function __invoke($a, $b) {
return $a+$b;
}
}
$a = new Demo();
echo $a(3, 7); // 输出:10
4、__set_state()
调用var_export()
的时候,会被执行,即:
class Demo {
public $a = 1;
public $b = 2;
public static function __set_state($arr) {
$obj = new Demo();
$obj->a = $arr['a'];
$obj->b = $arr['b'];
return $obj;
}
}
$a = new Demo();
$a->a = 10;
$a->b = 20;
var_export($a);
/*
输出:
Demo::__set_state(array(
'a' => 10,
'b' => 20,
))
*/
5、__debugInfo()
调用var_dump()
的时候,会被调用。当没有定义这个方法的时候,默认情况下public、protected、private属性都将被展示出来
十三、final关键字
1、如果父类方法被声明为final
,则子类无法重写该父类方法
2、如果一个类被声明为final
,则该类无法被继承
3、属性不能被定义为final
,只有类和方法才可以
十四、对象复制
通常情况下,直接使用=
来复制对象,并没有实现对象的复制,而是将被复制对象的引用复制了一份而已。如下所示:
class A {
public $arr = array(1, 2);
}
$p = new A();
$q = $p;
array_push($q->arr, 3); // 对$q->arr添加元素3
print_r($p->arr); // 为[1, 2, 3]
print_r($q->arr); // 为[1, 2, 3]
1、对象复制,使用clone
关键字来完成(如果类中定义了__clone()
魔术方法,将执行__clone()
魔术方法):
class A {
public $arr = array(1, 2);
}
$p = new A();
$q = clone $p;
array_push($q->arr, 3); // 对$q->arr添加元素3
print_r($p->arr); // 为[1, 2]
print_r($q->arr); // 为[1, 2, 3]
2、但是clone
关键字实现的只是浅复制
(只实现了一层的复制),对于类内部的对象,只是复制了引用地址,如下所示:
class A {
public $a = 1;
}
class B {
public $instance;
function __construct() {
$this->instance = new A();
}
}
$p = new B();
$q = clone $p;
var_dump( $p->instance === $q->instance ); // bool(true)
如果要解决这个问题,则应当在类里使用__clone()
魔术方法,来对类内部的对象也进行克隆,如:
class A {
public $a = 1;
}
class B {
public $instance;
function __construct() {
$this->instance = new A();
}
function __clone() {
$this->instance = clone $this->instance;
}
}
$p = new B();
$q = clone $p;
var_dump( $p->instance === $q->instance ); // bool(false)
十五、类型约束
从PHP5.1
其引入了类型约束,用于限制参数的类型,类型约束可以用在方法里,也可以用在函数里
1、类型必须是对象(类的名字)、接口、数组或者callable
(PHP5.4+引入)。如果传入的参数不符合类型约束的类型,那么就会报错
2、如果参数有个默认值NULL
,那么传入NULL的时候不会报错(也可以不传参)
十六、后期静态绑定
后期绑定是PHP5.3引入的新特性,可以使得调用静态方法的时候,绑定的类取决于调用的类,由于在继承范围内引用静态调用的类
1、self::
的限制:self只能获得定义它本身的类
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); // 输出A
2、上面的例子,B类继承A类方法,得到的是A类的引用。现在,我们希望得到继承的方法,但是调用的方法中用到了自身引用
的时候是子类自身,那么后期静态绑定就可以发挥作用了。使用static::
就能够使得类的引用是运行时决定,它取决于调用它的类!!所以有:
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); // 输出B
3、在非静态上下文中,使用static::
不会得到预期的效果。因为static::
仅仅只能用于静态对象。
class A {
private function foo() {
echo __CLASS__;
}
public function test() {
$this->foo();
static::foo();
}
}
class B extends A {
}
class C extends A {
private function foo() {
}
}
// 此时调用者是$p1,而$p1所属的类是B,但是结果会输出“AA”
$p1 = new B();
$p1->test();
// 此时C重写了A的方法,类外不可调用私有方法,所以会报错
$p2 = new C();
$p2->test();