PHP学习笔记14:命名空间

PHP学习笔记14:命名空间

image-20211129162010327

图源:php.net

在php没有引入命名空间之前,构建比较大的应用时可能会遇到命名冲突的问题,比如:

<?php
// a.php
class MyClass{
    
}
<?php
// b.php
require_once './a/a.php';
class MyClass{
    // PHP Fatal error:  Cannot declare class MyClass, because the name is already in use in ...
   
}

如果是多人协同开发的大型项目,或者需要使用第三方库,这种因为引用其它php代码而导致的命名冲突是很容易出现的。而手动修改所冲突名称也是一个不现实的解决方式。

因此php引入了命名空间。

定义

定义一个命名空间很简单:

<?php
namespace xyz\icexmoon\php_notes\ch14;

但需要注意的是,namespace语句必须是除了PHP标签之外的当前代码文件的第一行语句(declare语句除外)。即使是其它非php语句也不能出现在namespace语句之前:

<html>
<?php
namespace xyz\icexmoon\php_notes\ch14;
// PHP Fatal error:  Namespace declaration statement has to be the very first statement or after any declare call in the script in ...

命名空间的命名习惯是使用域名来避免可能的出现的重复,举例来说,我个人的域名是icexmoon.xyz,其中xyz是顶级域名,icexmoon是二级域名,所以我可以用xyz\icexmoon\project_name来定义我的某个php项目的命名空间。这肯定不会与其它人定义的命名空间冲突。

拥有域名的公司、组织和个人都可以使用这种方式定义。

如果是没有购买域名的个人,还可以借助免费的代码托管网站域名来定义命名空间,比如我的Github主页是http://github.com/icexmoon,我就可以将php项目的命名空间定义为com\github\icexmoon,一般来说这也是不会出现重复的。

现在我们看如何使用命名空间解决命名冲突:

<?php
// a.php
namespace xyz\icexmoon\php_notes\ch14\conflict2\A;
class MyClass{
    
}
<?php
// b.php
namespace xyz\icexmoon\php_notes\ch14\conflict2;
require_once './a/a.php';
class MyClass{
   
}
$mc = new MyClass;
$mc1 = new A\MyClass;

php的同一个命名空间可以出现在多个代码文件中,也就是拆分为多个代码文件。一般来说命名空间的层级结构是和代码目录结构完全一致的,所以也就是说一个目录中的代码文件共享一个命名空间。

这和Go语言的包是一致的。

比较特别的是,一个代码文件还可以有多个命名空间:

<?php

namespace xyz\icexmoon\php_notes\ch14\define3\A {
    class MyClass{}
}

namespace {
}

namespace xyz\icexmoon\php_notes\ch14\define3\B {
    use xyz\icexmoon\php_notes\ch14\define3\A\MyClass as MyClass2;
    class MyClass{}
    $mc = new MyClass();
    $mc2 = new MyClass2();
}

此时定义的命名空间需要使用{}来区分,并且同一文件中定义的全局代码需要定义在代表全局命名空间的namespace {...}中。

名称解析规则

在使用命名空间的代码中使用类、函数、常量时有四种命名空间使用方式:

  • 非限定名称,如new MyClass
  • 限定名称,如new A\MyClass
  • 完全限定名称,如new \xyz\icexmoon\php_notes\ch14\conflict2\A\MyClass
  • 相对名称,如new namespace\MyClass

非限定名称,也就是当前命名空间中那些没有任何前缀的类名、函数名、常量,这就像没有引入命名空间时那样的代码,此时对于类名,如果当前命名空间中有定义,就使用该类定义,如果没有,就会报错:

<?php
namespace xyz\icexmoon\php_notes\ch14;
$mc = new MyClass();
// PHP Fatal error:  Uncaught Error: Class "xyz\icexmoon\php_notes\ch14\MyClass" not found in ...
<?php
namespace xyz\icexmoon\php_notes\ch14;
class MyClass{

}
$mc = new MyClass();

函数和常量的非限定名称的解析过程与类不同,如果当前命名空间中存在就使用,如果不存在,会在全局命名空间中查找,如果存在就使用,否则报错。这么设计是有意义的,因为诸如int_valis_array之类的内置函数是非常常见的,如果每次使用都需要添加上全局命名空间名称使用(比如\int_val)就太麻烦了,常量也一样:

<?php
namespace xyz\icexmoon\php_notes\ch14;
echo intval("123");
// 123
<?php

namespace xyz\icexmoon\php_notes\ch14;

function intval(array $arr): int
{
    $total = 0;
    foreach ($arr as $val) {
        $total += \intval($val);
    }
    return $total;
}
echo intval([1, 2, 3]) . PHP_EOL;
echo \intval("123") . PHP_EOL;
// 6
// 123

