认识命名空间
==========
理解命名空间
-----------
在很久很久以前, PHP的世界大致上是这样的: 启动一个php脚本后, 一个个函数, 一个个类就被载入到同一个屋檐下, 开始按照你设计的逻辑一条条执行就够了.
在这种情况下, 为了让程序顺利执行, 你必须确保一点: 同一个屋檐下的所有函数或者类必须有互不相同唯一的名字. 否则当出现重名时, php就不知道该执行哪个, 因此遇到这种情况, php必然会报错终止.
在那时, 这确实很容易做到, 因为那时的php的项目都很单纯, 很小, 没有野心, 一个项目中的函数和类数量有限, 它们可以相对和谐地共同生活在同一个屋檐下.
但是, 随着互联网行业发展, 行业对Web项目的要求也在迅速提高: 一个PHP项目可能有数人甚至数十人共同开发, 项目需求庞大复杂. 产品更新迭代的需求越来越快.
这时, 在一个屋檐下容纳所有的函数和类导致这个屋檐越来越拥挤, 难以管理. 当屋檐倒塌(需求出现重大变更), 也许埋葬的就是整个项目(代码难以复用) . 这时, 一系列迫切需要解决的问题出现了:
1. 项目不同的部件之间如何避免出现函数和类名的冲突?
2. 项目越来越大, 命名时单词似乎不够用了, 如何让函数和类命名时更容易?
3. 面对一大堆的函数和类, 如何有条理地管理它们的载入过程?
4. 面对快速变化的市场和需求, 项目必须模块化, 易分割, 易替换, 可重用.
命名空间(namespace)为解决以上问题提供重要支撑.
namespace 与逻辑无关, 它是一个管理概念, 通过命名空间你可以把函数和类管理的井井有条.
让我们形象一点来阐述到底什么是"namespace".
中国有约70万个行政村, 假如你的家乡在"王村", 而中国叫"王村"的村子可能不下数百个. 而我想去访问你的家乡. 我该如何去?
你一定会这样告诉我: **山西省 太原市 平遥县 西六支乡 王村**(假想地址). 如果王村就是一个class, 那么你正是通过namespace确保访问到你的家乡,而不是访问另一个王村.
用程序语言的书写习惯, 上面的地址可以换一种格式: **山西省\太原市\平遥县\西六支乡\王村**
好了, 中文太复杂, 让我们换成拼音: **ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun**.
如果你初次接触命名空间, 现在是不是有些恍然大悟, 原来这就是命名空间, 它可以让我们轻松从70万个村子(class)中精确找到自己真正要访问的那个王村(class)而不会迷路.
使用命名空间
----------
在php中,声明一个命名空间非常简单,使用`namespace` 关键字:
```
namespace ShanXi\TaiYuan\PingYao\XiLiuZhi;
```
在声明了命名空间之后, **这条声明之后直到下一条命名空间声明之前**的所有函数,类,const常量就隶属于这个命名空间了.
```
<?php
namespace ShanXi\TaiYuan\PingYao\XiLiuZhi;
const WangCun="I am WangCun.";
function wang_cun(){
echo WangCun;
}
class WangCun {
public function getName(){
wang_cun();
}
}
namespace ShanXi\TaiYuan\PingYao\Other;
const WangCun="I am WangCun form Other.";
function wang_cun(){
echo WangCun;
}
class WangCun {
public function getName(){
wang_cun();
}
}
namespace Test;
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
$obj2=new \ShanXi\TaiYuan\PingYao\Other\WangCun;
$obj1->getName(); // 输出: "I am WangCun."
$obj2->getName(); // 输出: "I am WangCun from Other."
```
通常一个php文件中只声明一个命名空间, 为了解释命名空间的用法和特性, 上面的示例, 我故意在同一个文件中声明了三个命名空间.
如你所见, 我们在同一文件中似乎声明了两次名称"相同的"const常量, 两个名称"相同的"函数, 两个名称"相同的"class. 它们可以和谐共处,不会产生冲突,因为它们位于不同的屋檐(namespace)下.
如果你足够细心, 观察上面的代码你已经能够知晓如何访问命名空间下的成员:
* 只要加上命名空间路径, 就可以访问其他命名空间的成员.
* 如果没有加命名空间路径, PHP就将从当前命名空间下寻找成员.
### 一点疑惑
在上面的代码中, 为什么使用的是:
```
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
```
而不是
```
$obj1=new ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
```
这就像访问目录中的文件一样, 如果你不加`\\`, php会认为你要访问的是相对的命名空间, php会认为你想访问的是`\Test\ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun`
深入命名空间
------------
### 全局命名空间
如果一个类或函数没有定义在任何namespace之下, 那么它就属于**全局命名空间**.
通常, 在一个命名空间下的代码中访问**类**或**const常量**时, php只会在当前命名空间下寻找. 如果未找到, 则会报错,甚至退出. 不会进入全局空间搜索.
但对于函数访问则特殊一点, 因为php是从函数式编程语言发展而来, 内置的数千函数现在都隶属于了全局命名空间. 如果每次使用函数时, 都加上`\\`前缀无疑很容易让人情绪崩溃.
因此, 如果在命名空间下没有找到这个函数, php会试图再去全局空间找一次. 在全局空间没有找到才会报错.
* 有`\\`符号, 则表示绝对命名空间路径. 没有`\\`符号, 则表示相对命名空间路径.
* 全局空间习惯上也可以称为"根空间"
### use 和 as
到现在, 我相信你已经理解并学会了命名空间的简单使用. 让我们继续深入.
还是上面的示例代码, 让我们专注于 Test 命名空间:
```
namespace Test;
$obj1=new \ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
$obj2=new \ShanXi\TaiYuan\PingYao\Other\WangCun;
$obj1->getName(); // 输出: "I am WangCun."
$obj2->getName(); // 输出: "I am WangCun from Other."
```
在访问其他命名空间成员时, 我指定了完整路径, 只访问一个成员还说得过去, 如果访问多个成员, 每次写完整路径岂不是很烦人?
`use` 和 `as` 关键字解决你的烦恼, 下面是改造后的代码示例:
```
namespace Test;
use ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun as WangCun;
use ShanXi\TaiYuan\PingYao\Other\WangCun as OtherWangCun;
$obj1=new WangCun;
$obj2=new OtherWangCun;
$obj1->getName(); // 输出: "I am WangCun."
$obj2->getName(); // 输出: "I am WangCun from Other."
```
嗯, 终于可以少敲很多次键盘了. `use` 和 `as` 做了什么?
* **use** 告诉php, 我要在Test命名空间下**直接**使用其他命名空间的某个类.
* **as** 则告诉php这个其他命名空间的类在当前命名空间下使用什么名称去访问.
* 当你不需要重新设定使用的名称时, 而且不会引发重名问题时, **as** 可以省略
因此上面的代码可以简化为:
```
namespace Test;
use ShanXi\TaiYuan\PingYao\XiLiuZhi\WangCun;
use ShanXi\TaiYuan\PingYao\Other\WangCun as OtherWangCun;
...
```
### 用namespace管理代码载入
曽几何时, php项目中到处充斥着include. 你必须时时小心以下问题:
* 确保include的文件确实存在
* 确保include的文件的内容是你需要的
* 确保include的文件的内容不与以有代码产生冲突
* 每次修改include的文件, 都要小心翼翼确保不会产生冲突
* 在一个被include的文件删除后, 你必须在所有代码中搜索一遍, 删除每一处include
* include时还需要特别留意工作目录和相对路径的问题.
对程序员来说, 这简直一场噩梦. 因为它们与逻辑几乎毫无关系, 却需要耗费你大量的时间心血去处理, 最重要的是, 这些问题真的太太伤感情了, 很容易让你怀疑自己选择程序员是否是个正确的决定.
namespace 助你解脱.
namespace 为你的代码带来了分明的层次感, 它几乎就是我们电脑上目录结构的翻版. 你的电脑上文件目录再多, 我相信你也能轻松找到那个自己密不外宣的特殊目录. 你越悉心管理自己的电脑目录, 你使用电脑的工作效率就越高,出错概率越低.
所以, 你的php项目的 namespace 规划的越合理, 越层次分明, 你的项目就会越容易维护.
如果我们把命名空间与文件系统上的层次结构对应起来, 自动载入就成为了现实, include就成为了历史:
目录结构:
```
.\ShanXi
|- TaiYuan
|- PingYao
|- XiLiuZhi
| |- WangCun.php
|
|- Other
|- WangCun.php
```
现在你只需要定义一个自动载入函数注册到autload函数栈, 为命名空间路径和本地路径的关系建立逻辑, 根据这个固定的逻辑, 就可以自动载入任何的符合该逻辑的类了!
最重要的是:
1. 自定义的自动载入函数可以注册许多个, 意味着你可以设计多种自动载入逻辑. 在访问类时, php会根据注册时的顺序, 依次执行, 直到找到需要载入的文件为止.
2. 只有在第一次访问类时, 才会触发自动载入, 如果一个类没有用到, 那么这个类文件也就不会被载入.
在你设计好自动加载函数后, 调用`spl_autoload_register($callable)` 即可将你的自动加载器注册到php的autoload栈中.
所有使用了命名空间的现代的PHP项目和框架, 都是使用这种机制实现了类文件的自动加载. 你只需要按照规则放置类文件, 就可以随处使用它们.
介绍到这里, 你对命名空间的了解已经足够多. 如果还想彻底精通, 可以访问PHP文档中命名空间部分:
<http://php.net/manual/zh/language.namespaces.php>
- 序
- 前言
- 内容简介
- 目录
- 基础知识
- 起步
- 控制器
- 模型
- 模板
- 命名空间
- 进阶知识
- 路由
- 配置
- 缓存
- 权限
- 扩展
- 国际化
- 安全
- 单元测试
- 拿来主义
- 调试方法
- 调试的步骤
- 调试工具
- 显示trace信息
- 开启调试和关闭调试的区别
- netbeans+xdebug
- Socketlog
- PHP常见错误
- 小黄鸭调试法,每个程序员都要知道的
- 应用场景
- 第三方登录
- 图片处理
- 博客
- SAE
- REST实践
- Cli
- ajax分页
- barcode条形码
- excel
- 发邮件
- 汉字转全拼和首字母,支持带声调
- 中文分词
- 浏览器useragent解析
- freelog项目实战
- 需求分析
- 数据库设计
- 编码实践
- 前端实现
- rest接口
- 文章发布
- 文件上传
- 视频播放
- 音乐播放
- 图片幻灯片展示
- 注册和登录
- 个人资料更新
- 第三方登录的使用
- 后台
- 微信的开发
- 首页及个人主页
- 列表
- 归档
- 搜索
- 分页
- 总结经验
- 自我提升
- 进行小项目的锻炼
- 对现有轮子的重构和移植
- 写技术博客
- 制作视频教程
- 学习PHP的知识和新特性
- 和同行直接沟通、交流
- 学好英语,走向国际
- 如何参与
- 浏览官网和极思维还有看云
- 回答ThinkPHP新手的问题
- 尝试发现ThinkPHP的bug,告诉官方人员或者push request
- 开发能提高效率的ThinkPHP工具
- 尝试翻译官方文档
- 帮新手入门
- 创造基于ThinkPHP的产品,进行连带推广
- 展望未来
- OneThink
- ThinkPHP4
- 附录