php面向对象

发布时间 2024-01-07 03:18:54作者: xirang熙攘

1.1 今日目标

  1. 了解面向对象的基本概念;
  2. 了解面向对象和面向过程的区别;
  3. 掌握面向对象的基本语法:封装类;
  4. 掌握类成员的基本使用:定义和访问;
  5. 掌握访问修饰限定符的作用和实际运用;
  6. 掌握$this的概念和使用;
  7. 掌握构造方法的和析构方法的目的和触发时间;
  8. 了解对象存储和访问的内存原理;
  9. 了解对象比较的方式;
  10. 掌握对象与类在内存在依赖关系;

1.2 面向对象介绍

1.2.1 介绍

面向对象是一个编程思想。编程思想有面向过程和面向对象

面向过程:编程思路集中的是过程上

面向对象:编程思路集中在参与的对象

以去饭馆吃饭为例:

​ 面向过程:点菜——做菜——上菜——吃饭——结账——收拾

​ 面向对象:服务员,厨师,客人

1.2.2 面向对象的好处

  1. 多人合作方便
  2. 减少代码冗余,灵活性高
  3. 代码的可重用性发挥到极致
  4. 可扩展性强
多学一招:
OOP:面向对象编程(Object Oriented Programming,面向对象编程)
OOA: 面向对象分析(Object-Oriented Analysis,OOA)
OOD: 面向对象设计(Object-Oriented Design,OOD)

1.3 类和对象

1、对象是具体存在的事物,对象是由属性(变量)和方法(函数)组成的

1560565659019

2、类是具有相同属性和行为的一组对象的集合

分析:做菜动作——厨师对象——厨师类
结论:我们在开发的时候,先写类,通过类创建对象,然后调用对象的属性和方法实现功能。 类——对象——调用成员

注意:一个类可以创建多个对象

1560566059019

小结:

1、对象是由属性和方法组成的

2、类是所有对象的相同属性和方法的集合

3、在开发的时候先写类,通过类创建对象,通过对象调用方法和属性

4、一个类可以创建多个对象

1.4 在PHP中实现类和对象

1.4.1 创建类

语法:

class 类名{
	//属性
	//方法
	//常量
}
类是由属性、方法、常量组成的,也可以说
类成员有:属性、方法、常量    

类名的命名规则:

  1. 以字母、下划线开头,后面跟的是字母、数字、下划线
  2. 不能用PHP关键字做类名
  3. 类名不区分大小写(变量名区分,关键字、类名不区分大小写)
  4. 类名用帕斯卡命名法(大驼峰 单词的首字母大写)
<?php
class Student {
}

1.4.2 对象实例化

通过new关键字来实例化对象。

<?php
//定义类
class Student {
	
}
//实例化对象
$stu1=new Student();
$stu2=new Student;		//小括号可以省略
var_dump($stu1,$stu2);	//object(Student)#1 (0) { } object(Student)#2 (0) { } 

1.4.3 对象的比较

注意:对象的传递是地址传递

相等:结构和保存的值一样就相等
全等:指向同一个对象才是全等。

<?php
//定义类
class Student {
	
}
//实例化对象
$stu1=new Student();
$stu2=new Student;
$stu3=$stu2; //对象传递的是地址	
//var_dump($stu1,$stu2,$stu3); //object(Student)#1 (0) { } object(Student)#2 (0) { } object(Student)#2 (0) { } 
//对象比较
var_dump($stu1==$stu2);		//bool(true) ,比较对象的结构
echo '<br>';
var_dump($stu1===$stu2);	//bool(false) $stu1和$stu2是否是同一个对象
echo '<br>';
var_dump($stu2===$stu3);	//bool(true) $stu2和$stu3是同一个对象

1.5 属性

属性本质就是变量

通过->调用对象的成员 对象名->属性名 对象名->方法名()

<?php
//定义类
class Student {
	public $name;				//属性
	public $add='地址不详';		//属性
}
//实例化对象
$stu=new Student();
//print_r($stu);	//Student Object ( [name] => [add] => 地址不详 ) 
//操作属性
//1、给属性赋值
$stu->name='tom';
$stu->add='北京';

//2、获取属性的值
echo '姓名:'.$stu->name,'<br>';	//姓名:tom
echo '地址:'.$stu->add,'<br>';		//地址:北京

//3、添加属性
$stu->age=20;
print_r($stu);	//Student Object ( [name] => tom [add] => 北京 [age] => 20 ) 
echo '<br>';
//4、删除属性
unset($stu->add);
print_r($stu);	//Student Object ( [name] => tom [age] => 20 ) 

1.6 方法

方法的本质就是函数

<?php
class Student {
	//定义方法
	public function show() {
		echo '这是show方法<br>';
	}
	//public可以省略,如果省略,默认就是public
	function test() {
		echo '这是test方法<br>';
	}
}
$stu=new Student;
$stu->show();	//调用方法
$stu->test();

多学一招:

1、方法前面public是可以省略的,如果省略,默认就是public的。

2、属性前面的public不能省略

1.7 访问修饰符

用来控制成员的访问权限

修饰符 描述
public(公有的) 在类的内部和外部都能访问
private(私有的) 只能在类的内部访问
protected(受保护的) 在整个继承链上访问

多学一招:一般来说,属性都用私有的,通过公有的方法对私有的属性进行赋值和取值。

作用:保证数据的合法性

<?php
//访问修饰符
class Student {
	private $name;	//私有属性
	private $sex;	//私有属性
	//通过公有的方法对私有的属性进行赋值
	public function setInfo($name,$sex) {
		if($sex!='男' && $sex!='女'){
			echo '性别必须是男或女';
			exit;
		}
		$this->name=$name;   //$this表示当前对象
		$this->sex=$sex;
	}
	//显示信息
	public function getInfo() {
		echo '姓名:'.$this->name,'<br>';
		echo '性别:'.$this->sex,'<br>';
	}
}
//实例化
$stu=new Student;
$stu->setInfo('tom','男');
$stu->getInfo();
echo '<hr>';
$stu2=new Student;
$stu2->setInfo('berry','女');
$stu2->getInfo();

提示:$this表示调用当前方法的对象

运行结果

1560569427377

1.8 类和对象在内存中的分布

  1. 对象的本质是一个复杂的变量
  2. 类的本质是一个自定义的复杂数据类型
  3. 栈区:运行速度快,体积小,保存基本类型
  4. 堆区:运行速度稍慢,体积大,保存复杂类型
  5. 实例化的过程就是分配内存空间的过程
  6. 对象保存在堆区,将堆区的地址保存到栈区。

分析如下代码的结构

<?php
class Student {
	public $name;
	public $sex;
	public function show() {
	}
}

$stu1=new Student;
$stu2=new Student;

$stu1->show();

示意图

1560571020035

1.9 封装

封装就是有选择性的提供数据

通过访问修饰符来实现封装

1.10 构造方法

1.10.1 介绍

构造方法也叫构造函数,当实例化对象的时候自动执行。
语法:

function __construct(){
}
注意:前面是两个下划线

例题

<?php
class Student {
	public function __construct() {
		echo '这是构造方法<br>';
	}
}
new Student();	//这是构造方法
new Student();	//这是构造方法

注意:在其他语言里,与类名同名的函数是构造函数,在PHP中不允许这种写法。

class Student {
	//和类名同名的方法是构造方法,PHP中不建议使用
	public function Student() {
		echo '这是构造方法<br>';
	}
}
/*
Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Student has a deprecated constructor in F:\wamp\www\6-demo.php on line 2
这是构造方法
*/

1.10.2 构造函数作用:初始化成员变量

<?php
class Student {
	private $name;
	private $sex;
	//构造函数初始化成员变量
	public function __construct($name,$sex) {
		$this->name=$name;
		$this->sex=$sex;
	}
	//显示信息
	public function show() {
		echo "姓名:{$this->name}<br>";
		echo "性别:{$this->sex}<br>";
	}
}
//实例化
$stu=new Student('tom','男');
$stu->show();
//运行结果
/*
姓名:tom
性别:男
*/

注意:构造函数可以带参数,但不能有return。

1.11 析构方法

1.11.1 介绍

当对象销毁的时候自动调用

语法

function __destruct(){
}

脚下留心:析构函数不可以带参数

例题

