PHP的命名空间

885 阅读7分钟

前言

做开发也有2年的时间了,发现一个问题,做的越久,对基础知识掌握的越少,越喜欢理解一些很“高深”的东西,看似高深,其实low到不行。慢慢你会发现,越是学习高深的东西,对基础知识要求越高,比如本节说的命名空间,你对这个不了解,那么你对composer的文件自动加载就是一头雾水

踩过一些坑发现在学习这件路上,不能懒,只要你一懒下来,你之前学习的东西,会忘记的一干二净,什么都没有,记下来,记下来是最好的方式,哪怕这个知识点简单的不能再简单了,记下来

什么是命名空间

在回答这个问题之前,我想先回答为什么要有命名空间,如果我们反着问,没有命名空间是什么样子呢。想象一下你现在写了一个名为User的类,我也写了一个User的类,我们把这两个类放在一个文件里面。这个时候就会出错,提示不能有两个类名相同的类出现

如果我们给这两个类加上命名空间,namespae jacknamespae tom,这个时候就不出现刚才的问题了。

在PHP官网中,提到可以通过命名空间解决在编写类库或函数的时候遇到这两类问题:

  • 用户编写的代码与PHP内置的类/函数/常量或第三方类/函数/常量之间的名字冲突。
  • 为很长的标识符名称创建一个别名,提高源代码的可读性

上面的第一点我明白,第二点有点糊涂,后面发现这个要在使用命名空间的时候才会说到,一句话来说,就是命名空间可以创建别名,比如使用use App\Models\User,在引入User后同时也创建了一个User的别名,往后我们使用User就相当于使用\APP\Models\User

其实命名空间就是借助文件系统中的目录来解决命名这个难题的,应用到程序设计领域就是命名空间的概念

要记住一点就是,不能说命名空间就等同于目录,只不过借助目录这样一个原理,来实现同名的函数、类等等

定义命名空间

在PHP中定义命名空间非常的简单,使用namespage关键词来声明就可以了,如下:

namespace Jack;

const CARD_ID=5;
class User{}
function getInfo(){}

虽然说PHP的代码都可以包含在命名空间中,但也只有类、接口、函数、常量会受到影响。像变量就不起作用,所以在使用变量前一定得初始化。

命名空间也可以像目录文件一样,指定层次化的命名空间的名称,比如Laravel框架中定义的命名空间namespace App\Http\Controllers,这在你使用composer引入第三方类库更常见了

使用命名空间

使用命名空间是非常简单的操作,使用use关键字就可以导入外部的完全限定名称,并且还可以名别名

namespace foo;

// 下面两个是等价的
use My\Full\Classname;
use My\Full\Classname as Classname;

通过上面的例子,我们知道use关键字可以在引入外部命名空间的同时还给它取别名,后面就可以使用别名,来代替之前的完全限定名称了

因为导入的命名空间必须是完全限定名称,前导的反斜杠是不必需要的,例如use Http\Models

不仅可以对命名空间使用别名,还可以给类名称、接口、函数、常量设置别名,例如:

use My\Full\Classname as Another;
use function My\Full\functionName as func;
use const My\Full\CONSTANT;

也可以不使用use关键字导入类,直接在调用类的代码处使用完全限定名称,例如:\App\Model\Use ::first()

如何解析命名空间

现在已经知道如何使用命名空间,但对PHP使用哪一个命名空间中的元素是不知道的,比如有\foo()、\config\foo()两个函数,到底使用哪一个函数我们是不知道的,对此PHP有三种使用命名空间名称的方式

非限定名称

名称中不含有命名空间分隔符,例如 $a=new foo()。如果当前命名空间是currentnamespace,foo将被解析为currentnamespace\foo;如果当前命名空间是全局的,则foo会被解析为foo。

在PHP中对非限定的类、函数、常量,采用的是不同的优先策略来解析这些名称

类名称总会解析到当前命名空间中

namespace A\B\C;
class Exception extends \Exception {}

$a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception('hi'); // $b 是类 Exception 的一个对象

