ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
认识命名空间 ========== 理解命名空间 ----------- 在很久很久以前, 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>