<?php
class Student {
	private $name;
	//构造方法
	public function __construct($name) {
		$this->name=$name;
		echo "{$name}出生了<br>";
	}
	//析构方法
	public function __destruct() {
		echo "{$this->name}销毁了<br>";
	}
}
//测试
$stu1=new Student('tom');
$stu2=new Student('berry');
$stu3=new Student('ketty');
echo '<hr>';

运行结果

1560582573840

1.11.2 计算机的内存管理

计算机内存管理方式:先进先出,先进后出

先进先出的内存管理方式一般用在业务逻辑中,比如秒杀、购票等等

1560582771151

先进后出是计算机的默认内存管理方式

1560582861155

1.11.3 思考题

思考题1

<?php
class Student {
	private $name;
	//构造方法
	public function __construct($name) {
		$this->name=$name;
		echo "{$name}出生了<br>";
	}
	//析构方法
	public function __destruct() {
		echo "{$this->name}销毁了<br>";
	}
}
//测试
$stu1=new Student('tom');
$stu2=new Student('berry');
$stu3=new Student('ketty');
unset($stu2);
echo '<hr>';
/*
tom出生了
berry出生了
ketty出生了
berry销毁了

ketty销毁了
tom销毁了
*/

思考题2

<?php
class Student {
	private $name;
	//构造方法
	public function __construct($name) {
		$this->name=$name;
		echo "{$name}出生了<br>";
	}
	//析构方法
	public function __destruct() {
		echo "{$this->name}销毁了<br>";
	}
}
//测试
new Student('tom');
new Student('berry');
new Student('ketty');
/*
tom出生了
tom销毁了
berry出生了
berry销毁了
ketty出生了
ketty销毁了
*/

思考题3

<?php
class Student {
	private $name;
	//构造方法
	public function __construct($name) {
		$this->name=$name;
		echo "{$name}出生了<br>";
	}
	//析构方法
	public function __destruct() {
		echo "{$this->name}销毁了<br>";
	}
}
//测试
$stu=new Student('tom');
$stu=new Student('berry');
$stu=new Student('ketty');
/*
tom出生了
berry出生了
tom销毁了
ketty出生了
berry销毁了
ketty销毁了
*/

1.12 继承

1.12.1 继承介绍

  1. 继承使得代码具有层次结构
  2. 子类继承了父类的属性和方法,实现了代码的可重用性。
  3. 使用extends关键字实现继承
  4. 父类和子类是相对的

语法

class 子类 extends 父类{
}

例题

<?php
//父类
class Person {
	public function show() {
		echo '这是人类<br>';
	}
}
//子类继承父类
class Student extends Person {
}
//测试
$stu=new Student;
$stu->show();			//这是人类

执行过程:

第一步:在Student类中查找show(),如果找到就调用,找不到就到父类中查找

第二步:在Person类中查询show()

1.12.2 子类中调用父类成员

<?php
//父类
class Person {
	public function show() {
		echo '这是人类<br>';
	}
}
//子类
class Student extends Person {
	public function test() {
		//方法一;
		/*
		$person=new Person();
		$person->show();		//这是人类
		*/
		//方法二
		$this->show();			//这是人类
	}
}
//测试
$stu=new Student;
$stu->test();

小结:

1、方法一:通过实例化父类调用父类的成员

2、方法二:通过$this关键字调用父类的成员

1.12.3 protected

protected:受保护的,在整个继承链上使用

例题:

//例题一:
<?php
class A {
	protected $num=10;	//在整个继承链上访问
}
class B extends A {	
	public function getNum() {
		echo $this->num;
	}
}
//测试
$obj=new B();    //整个继承链上有A和B
$obj->getNum();		//10

//例题二:
<?php
class A {
	public function getNum() {
		echo $this->num;
	}
}
class B extends A {
	protected $num=10;	
}
//测试
$obj=new B();	//整个继承链上有A和B
$obj->getNum();		//10

//例题三:
<?php
class A {
	public function getNum() {
		echo $this->num;
	}
}
class B extends A {
	protected $num=10;	
}
//测试
$obj=new A();     //整个继承链上只有A
$obj->getNum();	 //Notice: Undefined property: A::$num 

1.12.4 继承中的构造函数

规则:

1、如果子类有构造函数就调用子类的,如果子类没有就调用父类的构造函数。

2、子类的构造函数调用后,默认不再调用父类的构造函数

通过类名调用父类的构造函数

类名::__construct()

例题

<?php
class Person {
    //父类的构造函数
	public function __construct() {
		echo '这是父类<br>';
	}
}
class Student extends Person {
    //子类的构造函数
	public function __construct() {
		Person::__construct();		//通过父类的名字调用父类的构造函数
		parent::__construct();		//parent表示父类的名字
		echo '这是子类<br>';
	}
}
//测试
new Student();

注意:parent关键字表示父类的名字,可以降低程序的耦合性

例题:给父类传递参数

<?php
class Person {
	protected $name;
	protected $sex;
    //父类的构造函数
	public function __construct($name,$sex) {
		$this->name=$name;
		$this->sex=$sex;
	}
}
class Student extends Person {
	private $score;
    //子类的构造函数
	public function __construct($name,$sex,$score) {
		parent::__construct($name,$sex);  //调用父类构造函数并传递参数
		$this->score=$score;
	}
    //显示信息
	public function getInfo() {
		echo "姓名:{$this->name}<br>";
		echo "性别:{$this->sex}<br>";
		echo "成绩:{$this->score}";
	}
}
//测试
$stu=new Student('tom','男',88);
$stu->getInfo();
/*
姓名:tom
性别:男
成绩:88
*/

1.12.5 $this详解

$this表示当前对象的引用,也就是是或$this保存的当前对象的地址

<?php
class A {
	public function __construct() {
		var_dump($this);
	}
}
class B extends A {
	
}
new A();	//object(A)#1 (0) { } 
echo '<br>';
new B();	//object(B)#1 (0) { } 

1.12.6 多重继承

PHP不允许多重继承,因为多重继承容易产生二义性

1560588666237

如何实现C继承A和B,使用继承链

1560588751879

2.1 今日目标

  1. 掌握静态成员的意义和应用;
  2. 掌握self关键字的实际运用;
  3. 了解面向对象的三大特性:封装、继承和多态;
  4. 掌握PHP中继承机制和基本语法;
  5. 掌握访问修饰限定符protected的原理和实际运用;
  6. 掌握重写override的作用和实际应用;
  7. 了解PHP中继承的特点;
  8. 了解静态延迟绑定的作用和使用;
  9. 掌握最终类的作用以及具体的使用方式;
  10. 掌握抽象类的作用以及具体的使用方式;
  11. 掌握接口的作用以及具体的使用方式;

2.2 多态

多态:多种形态。

多态分为两种:方法重写和方法重载

2.2.1 方法重写

子类重写了父类的同名的方法

<?php
//父类
class Person {
	public function show() {
		echo '这是父类<br>';
	}
}
//子类
class Student extends Person {
	//子类重写了父类的同名方法
	public function show() {
		echo '这是子类<br>';
	}
}
//测试
$stu=new Student;
$stu->show();			//这是子类

注意事项:

  1. 子类的方法必须和父类的方法同名
  2. 参数个数要一致
  3. 子类修饰的不能比父类更加严格

1560736886382

1560737021928

2.2.2 方法重载

在同一个类中,有多个同名的函数,通过参数的不同来区分不同的方法,称为方法重载

1560738378096

注意:PHP不支持方法重载,但是PHP可以通过其他方法来模拟方法重载。

2.3 面向对象三大特性

  1. 封装
  2. 继承
  3. 多态

2.4 私有属性继承和重写

私有属性可以继承但不能重写。

<?php
class A {
	private $name='PHP';
	public function showA() {
		//var_dump($this);	//object(B)#1 (2) { ["name":"B":private]=> string(4) "Java" ["name":"A":private]=> string(3) "PHP" } 
		echo $this->name,'<br>';	//PHP
	}
}
class B extends A {
	private $name='Java';
	public function showB() {
		//var_dump($this);	//object(B)#1 (2) { ["name":"B":private]=> string(4) "Java" ["name":"A":private]=> string(3) "PHP" } 
		echo $this->name,'<br>';	//Java
	}
}
$obj=new B();
$obj->showA();
$obj->showB();
/*分析:
showA()和showB()中的$this都表示B的对象,B中继承了A的私有属性,所以B中有两个$name.
在showA()中只能访问A中的$name,不能访问B中的$name
在showB()中只能访问B中的$name,不能访问A中的$name
*/