函数和常量如果在当前命名空间中不存在,那么会在全局空间中使用他们

namespace A\B\C;

const E_ERROR = 45;
function strlen($str)
{
echo 'hello';
}

echo E_ERROR, "\n"; // 输出 "45"
echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL

echo strlen('hi'), "\n"; // 输出 "hello"

限定名称

限定名称,名称中含有命名空间分隔符,例如 $a = new subnamespace\foo()。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo;如果当前的命名空间是全局的,foo 会被解析为subnamespace\foo。

namespace Foo\Bar\subnamespace;

function foo() {}

namespace Foo\Bar;

subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo

完全限定名称

名称以命名空间分隔符开始的标识符,例如$a = new \currentnamespace\foo(),在这种情况下,foo总是被解析为代码中的文字名currentnamespace\foo。

namespace Foo\Bar\subnamespace;

function foo() {}

\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo

说了这么多,意义是什么呢,对于平时的代码编写有什么帮助,个人认为总结下来,在这些方面平时可以注意一下。在使用函数的时候,使用非限定名称,如果当前空间没有找到函数,PHP会自动在全局空间查找;对于类、接口要是使用完全限定名称,明确它们所在的空间,不会因为当前环境的变化而受到影响

其他

命名空间常量

__NAMESPACE__的值是包含当前命名空间名称的字符串,如果是在全局空间,则是一个空字符串

// file1.php
namespace MyProject;
echo '"', __NAMESPACE__, '"'; // 输出 "MyProject"

// file2.php
echo '"', __NAMESPACE__, '"'; // 输出 ""

关键字namespace可以显示访问当前命名空间或子命名空间中的元素

namespace Help;

function test(){echo 'test';};
echo namespace\test();  //calls function Help\test()

这些东西到底有什么意义呢,有的,就是我们可以的动态创建名称,例如:

namespace MyProject;

class Student
{
	public function __construct()
	{
		echo 'new Student';
	}
}
function get($classname)
{
    $a = __NAMESPACE__ . '\\' . $classname;
    return new $a;
}

get('Student');

仔细发现,你会觉得这个代码有问题,都在同一个命名空间,而且你使用的又是限定名称,__NAMESPACE__ . '\\' . $classname会被解析成MyProject\MyProject\Student,运行代码发现又没有这样的问题,这到底是怎么回事

这是因为在动态的类名称、函数名称或常量名称中,必须要使用完全限定名称,又因为限定名称和完全限定名称没有区别,所以前导反斜杠是不必要加的

这样就很好的解释前面的问题,__NAMESPA CE__ . '\\' . $classname虽然是限定名称,但是它是动态类名称,不必要加前导反斜杠

如果上面的例子中的__NAMESPACE__ . '\\' . $classname改为$classname,结果会怎么样呢

Fatal error: Uncaught Error: Class 'Student' not found

PHP会把$classname解析为完全限定名称,在我们这个例子中就是全局空间下的Student类

为什么PHP会有这样的设计呢,我想这跟PHP的自动加载有很大的关系,完全限定名称就不需要在当前命名空间查找元素了,效率比非限定名称要好一些

啰嗦几句

在这篇文章写完之后,我发现对命名空间这块,有了新的认识,比如命名空间是怎么来的,是因为类重名、函数重名,参照文件目录的原理,设计了这么一个东西。它的出生是来解决问题的,不是制造问题的,在使用命名空间的时候,就要对它的原理掌握清楚,使用非限定名称会有哪些问题,那些地方是必须要使用完全限定名称的。

通过这么记下来,总结下来,要比以前自己走马观花式的学习方式要好一下,我不能说这会好太多,只是在某一两个知识点面前,会有深刻的认识,就是这个地方深入的了解,你对其他块的知识慢慢的也会了解

就比如下面一篇,我要学习的PSR-4自动加载规则,就要求你对PHP的命名空间要有很好的理解,不然的话,很多地方你只能记住,而不能有深刻的认识,时间一长你就忘了