限定名称指的是subnamespace\class_name这样的类名、函数名、常量。会被结合当前命名空间解析,比如如果当前命名空间是A\B,则C\MyClass会被解析为A\B\C\MyClass

<?php

namespace xyz\icexmoon\php_notes\ch14\analysis3\my_class;

function print_namespace()
{
    echo "namespace:" . __NAMESPACE__ . PHP_EOL;
}
<?php

namespace xyz\icexmoon\php_notes\ch14\analysis3;
require_once './my_class/my_class.cls.php';
function print_namespace()
{
    echo "namespace:" . __NAMESPACE__ . PHP_EOL;
}
print_namespace();
my_class\print_namespace();
// namespace:xyz\icexmoon\php_notes\ch14\analysis3
// namespace:xyz\icexmoon\php_notes\ch14\analysis3\my_class

魔术常量__NAMESPACE__可以代表代码所在的命名空间名称。

完全限定名称指的是\开头的完整的命名空间指定的类名、函数名、常量,这样的命名会明确地指向一个命名空间中的定义:

<?php

namespace xyz\icexmoon\php_notes\ch14\analysis6;

require_once './my_class/my_class.cls.php';
function print_namespace()
{
    echo "namespace:" . __NAMESPACE__ . PHP_EOL;
}
print_namespace();
\xyz\icexmoon\php_notes\ch14\analysis6\my_class\print_namespace();
// namespace:xyz\icexmoon\php_notes\ch14\analysis6
// namespace:xyz\icexmoon\php_notes\ch14\analysis6\my_class

可以使用完全限定名称来指定内置类、常量、函数,比如\intval\PHP_EOL

相对名称指的是使用namespace定义的类名、函数名、常量,其中namespace可以替换为当前命名空间的名称:

<?php

namespace xyz\icexmoon\php_notes\ch14\analysis7;

require_once './my_class/my_class.cls.php';
function print_namespace()
{
    echo "namespace:" . __NAMESPACE__ . PHP_EOL;
}
print_namespace();
namespace\my_class\print_namespace();
// namespace:xyz\icexmoon\php_notes\ch14\analysis7
// namespace:xyz\icexmoon\php_notes\ch14\analysis7\my_class

导入命名空间

如果每次使用其它命名空间的函数或类都要使用限定名称或完全限定名称,那无疑会相当麻烦。事实上使用命名空间时,最常见的是将需要使用到的其它命名空间直接导入到当前命名空间。导入的可以是类、函数、常量,或者是命名空间。

导入整个命名空间意味着可以使用该命名空间中的常量、类、函数:

<?php

namespace xyz\icexmoon\php_notes\ch14\use;

require_once './my_class/my_class.php';

use xyz\icexmoon\php_notes\ch14\use\my_class;

$mc = new my_class\MyClass();
$mc->number = 10;
my_class\print_my_class($mc);
echo my_class\NAME_SPACE . PHP_EOL;
// MyClass(10)
// xyz\icexmoon\php_notes\ch14\use\my_class

也可以直接导入命名空间中的类名、常量、函数:

<?php

namespace xyz\icexmoon\php_notes\ch14\use;

use xyz\icexmoon\php_notes\ch14\use\my_class\MyClass;

use const xyz\icexmoon\php_notes\ch14\use\my_class\NAME_SPACE;

use function xyz\icexmoon\php_notes\ch14\use\my_class\print_my_class;

require_once './my_class/my_class.php';
$mc = new MyClass();
$mc->number = 10;
print_my_class($mc);
echo NAME_SPACE . PHP_EOL;
// MyClass(10)
// xyz\icexmoon\php_notes\ch14\use\my_class

如果导入的名称和当前命名空间中的名称冲突,可以将其定义为别名:

<?php

namespace xyz\icexmoon\php_notes\ch14\use;

use xyz\icexmoon\php_notes\ch14\use\my_class\MyClass;

use const xyz\icexmoon\php_notes\ch14\use\my_class\NAME_SPACE;

use function xyz\icexmoon\php_notes\ch14\use\my_class\print_my_class as print_myclass;

require_once './my_class/my_class.php';
function print_my_class(MyClass $mc)
{
    echo "redeclared print_my_class function." . PHP_EOL;
    echo "MyClass[number=>{$mc->number}]" . PHP_EOL;
}
$mc = new MyClass();
$mc->number = 10;
print_myclass($mc);
// MyClass(10)
print_my_class($mc);
// redeclared print_my_class function.
// MyClass[number=>10]
echo NAME_SPACE . PHP_EOL;
// xyz\icexmoon\php_notes\ch14\use\my_class

以上就是命名空间的全部内容。

谢谢阅读。

往期内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值