练习一

<?php
class A {
	protected $name='tom';	
	public function showA() {
		echo $this->name,'<br>';
	}
}
class B extends A {
	public $name='berry';
	public function showB() {
		echo $this->name,'<br>';
	}
}
//测试
$obj=new B();
$obj->showA();	//berry
$obj->showB();	//berry

/*
分析:B中将A的$name重写,所以$obj中只有一个$name,($name='berry'),不管$this在哪个方法中访问,就只能访问这个$name
*/

练习二

<?php
class A {
	private $name='tom';	
	public function showA() {
		echo $this->name,'<br>';
	}
}
class B extends A {
	public $name='berry';
	public function showB() {
		echo $this->name,'<br>';
	}
}
//测试
$obj=new B();
$obj->showA();	//tom
$obj->showB();	//berry
/*
分析:
$obj中有两个$name,一个是私有的,一个是公有的
在showA()中既能访问私有的$name,也能访问公有的$name,但是私有的比公有的权限高,所以输出tom
在showB()中不能访问私有的$name,只能访问公有的$name,所以输出berry
*/

2.5 方法修饰符

方法修饰符有:static、final、abstract

2.5.1 static【静态的】

  1. static修饰的属性叫静态属性、static修饰的方法叫静态方法
  2. 静态成员加载类的时候分配空间,程序执行完毕后销毁
  3. 静态成员在内存中就一份。
  4. 调用语法 类名::属性 类名::方法名()
<?php
class Person {
	public static $add='北京';    // 修饰符之间没有顺序
	static public function show() {
		echo '这是一个静态的方法<br>';
	}
}
echo Person::$add,'<br>';		//北京
Person::show();					//这是一个静态的方法

练习:统计在线人数

<?php
class Student {
	private static $num=0;	//静态变量,在内存中就一份
	public function __construct() {
		self::$num++;      //self表示所在类的类名
	}
	public function __destruct() {
		self::$num--;
	}
	public function show() {
		echo '总人数是:'.self::$num,'<br>';
	}
}
//测试
$stu1=new Student;
$stu2=new Student;
$stu3=new Student;
$stu2->show();			//总人数是:3
unset($stu2);
$stu3->show();			//总人数是:2

注意:self表示所在类的类名,使用self降低耦合性

静态成员也可以被继承

<?php
class Person {
	public static $add='中国';
	public static function show() {
		echo '这是人类<br>';
	}
}
//继承
class Student extends Person {	
}
//测试
echo Student::$add,'<br>';		//中国   通过子类名称访问父类的静态成员
Student::show();				//这是人类

静态延时绑定

static表示当前对象所属的类

<?php
class Person {
	public static $type='人类';
	public function show1() {
		//var_dump($this);		//object(Student)#1 (0) { } 
		//echo self::$type,'<br>';	//人类
		echo static::$type,'<br>';			//学生   延时绑定
	}
}
class Student extends Person {
	public static $type='学生';
	public function show2() {
		//var_dump($this);		//object(Student)#1 (0) { } 
		//echo self::$type,'<br>';	//学生
		echo static::$type,'<br>';			//学生
	}
}
//测试
$obj=new Student();
$obj->show1();
$obj->show2();

小结:

1、static在内存中就一份,在类加载的时候分配空间

2、如果有多个修饰符,修饰符之间是没有顺序的

3、self表示所在类的类名

4、static表示当前对象所属的类

5、static有两个作用,第一表示静态的,第二表示类名

2.5.2 final【最终的】

final修饰的方法不能被重写

final修饰的类不能被继承

1560743854237

1560743910916

作用

1、如果一个类确定不被继承,一个方法确定不会被重写,用final修饰可以提高执行效率。

2、如果一个方法不允许被其他类重写,可以用final修饰。

2.5.3 abstract【抽象的】

  1. abstract修饰的方法是抽象方法,修饰的类是抽象类
  2. 只有方法的声明没有方法的实现称为抽象方法
  3. 一个类中只要有一个方法是抽象方法,这个类必须是抽象类。
  4. 抽象类的特点是不能被实例化
  5. 子类继承了抽象类,就必须重新实现父类的所有的抽象方法,否则不允许实例化
  6. 类中没有抽象方法也可以声明成抽象类,用来阻止类的实例化

例题

<?php
//抽象类
abstract class Person {
	public abstract function setInfo();	//抽象方法
	public function getInfo() {
		echo '获取信息<br>';
	}
}
//继承
class Student extends Person {
    //重写实现父类的抽象方法
	public function setInfo() {
		echo '重新实现父类的抽象方法<br>';
	}
}
//测试
$stu=new Student;
$stu->setInfo();		//重新实现父类的抽象方法
$stu->getInfo();		//获取信息

抽象类的作用:

1定义命名规范

1560753765133

2、阻止实例化,如果一个类中所有的方法都是静态方法,这时候没有必要去实例化,可以通过abstract来阻止来的实例化。

2.6 类常量

类常量是const常量

<?php
class Student {
	//public const ADD; 	//7.1以后才支持访问修饰符
	const ADD='地址不详';
}
echo Student::ADD;

问题:define常量和const常量的区别?

答:const常量可以做类成员,define常量不可以做类成员。

问题:常量和静态的属性的区别?

答:相同点:都在加载类的时候分配空间

​ 不同点:常量的值不可以更改,静态属性的值可以更改

2.7 接口(interface)

2.7.1 接口

  1. 如果一个类中所有的方法是都是抽象方法,那么这个抽象类可以声明成接口
  2. 接口是一个特殊的抽象类,接口中只能有抽象方法和常量
  3. 接口中的抽象方法只能是public,可以省略,默认也是public的
  4. 通过implements关键字来实现接口
  5. 不能使用abstract和final来修饰接口中的抽象方法。
<?php
//声明接口
interface IPerson {
	const ADD='中国';
	function fun1();
	function fun2();
}
//接口实现
class Student implements IPerson {
	public function fun1() {
		
	}
	public function fun2() {
		
	}
}
//访问接口中的常量
echo IPerson::ADD;

2.7.2 接口的多重实现

类不允许多重继承,但是接口允许多重实现。

<?php
interface IPic1 {
	function fun1();
}
interface IPic2 {
	function fun2();
}
//接口允许多重实现
class Student implements IPic1,IPic2 {
	public function fun1() {
		
	}
	public function fun2() {
		
	}
}

注意:

1、在接口的多重实现中,如果有同名的方法,只要实现一次即可

2、类可以继承的同时实现接口

class Student extends Person implements IPIc1,IPic1{
    
}

2.8 匿名类

这是了解的内容,PHP7.0支持

<?php
$stu=new class {
	public $name='tom';
	public function __construct() {
		echo '构造函数<br>';
	}
};
echo $stu->name;
/*运行结果;
构造函数
tom
*/

小结:

1、如果类只被实例化一次就可以使用匿名类

2、好处,在执行的过程中,类不占用空间

2.9 方法绑定

这是了解的内容,PHP7.0支持

作用:将方法绑定到对象上,并调用

语法:

闭包->call(对象):将闭包绑定到对象上,并调用

在PHP中匿名函数称为闭包

例题

<?php
$lang='en';
//类
class Student{
}
//匿名函数
if($lang=='ch'){
	$fun=function(){
		echo '我是一名学生';
	};
}else{
	$fun=function(){
		echo 'i am a studnet';
	};
}
//绑定
$stu=new Student;
$fun->call($stu);	//i am a studnet

2.10 异常处理

集中处理在代码块中发生的异常。

在代码块中发生了异常直接抛出,代码块中不处理异常,将异常集中起来一起处理。

2.10.1 使用的关键字

try:监测代码块
catch:捕获异常
throw:抛出异常
finally:无论有无异常都会执行,可以省略
Exception:异常类

语法结构

try{
	//检测代码
}catch(Exception $ex){
	//捕获异常
}
finally{
	//不论是否有异常,都要执行,finally可以省略
}

例题:

