对象遍历
foreach遍历
定义:遍历对象,其实就是指将对象中的所有属性(公有属性)以键值对的形式取出并进行访问
- 使用foreach对对象进行遍历
//定义类
class Man{
public $name = 'LiLei';
public $height = 178;
public $weight = 140;
protected $age = 30;
private $money = 1000;
}
//实例化
$m = new Man();
//遍历
foreach($m as $k => $v){
echo $k . ' : ' . $v . '<br/>'; //$k为属性名,$v为属性值
}
总结
- foreach可以对对象像数组一样遍历
foreach遍历对象遍历的是对象内部的所有公有属性(在类外部进行对象遍历)
Iterator迭代器
```php class MyIterator implements Iterator { private $var = array();
public function __construct($array) {
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind() {
echo "倒回第一个元素\n";
reset($this->var);
}
public function current() {
$var = current($this->var);
echo "当前元素: $var\n";
return $var;
}
public function key() {
$var = key($this->var);
echo "当前元素的键: $var\n";
return $var;
}
public function next() {
$var = next($this->var);
echo "移向下一个元素: $var\n";
return $var;
}
public function valid() {
$var = $this->current() !== false;
echo "检查有效性: {$var}\n";
return $var;
}
}
$values = array(1,2,3); $it = new MyIterator($values); foreach ($it as $k => $v) { print “此时键值对 — key $k: value $v\n\n”; }
public function rewind() { reset($this->info); }
public function current() {
return current($this->info);
}
public function key() {
return key($this->info);
}
public function next() {
return next($this->info);
}
public function valid() {
return isset($this->info[key($this->info)]);
}
<a name="SnWX8"></a>
### Generator生成器
<br /><br /><br />
<a name="LqT7L"></a>
## 设计模式
> **定义**:设计模式(Design pattern) 是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式有很多,在PHP中通常只用两种设计模式
- 单例模式
- 工厂模式

<a name="aXDsU"></a>
### 单例模式
> **定义**:单例模式singleton,是一种类的设计只会最多产生一个对象的设计思想。
1. 首先定义一个空类,叫做Singleton
```php
<?php
//创建一个空类
class Singleton{
}
?>
- 思考:对象的产生是通过实例化产生的,而实例化是一种不可控行为,即可以产生无限多个对象,所以应该禁止:即”禁止实例化“,之所以是引号,是因为只能禁止在类外部实例化对象,私有化构造方法
//在上述类中增加私有化构造方法
class Singleton{
private function __construct(){}
}
//尝试外部实例化
$s = new Singleton(); //致命错误:不能访问私有方法
- 思考:一旦外部不能实例化对象了,那就意味着根本“不可能”产生对象了,此时就只能想办法在还没有产生对象的时候就进入到“类内部”,意味着只能通过
静态方法
让类直接进入到类的内部
//在上述类中增加公有静态方法
public static function getInstance(){
}
- 思考:进入类的内部依然还是没有对象,此时需要在静态方法内部进行对象实例化,并且把得到的对象返回到外部
//修改公有静态方法:获取对象,并返回给外部调用出
public static function getInstance(){
return new self();
}
//外部获取对象
$s = Singleton::getInstance();
- 思考:此方法开启了实例化对象的窗口,但是此时新问题出现:无限调用静态方法依然可以得到多个对象。如果想要该方法只返回一个对象,就得保证类内部有办法存着某个产生的对象,第一次产生新的,后面返回旧的。此时需要使用静态属性
//增加静态属性:私有,不允许外部修改,否则外部修改之后就没有意义了
private static $object = NULL; //初始化为NULL,没有对象
//修改静态方法
public static function getInstance(){
//判断内部属性是否存在对象(is_object函数):最好的判定是存的对象是当前类的 instanceof
if(!(self::$object instanceof self)){
//当前保存的内容不是当前类的对象
self::$object = new self();
}
//返回对象给外部
return self::$object;
}
- 此时可以保证外部无论多少次调用公有静态方法获取实例,都会只得到一个对象。但是此时外部对象依然可以产生新的对象:因为克隆,所以还必须禁止对象的克隆,即在类内部私有化克隆方法
//在Singleton类中增加私有化的__clone()方法
private function __clone(){}
总结
- 单例模式就是设计的类最多只能得到一个对象
- 单例模式的设计规范就是“三私一公”
- 私有化构造方法:禁止在类外无限实例化对象
- 私有化克隆方法:禁止对象无限克隆对象
- 私有化静态属性:保存类内部实例化得到的对象
- 公有化静态方法:允许外部通过调用类内部方法获取对象
- 单例模式如果还有其他功能诉求,可以在类中增加相应的其他类成员
- 单例模式的目的是为了保护资源的唯一性
工厂模式
定义:工厂模式factory,是指像工厂一样流水线生产对象,由一个地方生产对象,其他位置就不需要额外实例化对象,从而可以方便后期代码统一的维护。而且工厂模式下可以方便隐藏真实的类结构,因此也更加安全。
- 工厂模式针对的是“相同模型”的统一产出,即使用工厂模式产出对象对应的类都有相同的结构或者功能。所以,首先要有一批具有类似功能的类(其实本质是同样的大类下的小类)
//三个类文件
//人是一种大类,人下应该有男人、女人、人妖,各自成类
class Man{
public function display(){
echo '这是男人<br/>';
}
}
class Woman{
public function display(){
echo '这是女人<br/>';
}
}
class Ladyboy{
public function display(){
echo '这是人妖<br/>';
}
}
- 以前访问这些类的话都需要通过new 类名来实现,多出使用就多次new。如果使用工厂模式的话,就需要增加一个工厂类:HumanFactory
<?php
//人类工厂
class HumanFactory{
public function getInstance($classname){
return new $classname(); //可变变量使用
}
}
$hf = new HumanFactory();
$m = $hf->getInstance('Man');
$m->display(); //这是男人
$w = $hf->getInstance('Woman');
$w->display(); //这是女人
- 上述工厂类在对象生产的时候,额外产生了一个工厂类的对象,该对象无实际含义,因此可以使用更优的方式来生产对象:静态工厂
<?php
//静态人类工厂
class HumanFactory{
public static function getInstance($classname){
return new $classname(); //可变变量使用
}
}
$m = HumanFactory::getInstance('Man');
$m->display(); //这是男人
$w = HumanFactory::getInstance('Woman');
$w->display(); //这是女人
- 以上模式虽然也是工厂生产对象,但是是建立在使用者知道类名的前提下,而且如果原类名修改,依然需要多处修改代码,所以没有达到工厂模式的真实目的。修改工厂模式
<?php
//静态人类工厂
class HumanFactory{
public static function getInstance($flag){ //flag只是一种标志:不是类名
//$flag可以是m代表Man,w代表Woman,L代表Ladyboy
switch($flag){
case 'm':
return new Man();
case 'w':
return new Woman();
case 'L':
return new Ladyboy();
default:
return null; //匹配失败,返回空
}
}
}
$m = HumanFactory::getInstance('m');
$m->display(); //这是男人
$w = HumanFactory::getInstance('w');
$w->display(); //这是女人
总结
- 工厂模式是一种按需生产对象的模式
- 工厂模式通常是需要在大型项目中,会出现很多的相同功能的类,此时可以使用工厂产生对象
- 工厂模式的优点是能够方便后期对类的维护(更名)
- 工厂模式的缺点是随着功能增加,会需要增加很多开发量(开发多个工厂)
命名空间
基础
定义:命名空间namespace,是指人为的将内存进行
分隔
,让不同内存区域的同名结构共存。从而解决在大型项目中可能出现的重名结构问题。
- 基本语法:namespace 空间名字;
<?php
//定义空间
namespace my_space; //定义一个叫做my_space的空间
- 命名空间的命名规则
- 由字母、下划线和数字构成
- 可以以字母和下划线开头
- 较少出现多单词空间名,一般使用下划线法
- 命名空间的作用:能够创建同名结构,包含函数、常量和类
namespace space1;
function display(){
echo __NAMESPACE__,'<br/>';
}
const PI = 3;
class Human{}
namespace space2;
function display(){
echo __NAMESPACE__,'<br/>';
}
const PI = 3.14;
class Human{}
- 命名空间里的内容
- 命名空间里可以定义同名的函数、常量和类(结构):因为此类结构不允许同名,这些是命名空间规范的目标(称为空间元素)
- 命名空间里可以有其他代码
namespace space;
class Human{}
function display(){}
const PI = 3.14;
$a = 100;
echo $a;
- 命名空间注意事项:命名空间的声明(第一次)必须在所有代码之前
//命名空间之前不能有任何代码
namespace space1; //正确
echo 'test';
namespace space1; //错误:第一次命名空间之前不能有任何其他代码
注意:命名空间在一个脚本中只会定义一个(最开始),但是在讲课的时候可能会定义多个
总结
- 命名空间是使用namespace + 空间名字定义
- 不同命名空间里可以定义同名的函数、常量和类(同名结构)
- 命名空间里可以书写任意代码
- 命名空间的定义必须在脚本的最前面
- 一个脚本中通常只会定义一个空间
- 命名空间其实就好比是磁盘上划分的不同文件夹,用来保存同名文件
子空间
定义:子空间,即在已有空间之上,再在内部进行空间划分,让每个小空间独立起来。
- 命名空间子空间是直接通过namespace+路径符号
\
实现
namespace space; //创建一个一级空间
function display(){}
//创建子空间
namespace space\space1; //在space空间下创建一个叫做space1的子空间
function display(){}
- 子空间的创建不一定非要在前面创建了上级空间,即可以直接在某个脚本中创建子空间
//脚本最上面
namespace space\space2;
function display(){}
总结
- 子空间也是通过namespace实现,用namespace+
\
区分上下级空间名 - 基于一个脚本中通常只有一个空间名,所以子空间的创建可以直接创建(不用一定先创建一级空间)
- 子空间理论上可以创建无限多层,但是实际层次根据项目需求确定(一般不超过四层)
命名空间访问
定义:命名空间访问,是指访问不同空间里的结构元素,如果空间里有除了函数、常量和类的其他代码,会自动执行,只有空间元素本身(函数、常量和类)是需要通过空间进行访问的。在PHP命名空间中,提供了三种空间元素的访问方式:
非限定名称
、限定名称
和完全限定名称
- 非限定名称访问:即直接访问空间元素的名字,此类访问访问的是当前代码所属空间内的元素
namespace space1;
function display(){
echo 'space1';
}
namespace space2;
function display(){
echo 'space2';
}
//非限定名称访问
display(); //输出space2,因为当前display函数调用所属空间为space2
注意:非限定名称访问就好比是访问当前自己文件夹下的所有文件
- 限定名称访问,即在访问元素的前面使用相应的空间名字,非限定名称的访问是基于子空间来实现的
//定义子空间
namespace space\space1;
function display(){
echo 'space\space1<br/>';
}
//定义子空间
namespace space\space2;
function display(){
echo 'space\space2<br/>';
}
//所属父空间
namespace space;
function display(){
echo 'space<br/>';
}
//非限定名称访问
display(); //space:当前向上所属空间
space1\display(); //space\space1:实际为当前空间space + space1\display()
注意:限定名称访问好比访问当前文件夹下的子文件夹内容
- 完全限定名称访问,即从根目录(全局空间)开始访问,使用
\
作为全局空间开始符号
//接上述代码
//完全限定名称访问
\space\display(); //space空间下的display
\space\space1\display(); //space下space1空间的display
注意:完全限定名称访问好比从磁盘根目录访问对应路径下的内容(绝对路径)
总结
- 命名空间的访问分为三种模式
- 非限定名称访问,直接访问元素本身,代表当前所属空间(当前目录)
- 限定名称访问,使用空间名+元素,代表访问当前空间子空间(当前目录子目录)
- 完全限定名称访问,使用全局空间开始,代表从全局开始进行访问(根目录)
- 任何空间元素访问针对的都是类、常量和函数(其他代码会自动执行)
全局空间
定义:全局空间,即空间元素在没有定义空间的情况下所属的空间,也是所有定义的空间的顶级空间(即所有空间都是从全局空间分离出来的)。
- 没有指定空间的元素所属的空间属于全局空间
//不定义空间
function display(){
echo __NAMESPACE__,'<br/>';
}
- 所有的空间本质都是在全局空间下的划分
//定义空间
namespace space;
function display(){
echo __NAMESPACE__,'<br/>';
}
//space空间属于从全局空间里划分出一部分用于space空间管理
- 全局空间元素的访问:使用完全限定名称访问
//不定义空间
function display(){
echo __NAMESPACE__,'<br/>';
}
display(); //非限定名称访问:本身当前就是全局空间内,所以可以访问
\display(); //完全限定名称访问:全局符号"\"+全局空间元素
- 一旦命名空间出现,那么空间元素(类、常量和函数)的访问就被限定在空间内,如果使用非限定名称访问,那么系统会以下解析逻辑(限定名称或者完全限定名称是直接按路径准确找)
- 首先一定是在自己空间内查找
- 如果找不到元素,不同空间元素的处理不同
- 系统常量、系统函数如果找不到,会自动去全局空间找(也就是能找到)
- 系统类是不会自动去全局空间找的(报错,提示当前所属空间内元素找不到)
//定义空间
namespace space;
function display(){
echo __FUNCTION__,'<br/>';
}
//当前所有访问如果使用非限定名称都代表访问当前空间内的元素
display(); //space下的display函数
//想访问函数
define('PI',3.14); //正确:space下没有define函数,但是全局空间有(系统函数属于全局空间)
//访问系统常量
echo PHP_VERSION; //正确:space下没有,但全局空间有
//想访问类
//$m = new Mysqli('localhost','root','root');
//错误:系统提示space\Mysqli不存在
//正确方案
$m = new \Mysqli('localhost','root','root');
- 同样的,如果一个文件有空间,包含了一个没有空间的文件,那么要访问文件中的内容,需要使用全局空间
//无空间文件:nospace.php
function display(){ //属于全局空间
echo __FUNCTION__;
}
//有空间文件
namespace space();
function display(){
echo 'space';
}
//包含无空间文件
include_once 'nospace.php';
//访问元素
display(); //访问的是space空间下的display函数
\display(); //正确:访问全局空间的display函数
//注意:如果space空间没有display的话,直接访问display函数也是正确的,因为系统会自动寻找全局空间
总结
- 全局空间就是没有使用namespace定义空间的空间(所有空间本质都是在全局空间下划分)
- 全局空间的元素访问使用完全限定名称访问(当前文件中,非限定名称一样)
- 系统内置的函数、常量和类都属于全局空间
- 系统函数、常量,在空间内访问的时候系统会自动在自己空间找,如果找不到会去全局空间
- 系统类必须使用全局空间访问:\类名
命名空间运用
定义:命名空间应用是模拟真实的开发环境,来运用命名空间的规则。
- 创建文件夹:模拟项目不同文件PHP文件放到不同文件夹下
—|root ————根目录
—|—|controller ————业务模块
—|—|model ————数据模块
—|—|core ————核心工具
- 业务说明
- root根目录,存放用户可以直接访问的文件,文件都是请求controller里的文件
- controller目录,存放业务逻辑文件,所有业务都是类文件,业务要操作数据库,请求model里的文件,属于controller空间
- model目录,存放数据库操作的类文件,一张表一个类文件,属于model空间
- core目录,核心工具的存放,属于core空间
- 创建3个文件:分表代表root目录下(不需要空间),controller目录下,controller空间,model目录下的model空间
//root目录下:index.php
//啥都不用做,直接包含controller文件
include_once 'controller/User.class.php';
//$u = new User(); //错误:当前空间没有User类
$u = new controller\User(); //限定名称访问:因为controller空间属于全局空间
$u->display();
//root/core目录下:DB.class.php
namespace core;
class DB{
private $link;
public function __construct(){
//数据库初始化
}
//简单效果:查询全部数据
public function getAll($sql){
$res = $this->link->query($sql);
return $res->fetchAll($this->link);
}
}
//root/model目录下:User.class.php
namespace model;
//加载DB类
include_once '../DB.class.php';
class User{
public function getAllUsers(){
//假设数据库连接、数据库、表都已经存在
$sql = "select * from user";
//调用更高级的操作类实现SQL执行并返回结果:DB属于Core空间,使用完全限定名称访问
$db = new \Core\DB();
return $db->getAll($sql);
}
}
//root/controller目录下:User.class.php
namespace controller;
class User{
public function display(){
//调用模型目录下的user类实现数据库操作:使用完全限定名称访问
include_once '../Model/User.class.php'
$u = new \Model\User();
$users = $u->getAllUsers();
var_dump($users);
}
}
- 代码说明
- index.php在root目录下,没有定义空间,内部元素属于全局空间:index.php包含了子目录controller下的User.class.php,而User类属于controller空间,所以在index.php中访问User类的使用,可以使用限定名称(全局空间的子空间controller\空间元素),或者完全限定名称(\子空间\空间元素)
- controller/User.class.php在root/controller文件夹下,定义了空间controller,所以文件里面所有的访问,默认都是在controller下找。controller/User类中用到了model/User类,所以需要使用完全限定名称访问(同级别不同空间)\model\User
- model/User.class.php在root/model文件夹下,定义了空间model,所以文件里所有的访问,默认都是在model下找。model/User类中用到了core/DB类,所以需要使用完全限定名称访问\core\DB
- core/DB.class.php在root/core文件夹下,定义了空间core
总结
- 空间的实际应用是以文件为单位定义空间的
- 空间的划分是按业务对应的脚本进行划分的,如业务controller,数据model之类
- 文件的包含和空间的包含没有联系,二者是独立的:文件是在加载文件时,而空间是在进入内存后
- 空间应用,通常是采用非限定名称(自己空间里)和完全限定名称访问(其他空间)
命名空间引入
定义:命名空间引入其实就是将另外一个空间的元素(类、函数和常量)引入到当前空间来,当做当前空间的元素访问,从而可以减少复杂的完全限定名称访问,取而代之的是非限定名称访问。
- 空间引入方式:use关键字
namespace space;
class Man{}
namespace space1;
//引入空间元素
use space\Man;
new Man();
注意:use进行空间包含的时候,默认是从全局空间开始构建空间路径的(不是自己空间的相对路径),所以上述代码等价于以下代码
namespace space;
class Man{}
namespace space1;
//引入空间元素
use \space\Man;
- 空间引入的元素默认是类,如果要引入其他元素,就必须使用相应关键字:function和const
namespace space;
function display(){}
class Man{}
const PI = 3.14;
namespace space1;
//引入空间元素
use function space\display; //引入函数
use space\Man; //引入类
use const space\PI; //引入常量
display();
new Man();
echo PI;
- 如果被引入的元素在当前空间已经存在,则会出现重名,解决方案是使用别名 as alias
namespace space;
function display(){}
class Man{}
const PI = 3.14;
namespace space1;
class Man{}
//引入空间元素
//use space\Man; //错误:当前空间已经存在Man
use space\Man as M;
use function space\display as dis;
use const space\PI as D;
- 一旦引入的时候使用了别名,那么在使用的时候就直接通过别名使用
namespace space;
function display(){}
class Man{}
namespace space1;
class Man{}
//引入空间元素
use space\Man as M;
new M(); //使用别名(且只能使用别名)
- 如果一个空间有多个元素要引入,那么可以进行一次引入多个,使用逗号
,
分隔即可
namespace space;
function display(){}
class Man{}
class Woman{}
namespace space1;
class Man{}
//一次引入多个
use space\Man as M,spcae\Woman;
//引入了一个Man类别名为M,Woman类没有定义别名
注意:以上方式都是在引入同一种元素,如果要引入多个不同元素,可以如下使用
//引入space\spac1下的三个类,两个函数和一个常量
use space\space1\{
Man as M,
Woman,
Ladyboy,
function display,
function show,
const PI as P
}; //注意分号
- 如果说确定一个空间里的所有元素都需要引入进来,也可以直接引入空间
namespace space;
class Man{}
namespace space1;
//引入空间
use space;
注意:如果是直接进行空间引入,那么被引入的空间属于当前空间的一个元素,要访问引入空间的其他元素,得从引入的空间开始:即引入的空间最后一级空间名字+元素(引入空间当做当前空间的子空间)
namespace space\space1\space2;
class Man{}
namespace space3;
class Man{}
//引入空间
use space\space1\space2;
new Man(); //访问的是space3\Man
new space2\Man(); //使用引入空间的最后一级空间访问
总结
- 空间引入是解决访问时的麻烦:由完全限定名称(限定名称)变成非限定名称访问
- 空间元素都可以引入,但是引入方式有区别
- 类直接引入
- 函数需要在use之后跟关键字function
- 常量需要在use之后跟关键字const
- 空间引入过程中如果出现重名,需要使用别名来处理,引入后在空间里可以直接访问别名
- 可以一次性引入一个空间内的多个元素
- 如果必要的情况下,也可以直接使用空间引入,但是注意被引入空间的元素不允许直接使用非限定名称访问,必须使用被引入空间的最后一级空间+元素访问(不常使用,引入方便但是使用不方便:限定名称)