<?php
if(isset($_POST['button'])) {
	try{
		$age=$_POST['age'];
		if($age=='')
			throw new Exception('年龄不能为空',1001);	//抛出异常
		if(!is_numeric($age))
			throw new Exception('年龄必须是数字',1001);	//抛出异常
		if(!($age>=10 && $age<=30)) 
			throw new Exception('年龄必须在10-30之间',1002);	//抛出异常
		echo '您的年龄合适';
	}catch(Exception $ex){		//捕获异常
		echo '错误信息:'.$ex->getMessage(),'<br>';
		echo '错误码:'.$ex->getCode(),'<br>';
		echo '文件地址:'.$ex->getFile(),'<br>';
		echo '错误行号:'.$ex->getLine(),'<br>';
	}
	finally{
		echo '关闭数据库连接';  //不管是否有异常,finally都要执行
	}
}
?>
<form method="post" action="">
	年龄: <input type="text" name="age"> <br />
	<input type="submit" name="button" value="提交">
</form>

注意:抛出异常后,try块终止执行,执行权限交给catch块.

运行结果

1560759058383

2.8.2 自定义异常

场景:如果实现异常的分类处理?比如异常有三个级别异常对应三种处理方式

自定义三种异常即可

所有异常类的父类是Exception,Exception中的方法不允许重写

<?php
//自定义空异常类
class MyNullException extends Exception {
}
//自定义类型异常
class MyTypeException extends Exception {
}
//自定义范围异常
class MyRangeException extends Exception {
}
//逻辑代码
if(isset($_POST['button'])) {
	try{
		$name=$_POST['name'];
		$age=$_POST['age'];
		if($name=='')
			throw new MyNullException('姓名不能为空');
		if($age=='')
			throw new MyNullException('年龄不能为空');
		if(!is_numeric($age))
			throw new MyTypeException('年龄不是数字');
		if($age<10 || $age>30)
			throw new MyRangeException('年龄必须在10-30之间');
		echo '姓名:'.$name,'<br>';
		echo '年龄:'.$age;
	}catch(MyNullException $ex){
		echo $ex->getMessage(),'<br>';
		echo '错误记录在日志中';
	}catch(MyTypeException $ex){
		echo $ex->getMessage(),'<br>';
		echo '发送电子邮件';
	}catch(MyRangeException $ex){
		echo $ex->getMessage(),'<br>';
		echo '给管理员打电话';
	}

}
?>
<form method="post" action="">
	姓名: <input type="text" name="name"> <br />
	年龄: <input type="text" name="age"> <br />
	<input type="submit" name="button" value="提交">
</form>

2.9 作业实现

1、打印图像

<body>
<style type="text/css">
	body{
		text-align:center;
		font-size:25px;
	}
	span{
		width:30px;
		height:10px;
		display:inline-block;
	}
</style>
<?php
for($i=1;$i<=9;$i++){
	$n=$i>5?(10-$i):$i;
	$k=2*$n-1;			//星星的个数
	for($j=1;$j<=$k;$j++){
		echo '<span>*</span>';
	}
	echo '<br>';
}
?>
</body>

1560760166882

2、打印回形

<body>
<style type="text/css">
	body{
		text-align:center;
		font-size:25px;
	}
	span{
		width:30px;
		height:10px;
		display:inline-block;
	}
</style>
<?php
for($i=1; $i<=10; $i++) {
	for($j=1; $j<=10; $j++) {
		if($i>=3 && $i<=8 && $j>=3 && $j<=8)
			echo '<span></span>';
		else
			echo '<span>*</span>';
	}
	echo '<br>';
}
?>
</body>

1560760212372

3.1 今日目标

  1. 掌握类的自动加载原理和实际应用;
  2. 了解对象的克隆意义;
  3. 掌握PHP重载的意义以及具体重载的应用;
  4. 了解对象的序列化和反序列化操作以及注意事项
  5. 掌握foreach对对象进行遍历;
  6. 掌握Mysqli的类封装以及应用;

3.2 自动加载类

在项目开发中,因为一个文件中只能写一个类,并且在执行过程中会有很多的类参与,如果一个一个的加载很麻烦,所以,就需要一个机制实现在PHP执行过程中自动加载需要的类。

3.2.1 类的规则

  1. 一个文件中只能放一个类(必须)
  2. 文件名和类名同名(必须)
  3. 类文件以.class.php结尾(不是必须)

3.2.2 手动加载类

1、创建Goods.class.php页面

<?php
//商品类
abstract class Goods {
	protected $name;
	final public function setName($name) {
		$this->name=$name;	
	}
	public abstract function getName();
}

2、创建Book.class.php页面

<?php
//图书类
class Book extends Goods {
	public function getName() {
		echo "《{$this->name}》<br>";
	}
}

3、创建Phone.class.php页面

<?php
//电话类
class Phone extends Goods {
	public function getName() {
		echo $this->name,'<br>';
	}
}

4、在PHP页面上加载类文件

<?php
require './Goods.class.php';    //手动加载类文件
require './Book.class.php';		//手动加载类文件
require './Phone.class.php';	//手动加载类文件
//测试
$book=new Book();
$book->setName('面向对象编程');
$phone=new Phone();
$phone->setName('苹果6s');
$book->getName();
$phone->getName();

运行结果

1560824628303

3.2.3 自动加载类

当缺少类的时候自动的调用__autoload()函数,并且将缺少的类名作为参数传递给__autoload()

<?php
/*
*作用:自动加载类
*@param $class_name string 缺少的类名
*/
function __autoload($class_name) {
	require "./{$class_name}.class.php";
}
//测试
$book=new Book();
$book->setName('面向对象编程');
$phone=new Phone();
$phone->setName('苹果6s');
$book->getName();
$phone->getName();

注意:__autoload()函数在PHP7.2以后就不支持了。

3.2.4 注册加载类

通过spl_autoload_register()注册__autoload()函数

<?php
//方法一:
/*
//加载类函数
function loadClass($class_name) {
	require "./{$class_name}.class.php";
}
//注册加载类函数
spl_autoload_register('loadClass');
*/

//方法二:
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});

//测试
$book=new Book();
$book->setName('面向对象编程');
$phone=new Phone();
$phone->setName('苹果6s');
$book->getName();
$phone->getName();

1、spl_autoload_register()可以注册多个自动加载函数

<?php
function load1($class) {
	require "./{$class}.class.php";
}
function load2($class) {
	require "./{$class}.php";
}
function load3($class) {
	require "./{$class}.fun.php";
}
spl_autoload_register('load1');
spl_autoload_register('load2');
spl_autoload_register('load3');

2、PHP5.1以后就开始支持此函数。

3.2.5 类文件存储不规则的加载方法

将类名和文件地址做一个映射,组成一个关联数组。

$map=array(
    //类名	=>	类文件地址
	'Goods'	=>	'./aa/Goods.class.php',
	'Book'	=>	'./bb/Book.class.php',
	'Phone'	=>	'./cc/Phone.class.php'
);

代码如下

<?php
spl_autoload_register(function($class_name){
	//类名和文件地址映射成一个关联数组
	$map=array(
		'Goods'	=>	'./aa/Goods.class.php',
		'Book'	=>	'./bb/Book.class.php',
		'Phone'	=>	'./cc/Phone.class.php'
	);
	//在映射数组中找到就包含
	if(isset($map[$class_name]))
		require $map[$class_name];
});
//测试
$book=new Book();
$book->setName('面向对象编程');
$phone=new Phone();
$phone->setName('苹果6s');
$book->getName();
$phone->getName();

在项目中,绝大部分都是规则存储的,不规则的比较少。

3.3 clone和__clone()

思考:创建对象的方式有哪些?

方法一:实例化
方法二:克隆

例题

<?php
class Student {
    //执行clone指令的时候自动执行
	public function __clone() {
		echo '正在克隆对象<br>';
	}
}
$stu1=new Student;
$stu2=clone $stu1;		//克隆对象
var_dump($stu1,$stu2);  //object(Student)#1 (0) { } object(Student)#2 (0) { } 

小结:

1、clone的创建对象的方法之一

2、当执行clone指令的时候,会自动的调用__clone()方法

3.4 设计模式

3.4.1 单例模式

一个类只能有一个对象

应用场景:多次请求数据库只需要一个连接对象。

实现:三私一公

1、私有的静态属性用来保存对象的单例
2、私有的构造方法用来阻止在类的外部实例化
3、私有的__clone阻止在类的外部clone对象
4、公有的静态方法用来获取对象的单例

代码

<?php
//三私一公
class DB {
	//静态的属性用来保存对象的单例
	private static $instance;
	//私有的构造方法阻止在类的外部实例化
	private function __construct() {
		
	}
	//私有的__clone()阻止在类的外部clone对象
	private function __clone() {
		
	}
	public static function getInstance() {
		//保存的值不属于DB类的类型就实例化
		if(!self::$instance instanceof self)
			self::$instance=new self();
		return self::$instance;
	}
}
//测试
$db1=DB::getInstance();
$db2=DB::getInstance();
var_dump($db1,$db2);	//object(DB)#1 (0) { } object(DB)#1 (0) { } 

3.4.2 工厂模式

特点:传递不同的参数获取不同的对象

<?php
class ProductsA {
}
class ProductsB {	
}
//工厂模式
class ProductsFactory {
	public function create($num) {
		switch($num) {
			case 1:
				return new ProductsA;
			case 2:
				return new ProductsB;
			default:
				return null;
		}
	}
}
//测试
$factory=new ProductsFactory();
$obj1=$factory->create(1);
$obj2=$factory->create(2);
var_dump($obj1,$obj2); //object(ProductsA)#2 (0) { } object(ProductsB)#3 (0) { } 

3.4.3 策略模式

特点:传递不同的参数调用不同的策略(方法)

<?php
class Walk {
	public function way() {
		echo '走着去<br>';
	}
}
class Bus {
	public function way() {
		echo '坐车去<br>';
	}
}
//策略模式
class Student {
	public function play($obj) {
		$obj->way();
	}
}
//测试
$stu=new Student;
$stu->play(new Walk());	//走着去
$stu->play(new Bus());	//坐车去

3.5 序列化与反序列化

在PHP中,数组和对象无法保存,如果需要保存就要将数组或对象转换成一个序列。

序列化:将数组或对象转换成一个序列(serialize)

反序列化:将序列化的字符串转换成数组或对象。(unserialize)

3.5.1 数组的序列化与反序列化

<?php
//数组的序列化
/*
$stu=['tom','berry','ketty'];
$str=serialize($stu);		//序列化
file_put_contents('./stu.txt',$str);
*/

//数组的反序列化
$str=file_get_contents('./stu.txt');
$stu=unserialize($str);		//反序列化
print_r($stu);	//Array ( [0] => tom [1] => berry [2] => ketty ) 

3.5.2 对象的序列化与反序列化

注意:对象的反序列化需要有类的参与,如果没有类在反序列化时候无法确定类

1560841768999

代码

<?php
class Student {
	public $name;
	protected $sex;
	private $add;
	public function __construct($name,$sex,$add) {
		$this->name=$name;
		$this->sex=$sex;
		$this->add=$add;
	}
}
/*
//测试
$stu=new Student('tom','男','北京');
//序列化
$str=serialize($stu);
file_put_contents('./stu.txt',$str);
*/

//反序列化,类的反序列化必须要有类的参与
$str=file_get_contents('./stu.txt');
$stu=unserialize($str);
echo '<pre>';
var_dump($stu);

运行结果

1560841835640

3.6 魔术方法

已经学习的魔术方法

__construct()
__destruct()
__clone()

3.6.1 __tostring()、__invoke()

__tostring():将对象当成字符串使用的时候自动调用

__invoke():将对象当成函数使用的时候自动调用

<?php
class Student {
	//把对象当成字符串使用的时候自动执行
	public function __tostring() {
		return '这是一个对象,不是字符串<br>';
	}
	//把对象当成函数使用的时候自动执行
	public function __invoke() {
		echo '这是一个对象,不是函数<br>';
	}
}
$stu=new Student;
echo $stu;	//当成字符串使用
$stu();		//当成函数使用

3.6.2 __set()、__get()、__isset()、__unset()

__set($k,$v):给无法访问的属性赋值的时候自动执行
__get($k):获取无法访问的属性值的时候自动调用
__isset($k):判断无法访问的属性是否存在自动调用
__unset($k):销毁无法访问的属性的时候自动执行

例题

<?php
class Student {
	private $name;
	private $sex;
	private $age;
	//给无法访问的属性赋值的时候自动执行
	public function __set($k,$v) {
		$this->$k=$v;
	}
	//获取无法访问的属性值的时候自动调用
	public function __get($k) {
		return $this->$k;
	}
	//判断无法访问的属性是否存在自动调用
	public function __isset($k) {
		return isset($this->$k);
	}
	//销毁无法访问的属性的时候自动执行
	public function __unset($k) {
		unset($this->$k);
	}
}
//测试
$stu=new Student;
//1、给私有属性赋值
$stu->name='tom';
$stu->sex='男';
$stu->age=22;
//2、获取私有属性的值
//echo $stu->name;
//3、判断私有属性是否存在
//var_dump(isset($stu->name));
//4、销毁私有属性
unset($stu->age);
print_r($stu);

应用:设置读写属性

<?php
class Student {
	private $name;			//读写属性
	private $add='中国';	//只读属性
	private $age;			//只写属性
	
	public function __set($k,$v) {
		if(in_array($k,array('name','age')))
			$this->$k=$v;
		else
			echo "{$k}属性是只读属性<br>";
	}
	public function __get($k) {
		if(in_array($k,array('name','add')))
			return $this->$k;
		else
			echo "{$k}是只写属性<br>";
	}
}
//测试
$stu=new Student;
$stu->name='tom';
$stu->age=22;
echo '姓名:'.$stu->name,'<br>';
echo '地址:'.$stu->add,'<br>';

3.6.3 __call()、__callstatic()

__call():调用无法访问的方法时自动执行
__callstatic():调用无法访问的静态方法时自动执行

例题:

<?php
class Student {
	/**
	*作用:调用无法访问的方法时自动执行
	*@param $fn_name string 方法名
	*@param $fn_args array 参数数组
	*/
	public function __call($fn_name,$fn_args) {
		echo "{$fn_name}不存在<br>";
	}
	//调用无法访问的静态方法时自动执行
	public static function __callstatic($fn_name,$fn_args) {
		echo "{$fn_name}静态方法不存在<br>";	
	}
}
//测试
$stu=new Student;
$stu->show(10,20);

Student::show();

3.6.4 __sleep()、__wakeup()

__sleep():当序列化的时候自动调用
__wakeup():当反序列化的时候自动调用

例题

<?php
class Student {
	private $name;
	private $sex;
	private $add='中国';
	public function __construct($name,$sex) {
		$this->name=$name;
		$this->sex=$sex;
	}
	/**
	*序列化的时候自动调用
	*@return array 序列化的属性名
	*/
	public function __sleep() {
		return array('name','sex');
	}
	//反序列化的时候自动调用
	public function __wakeup() {
		$this->type='学生';
	}
}
//测试
$stu=new Student('tom','男');
$str=serialize($stu);	//序列化
$stu=unserialize($str);	//反序列化
print_r($stu);

3.7 模拟方法重载

通过魔术方法模拟方法重载

<?php
class Math {
	public function __call($fn_name,$fn_args) {
		$sum=0;
		foreach($fn_args as $v) {
			$sum+=$v;
		}
		echo implode(',',$fn_args).'的和是:'.$sum,'<br>';
	}
}
//利用魔术方法模拟方法重载
$math=new Math();
$math->call(10,20);
$math->call(10,20,30);
$math->call(10,20,30,40);

3.8 遍历对象

通过foreach遍历对象

<?php
class Student {
	public $name='tom';
	protected $sex='男';
	private $age=22;

	public function show() {
		foreach($this as $k=>$v) {
			echo "{$k}-{$v}<br>";
		}
	}
}
//测试
$stu=new Student;
foreach($stu as $k=>$v) {
	echo "{$k}-{$v}<br>";
}
echo '<hr>';
$stu->show();

1560846141700

结论:遍历到当前位置所能访问到属性

3.9 封装MySQL的单例

3.8.1 分析

1、实现单例

2、连接数据库

3、对数据进行操作

3.8.2 步骤

第一步:实现单例

第二步:初始化参数

第三步:连接数据库

第四步:操作数据

1、执行数据操作语句(增、删、改)

2、执行数据查询语句

​ a) 返回二维数组

​ b) 返回一维数组

​ c)返回一行一列

3.8.3 代码实现

第一步:实现单例

<?php
class MySQLDB {
	private static $instance;
	private function __construct() {

	}
	private function __clone() {
		
	}
	public static function getInstance() {
		if(!self::$instance instanceof self)
			self::$instance=new self();
		return self::$instance;
	}
}
//测试
$db=MySQLDB::getInstance();
var_dump($db);

注意:A instanceof B,表示A是否是B的类型,返回bool值

第二步:初始化参数

<?php
//封装MySQL单例
class MySQLDB {
	private $host;		//主机地址
	private $port;		//端口号
	private $user;		//用户名
	private $pwd;		//密码
	private $dbname;	//数据接名
	private $charset;	//字符集
	private $link;		//连接对象
	private static $instance;
	private function __construct($param) {
		$this->initParam($param);		
	}
	private function __clone() {
		
	}
	//获取单例
	public static function getInstance($param=array()) {
		if(!self::$instance instanceof self)
			self::$instance=new self($param);
		return self::$instance;
	}
	//初始化参数
	private function initParam($param) {
		$this->host=$param['host']??'127.0.0.1';
		$this->port=$param['port']??'3306';
		$this->user=$param['user']??'';
		$this->pwd=$param['pwd']??'';
		$this->dbname=$param['dbname']??'';
		$this->charset=$param['charset']??'utf8';
	}
}

//测试
//配置参数
$param=array(
	'user'		=>	'root',
	'pwd'		=>	'root',
	'dbname'	=>	'data'
);
//获取单例
$db=MySQLDB::getInstance($param);
var_dump($db);

第三步:连接数据库

<?php
//封装MySQL单例
class MySQLDB {
	private $host;		//主机地址
	private $port;		//端口号
	private $user;		//用户名
	private $pwd;		//密码
	private $dbname;	//数据接名
	private $charset;	//字符集
	private $link;		//连接对象
	private static $instance;
	private function __construct($param) {
		$this->initParam($param);
		$this->initConnect();
	}
	private function __clone() {
		
	}
	//获取单例
	public static function getInstance($param=array()) {
		if(!self::$instance instanceof self)
			self::$instance=new self($param);
		return self::$instance;
	}
	//初始化参数
	private function initParam($param) {
		$this->host=$param['host']??'127.0.0.1';
		$this->port=$param['port']??'3306';
		$this->user=$param['user']??'';
		$this->pwd=$param['pwd']??'';
		$this->dbname=$param['dbname']??'';
		$this->charset=$param['charset']??'utf8';
	}
	//连接数据库
	private function initConnect() {
		$this->link=@mysqli_connect($this->host,$this->user,$this->pwd,$this->dbname);
		if(mysqli_connect_error()){
			echo '数据库连接失败<br>';
			echo '错误信息:'.mysqli_connect_error(),'<br>';
			echo '错误码:'.mysqli_connect_errno(),'<br>';
			exit;
		}
		mysqli_set_charset($this->link,$this->charset);
	}
}

//测试
//配置参数
$param=array(
	'user'		=>	'root',
	'pwd'		=>	'root',
	'dbname'	=>	'data'
);
//获取单例
$db=MySQLDB::getInstance($param);
var_dump($db);

第四步:数据操作的功能

1、执行增、删、改操作

<?php
//封装MySQL单例
class MySQLDB {
	private $host;		//主机地址
	private $port;		//端口号
	private $user;		//用户名
	private $pwd;		//密码
	private $dbname;	//数据接名
	private $charset;	//字符集
	private $link;		//连接对象
	private static $instance;
	private function __construct($param) {
		$this->initParam($param);
		$this->initConnect();
	}
	private function __clone() {
		
	}
	//获取单例
	public static function getInstance($param=array()) {
		if(!self::$instance instanceof self)
			self::$instance=new self($param);
		return self::$instance;
	}
	//初始化参数
	private function initParam($param) {
		$this->host=$param['host']??'127.0.0.1';
		$this->port=$param['port']??'3306';
		$this->user=$param['user']??'';
		$this->pwd=$param['pwd']??'';
		$this->dbname=$param['dbname']??'';
		$this->charset=$param['charset']??'utf8';
	}
	//连接数据库
	private function initConnect() {
		$this->link=@mysqli_connect($this->host,$this->user,$this->pwd,$this->dbname);
		if(mysqli_connect_error()){
			echo '数据库连接失败<br>';
			echo '错误信息:'.mysqli_connect_error(),'<br>';
			echo '错误码:'.mysqli_connect_errno(),'<br>';
			exit;
		}
		mysqli_set_charset($this->link,$this->charset);
	}
	//执行数据库的增、删、改、查
	private function execute($sql) {
		if(!$rs=mysqli_query($this->link,$sql)){
			echo 'SQL语句执行失败<br>';
			echo '错误信息:'.mysqli_error($this->link),'<br>';
			echo '错误码:'.mysqli_errno($this->link),'<br>';
			echo '错误的SQL语句:'.$sql,'<br>';
			exit;
		}
		return $rs;
	}
	/**
	*执行增、删、改
	*@return bool 成功返回true,失败返回false
	*/
	public function exec($sql) {
		$key=substr($sql,0,6);
		if(in_array($key,array('insert','update','delete')))
			return $this->execute($sql);
		else{
			echo '非法访问<br>';
			exit;
		}

	}
	//获取自动增长的编号
	public function getLastInsertId() {
		return mysqli_insert_id($this->link);
	}
}

//测试
//配置参数
$param=array(
	'user'		=>	'root',
	'pwd'		=>	'root',
	'dbname'	=>	'data'
);
//获取单例
$db=MySQLDB::getInstance($param);
//更新
//$db->exec("update news set title='青草' where id=2");
//插入
if($db->exec("insert into news values (null,'aa','bb',unix_timestamp())"))
	echo '编号是:'.$db->getLastInsertId();

2、查询结果

<?php
//封装MySQL单例
class MySQLDB {
	private $host;		//主机地址
	private $port;		//端口号
	private $user;		//用户名
	private $pwd;		//密码
	private $dbname;	//数据接名
	private $charset;	//字符集
	private $link;		//连接对象
	private static $instance;
	private function __construct($param) {
		$this->initParam($param);
		$this->initConnect();
	}
	private function __clone() {
		
	}
	//获取单例
	public static function getInstance($param=array()) {
		if(!self::$instance instanceof self)
			self::$instance=new self($param);
		return self::$instance;
	}
	//初始化参数
	private function initParam($param) {
		$this->host=$param['host']??'127.0.0.1';
		$this->port=$param['port']??'3306';
		$this->user=$param['user']??'';
		$this->pwd=$param['pwd']??'';
		$this->dbname=$param['dbname']??'';
		$this->charset=$param['charset']??'utf8';
	}
	//连接数据库
	private function initConnect() {
		$this->link=@mysqli_connect($this->host,$this->user,$this->pwd,$this->dbname);
		if(mysqli_connect_error()){
			echo '数据库连接失败<br>';
			echo '错误信息:'.mysqli_connect_error(),'<br>';
			echo '错误码:'.mysqli_connect_errno(),'<br>';
			exit;
		}
		mysqli_set_charset($this->link,$this->charset);
	}
	//执行数据库的增、删、改、查
	private function execute($sql) {
		if(!$rs=mysqli_query($this->link,$sql)){
			echo 'SQL语句执行失败<br>';
			echo '错误信息:'.mysqli_error($this->link),'<br>';
			echo '错误码:'.mysqli_errno($this->link),'<br>';
			echo '错误的SQL语句:'.$sql,'<br>';
			exit;
		}
		return $rs;
	}
	/**
	*执行增、删、改
	*@return bool 成功返回true,失败返回false
	*/
	public function exec($sql) {
		$key=substr($sql,0,6);
		if(in_array($key,array('insert','update','delete')))
			return $this->execute($sql);
		else{
			echo '非法访问<br>';
			exit;
		}

	}
	//获取自动增长的编号
	public function getLastInsertId() {
		return mysqli_insert_id($this->link);
	}

	//执行查询语句
	private function query($sql) {
		if(substr($sql,0,6)=='select' || substr($sql,0,4)=='show' || substr($sql,0,4)=='desc'){
			return $this->execute($sql);
		}else{
			echo '非法访问<br>';
			exit;
		}
	}
	/**
	*执行查询语句,返回二维数组
	*@$sql string 查询sql语句
	*@type string assoc|num|both
	*/
	public function fetchAll($sql,$type='assoc') {
		$rs=$this->query($sql);
		$type=$this->getType($type);
		return mysqli_fetch_all($rs,$type);
	}
	//匹配一维数组
	public function fetchRow($sql,$type='assoc') {
		$list=$this->fetchAll($sql,$type);
		if(!empty($list))
			return $list[0];
		return array();
	}
	//匹配一行一列
	public function fetchColumn($sql) {
		$list=$this->fetchRow($sql,'num');
		if(!empty($list))
			return $list[0];
		return null;
	}

	//获取匹配类型
	private function getType($type) {
		switch($type){
			case 'num':
				return  MYSQLI_NUM;
			case 'both':
				return  MYSQLI_BOTH;
			default:
				return  MYSQLI_ASSOC;
		}
	}
}

//测试
//配置参数
$param=array(
	'user'		=>	'root',
	'pwd'		=>	'root',
	'dbname'	=>	'data'
);
//获取单例
$db=MySQLDB::getInstance($param);
//更新
//$db->exec("update news set title='青草' where id=2");
//插入
/*
if($db->exec("insert into news values (null,'aa','bb',unix_timestamp())"))
	echo '编号是:'.$db->getLastInsertId();
*/

//查询
//$list=$db->fetchAll('select * from news','aa');
//$list=$db->fetchRow('select * from news where id=1','aa');

$list=$db->fetchColumn('select count(*) from news');

echo '<pre>';
var_dump($list);

小结:

1、instanceof 用来判断对象是否属于某个类

2、参数必须从外部传递到内部,不能写死到类的内部。

3、为了保证代码的可重用性,一个方法只实现一个功能,所以初始化参数和连接数据库分到两个方法中。

4.1 今日目标

  1. 掌握trait的作用以及实际应用;
  2. 了解预定义接口Iterator对foreach的修改;
  3. 掌握命名空间的基本语法;
  4. 掌握命名空间的成员控制;
  5. 了解子空间的概念;
  6. 掌握命名空间的三种访问方式及其区别;
  7. 掌握空间引入的使用;
  8. 掌握全局空间的概念;
  9. 掌握全局空间与其他空间进行引入时的区别;
  10. 了解PSR-1基础编码规范;
  11. 了解不产生副作用和只产生副作用的概念
  12. 能够按照PSR-1规范进行文件分类
  13. 了解PSR-2编码规范

4.2 命名空间

4.2.1 介绍

在一个大的项目中,可能会遇到同名的类、函数、常量,为了区分这些元素,我们可以将这些元素分别存放到不同的命名空间中。

1、命名空间就是包,用来存放项目中的类、函数、常量

2、通过namespace关键字来声明命名空间

4.2.2 声明命名空间

<?php
namespace China;	//定义命名空间
function getInfo() {
	echo '我是中国人<br>';
}

namespace USA;		//定义命名空间
function getInfo() {
	echo 'I am a America<br>';
}
//调用
getInfo();			//I am a America
\USA\getInfo();		//I am a America
\China\getInfo();	//我是中国人

注意:\表示公共空间

4.2.3 多级命名空间

命名空间的名字可以是多级的(子级命名空间)。

<?php
namespace China\Beijing\Shunyi;
class Student {
	
}
namespace USA\Washington;
class Student {
	
}
//测试:
$stu1=new Student();	//相对路径
$stu2=new \USA\Washington\Student();	//绝对路径
$stu3=new \China\Beijing\Shunyi\Student();	//绝对路径
var_dump($stu1,$stu2,$stu3);

//object(USA\Washington\Student)#1 (0) { }
//object(USA\Washington\Student)#2 (0) { }
//object(China\Beijing\Shunyi\Student)#3 (0) { } 

总结:如果将相对路径转成绝对路径

公共空间+命名空间+空间元素
公共空间           命名空间                  空间元素
   \         China\Shanghai\PuDong\        Student

4.2.4 访问空间元素的三种方式

1、非限定名称访问

2、完全限定名称访问

3、限定名称访问

<?php
namespace China\Beijing\Shunyi;
function getInfo() {
	echo '顺义...<br>';
}

namespace China\Beijing;
function getInfo() {
	echo '北京...<br>';	
}
//访问空间元素的三种方式
getInfo();					//非限定名称访问	北京...			
\China\Beijing\getInfo();	//完全限定名称访问		北京...
Shunyi\getInfo();			//限定名称访问		顺义...
//转成绝对路径如下:
// \China\Beijing\Shunyi\getInfo();

练习:将下面的相对路径转成绝对路径

例题一:
namespace A\B\C;
fun();		=>	\A\B\C\fun();
a\fun();	=>	\A\B\C\a\fun()
C\fun();	=>	\A\B\C\C\fun();

4.2.5 引入命名空间

完全限定名称访问元素路径太长,可以将其他空间引入到当前空间来

通过use引入命名空间

<?php
namespace China\Beijing\Shunyi;
function getInfo() {
	echo '李白<br>';
}
namespace USA;
function getInfo() {
	echo 'Lincoln<br>';
}
//引入命名空间
use China\Beijing\Shunyi;	
//测试
getInfo();			//Lincoln
Shunyi\getInfo();	//李白
/*
分析:
第一步:通过当前空间拼接成绝对路径:\USA\Shunyi\getInfo(),这个地址没有对应的空间元素
第二步:通过引入的空间拼接绝对路径:\China\Beijing\Shunyi+Shunyi\getInfo(),Shunyi是公共部分,只需要取一个,最后拼接的地址是:\China\Beijing\Shunyi\getInfo(),这个地址可以找到对应的元素
*/

引入命名空间的拼接规则

公共空间+引入空间+(去除公共部分,公共部分只能有一级)空间元素

比如:
namespace A\B\C;
function getInfo(){}

namespace D\E;
use A\B\C;	//引入命名空间
C\getInfo();	//正确	\A\B\C\getInfo();
B\C\getInfo();	//错误   \A\B\C\B\C\getInfo();

4.2.6 引入空间元素

引入类:use

引入函数:use function [php7.0以后支持]

引入常量:use const [php7.0以后支持]

<?php
namespace China\Beijing\Shunyi;
class Student {
}
function getInfo() {
	echo '李白<br>';
}
const TYPE='学生';
namespace USA;
//引入类
use China\Beijing\Shunyi\Student;
//引入函数
use function China\Beijing\Shunyi\getInfo;
//引入常量
use const China\Beijing\Shunyi\TYPE;

//测试
$stu=new Student;
var_dump($stu);
echo '<br>';
getInfo();
echo TYPE;

4.2.7 给类、函数取别名

如果引入的类和函数与当前空间的类和函数名称相同,需要给引入的类和函数取别名。

通过as取别名

<?php
namespace China\Beijing\Shunyi;
class Student {
	
}
function getInfo() {
	echo '李白<br>';
}
namespace USA\Washington;
class Student {
	
}
function getInfo() {
	echo 'Lincoln<br>';
}
//引入类取别名
use China\Beijing\Shunyi\Student as ChinaStudent;
//引入函数
use function China\Beijing\Shunyi\getInfo as info1;

//测试
$stu=new ChinaStudent;
var_dump($stu);

getInfo();	//Lincoln
info1();	//李白

4.2.8 公共空间

如果一个页面没有namespace声明空间,这个页面的元素在公共空间下

公共空间用\表示

<?php
function getInfo() {
	echo '李白<br>';
}
\getInfo();		//李白

4.2.9 命名空间注意事项

1、命名空间只能存放类、函数、const常量

2、第一个namespace前面不能有任何的代码,空白字符、header()也不行。

3、包含文件不影响当前的命名空间

4.3 trait(原型)

trait 为了减少单继承语言的限制,可以在不同层次结构内独立的类中复用类的方法集。

<?php
//原型
trait A{
	public function getInfo() {
		echo '锄禾日当午<br>';
	}
}
//使用原型
class Student {
	use A;	//代码复用
}
//测试
$stu=new Student;
$stu->getInfo();	//锄禾日当午

引入多个 trait

<?php
//原型
trait A{
	public function getInfo1() {
		echo '锄禾日当午<br>';
	}
}
trait B{
	public function getInfo2() {
		echo '床前明月光<br>';
	}
}
//使用原型
class Student {
	use A,B;	//引入A、B trait
}
//测试
$stu=new Student;
$stu->getInfo1();	//锄禾日当午
$stu->getInfo2();	//床前明月光

trait和继承结合

<?php
trait A{
	public function getInfo() {
		echo '这是trait原型<br>';
	}
}
class Person {
	public function getInfo() {
		echo '这是Person类<br>';
	}
}
//继承类同时代码复用
class Student extends Person {
	use A;		//继承了getInfo,有被A中getInfo覆盖
}
//测试
$stu=new Student;
$stu->getInfo();	//这是trait原型

解决同名冲突

<?php
//原型
trait A{
	public function getInfo() {
		echo '锄禾日当午<br>';
	}
}
trait B{
	public function getInfo() {
		echo '床前明月光<br>';
	}
}
//使用原型
class Student {
	use A,B{	//引入A和B的trait,同时解决名称冲突
		//方法一:方法替换
		//A::getInfo insteadof B;	//将A中的getInfo替换掉B中的getInfo
		//B::getInfo insteadof A;		//将B中的getInfo替换到A中的getInfo

		//方法二:改名
		A::getInfo insteadof B;
		B::getInfo as show;		//将B的getInfo改名为show
	}
}
//测试
$stu=new Student;
$stu->getInfo();	//锄禾日当午
$stu->show();		//床前明月光
/*
同名冲突的解决方法有两个:
第一:方法替换
第二:方法改名

更改权限

<?php
trait A{
	private function show() {
		echo '锄禾日当午<br>';
	}
}
class Student {
	use A{
		//show as public;	//将show方法权限设为public;
		show as public show2;	//将show方法设置public,并改名为show2
	}
}

$stu=new Student;
//$stu->show();
$stu->show2();

多学一招:具体参见手册

1、 多个trait可以组成一个trait

2、 trait可以定义抽象成员

3、 trait可以定义静态成员

4、 trait可以定义属性

4.4 迭代器

4.4.1 遍历数组

手动遍历数组

步骤:

1、复位数组指针 reset()

2、检查指针是否合法 获取当前指针,如果不为null就是合法的

3、获取当前值 current()

4、获取当前键 key()

5、指针下移 next()

代码实现

<?php
$stu=['tom','berry','ketty','rose'];
reset($stu);	//复位指针
while(key($stu)!==null){//键合法
	echo key($stu),'-',current($stu),'<br>';//获取键、值
	next($stu);		//指针下移
}
/*
0-tom
1-berry
2-ketty
3-rose
*/

4.4.2 迭代器

迭代器是PHP内置的接口

1561014358049

场景:遍历对象,获取的是对象中属性保存的数组

<?php
//定义类实现迭代器接口
class MyClass implements Iterator{
	//$list属性用来保存学生数组
	private $list=array();
	//添加学生
	public function addStu($name) {
		$this->list[]=$name;
	}
	//实现接口中的复位方法
	public function rewind() {
		reset($this->list);
	}
	//验证当前指针是否合法
	public function valid() {
		return key($this->list)!==null;
	}
	//获取值
	public function current() {
		return current($this->list);
	}
	//获取键
	public function key() {
		return key($this->list);
	}
	//指针下移
	public function next() {
		next($this->list);
	}
}

//创建班级
$class=new MyClass();
//添加学生
$class->addStu('tom');
$class->addStu('berry');
$class->addStu('ketty');
//遍历班级
foreach($class as $k=>$v){
	echo "{$k}-{$v}<br>";
}
/*
0-tom
1-berry
2-ketty
*/

4.5 PSR编码规范

4.5.1 概述

  1. PSR 是 PHP Standard Recommendations 的简写,由 PHP FIG 组织制定的 PHP 规范,是 PHP 开发的实践标准。
  2. 目前已表决通过了 6 套标准,已经得到大部分 PHP 框架的支持和认可。
  3. 网址:http://psr.phphub.org/

1560769585637

4.5.2 PSR-1 基础编码规范

1、PHP代码文件 必须 以 <?php 或 <?= 标签开始
2、类的命名大写开头的驼峰命名规范
3、类中的常量所有字母都必须大写,单词间用下划线分隔
4、方法名称必须符合小写开头驼峰命名规范5、

副作用:(side effects),一个文件只做一件事情,如果做了其他事情就是产生了副作用

不产生副作用:一个文件只做一件事

产生副作用:一个文件做了多件事情

4.5.3 PSR-2 编码风格规范

代码 必须 使用 4 个空格符而不是「Tab 键」进行缩进
每个 namespace 命名空间声明语句和 use 声明语句块后面,必须 插入一个空白行
类的开始花括号({) 必须 写在类声明后自成一行,结束花括号(})也 必须 写在类主体后自成一行
方法的开始花括号({) 必须 写在函数声明后自成一行,结束花括号(})也 必须 写在函数主体后自成一行。
类的属性和方法 必须 添加访问修饰符(private、protected 以及 public),abstract 以及 final 必须 声明在访问修饰符之前,而 static 必须 声明在访问修饰符之后。

4.6 分页

4.6.1 分析

-- 1、获取当前页码的数据
页码		SQL语句
1			select * from products limit 0,10
2			select * from products limit 10,10
3			select * from products limit 20,10
结论:
$pageno:页码
$startno:起始位置
$pagesize=10:页面大小
公式:$startno=($pageno-1)*$pagesize;

-- 2、如何获取页码
用户点击页面底端页码,传递当前的页面

-- 3、如何获取总页码		
记录数			页数			计算
60				6			60/10=6
51				6			ceil(51/10)=6

结论:
$rowcount:总记录数
$pagecount:总页数
公式:$pagecount=ceil($rowcount/$pagesize)

-- 4、如何获取总记录数
select count(*) from products;

1561018119397

4.6.2 步骤

第一步:获取总记录数

第二步:求出总页数

第三步:循环显示页码

第四步:通过当前页面,求出起始位置

第五步:获取当前页面数据,并遍历显示

4.6.3 代码实现

将上一讲的MySQLDB类拷贝到站点下,将测试代码删除,只留下类代码,文件名改为MySQLDB.class.php

分页页面代码如下:

<?php
//自动加载类
spl_autoload_register(function($class_name){
	require "./{$class_name}.class.php";
});
//获取单例
$param=array(
	'user'		=>	'root',
	'pwd'		=>	'root',
	'dbname'	=>	'data'
);
//获取单例
$db=MySQLDB::getInstance($param);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>无标题文档</title>
<style type="text/css">
	table{
		width:780px;
		border:solid #000 1px;
	}
	td,th{
		border:solid #000 1px;
	}
</style>
</head>

<body>
<?php
$pagesize=10;		//页面大小
//第一步:获取总记录数
$rowcount=$db->fetchColumn('select count(*) from products');
//第二步:求出总页数
$pagecount=ceil($rowcount/$pagesize);
//第四步:通过当前页面,求出起始位置
//$pageno=isset($_GET['pageno'])?$_GET['pageno']:1;
$pageno=$_GET['pageno']??1;
$pageno=$pageno<1?1:$pageno;
$pageno=$pageno>$pagecount?$pagecount:$pageno;
$startno=($pageno-1)*$pagesize;
//第五步:获取当前页面数据,并遍历显示
$sql="select * from products limit $startno,$pagesize";
$rs=$db->fetchAll($sql);
?>
<table>
	<tr>
		<th>编号</th>
		<th>商品名称</th>
		<th>规格</th>
		<th>价格</th>
	</tr>
	<?php foreach($rs as $row):?>
	<tr>
		<td><?=$row['proID']?></td>
		<td><?=$row['proname']?></td>
		<td><?=$row['proguige']?></td>
		<td><?=$row['proprice']?></td>
	</tr>
	<?php endforeach;?>
</table>
<!--第三步:循环显示页码-->
一共有<?=$rowcount?>条记录,每页放<?=$pagesize?>条记录,当前是<?=$pageno?>页 <br />
【<a href="?pageno=1">首页</a>】
【<a href="?pageno=<?=$pageno-1?>">上一页</a>】
<?php for($i=1; $i<=$pagecount; $i++):?>
	<a href="?pageno=<?=$i?>"><?=$i?></a>
<?php endfor;?>
【<a href="?pageno=<?=$pageno+1?>">下一页</a>】
【<a href="?pageno=<?=$pagecount?>">末页</a>】
</body>
</html>

运行结果

1561019989388

4.6.4 分页优化

在上面的分页代码中,虽然SQL语句比较经典,但是每次都要获取不需要的数据,浪费资源

$sql="select * from products limit $startno,$pagesize";

优化

$sql="select * from products where proid>=(select proid from products limit $startno,1) limit $pagesize";

4.6.5 作业

通过异步实现分页