php备忘录(三)之命名空间

  • A+
所属分类:php

参考php手册中的命名空间说明,重新审视下面的笔记。 PHP官方手册的命名空间http://php.net/manual/zh/language.namespaces.php

(1)什么是php命名空间

PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。 不过在PHP当中还是有着相当重要的意义。

PHP 命名空间可以解决以下两类问题:

  1. 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
  2. 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。

默认情况下,所有常量、类和函数名都放在全局空间下。

(2)定义命名空间

虽然任意合法的PHP代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和traits)、接口、函数和常量

命名空间通过关键字namespace 来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间,除了一个以外:declare关键字。

命名空间通过关键字namespace 来声明。

<?php
namespace MyProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

?>

定义命名空间的限制条件:

必须在其它所有代码之前声明命名空间。在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。如下的代码是正确的。下面的代码声明了两部分命名空间的代码,一个是 MyProject, 另一个是 全局代码。

<?php
declare(encoding='UTF-8'); 
//声明命名空间 MyProject
namespace MyProject 
{
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function connect() { /* ... */  }
}

//声明全局代码
namespace 
{
    session_start();
    $a = MyProject\connect();
    echo MyProject\Connection::start();
}
?>

下面的代码是错误的:命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句

<html>
<?php
namespace MyProject; 
// 命名空间前出现了“<html>” 会致命错误 - 命名空间必须是程序脚本的第一条语句
?>

另外,与PHP其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。

(3)定义子命名空间

子命名空间的层级,与目录和文件的关系很象。PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义,下面的例子定义了一个三层的命名空间:

<?php
namespace MyProject\Sub\Level;  //声明分层次的单个命名空间

const CONNECT_OK = 1;
class Connection { /* ... */ }
function Connect() { /* ... */  }

?>

上面的例子创建了:

常量 MyProject\Sub\Level\CONNECT_OK

类 MyProject\Sub\Level\Connection

函数 MyProject\Sub\Level\Connect

(4)同一个文件中,定义多个命名空间

也可以在同一个文件中定义多个命名空间。在同一个文件中定义多个命名空间有两种语法形式。

第一种,简单组合语法:

<?php
namespace MyProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }

namespace AnotherProject;

const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */  }
?>

第二种,大括号语法

<?php
namespace MyProject 
{
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function connect() { /* ... */  }
}

namespace AnotherProject 
{
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function connect() { /* ... */  }
}
?>

如果单个文件中定义多个命名空间,建议使用大括号形式的语法。

在实际的编程实践中,非常不提倡在同一个文件中定义多个命名空间。这种方式的主要用于将多个 PHP 脚本合并在同一个文件中。

 

将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,例如:

<?php
namespace MyProject 
{
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function connect() { /* ... */  }
}

// global code
namespace 
{
    session_start();
    $a = MyProject\connect();
    echo MyProject\Connection::start();
}
?>

(5)使用命名空间

  1. 非限定名称,或不包含前缀的类名称【纯变量名】(不包含任何命名空间前缀:类似纯文件名),例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。
  2. 限定名称,或包含前缀的名称【相对命名空间】(包含相对命名空间前缀:类似相对路径),例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespace\foo。
  3. 完全限定名称,或包含了全局前缀操作符的名称【绝对命名空间】(包含绝对命名空间前缀:类似绝对路径),例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo。

下面是一个使用这三种方式的实例:

file1.php

<?php
namespace Foo\Bar\subnamespace;

const FOO = 1;
function foo() {}
class foo
{
    static function staticmethod() {}
}
?>

file2.php

<?php
namespace Foo\Bar;
include 'file1.php';

const FOO = 2;
function foo() {}
class foo
{
    static function staticmethod() {}
}

/* 非限定名称 */
foo();                // 解析为 Foo\Bar\foo resolves to function Foo\Bar\foo
foo::staticmethod();  // 解析为类 Foo\Bar\foo的静态方法staticmethod。
echo FOO;             // resolves to constant Foo\Bar\FOO

/* 限定名称 */
subnamespace\foo();              // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod();// 解析为类 Foo\Bar\subnamespace\foo::staticmethod
echo subnamespace\FOO;           // 解析为常量 Foo\Bar\subnamespace\FOO
                                  
/* 完全限定名称 */
\Foo\Bar\foo();               // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
echo \Foo\Bar\FOO;            // 解析为常量 Foo\Bar\FOO
?>

注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如: \strlen() 或 \Exception 或 \INI_ALL。下面的例子:在命名空间内部访问全局类、函数和常量

<?php
namespace Foo;

function strlen() {}
const INI_ALL = 3;
class Exception {}

$a = \strlen('hi');   // 调用全局函数strlen,而不是本命名空间中的strlen
$b = \INI_ALL;        // 访问全局常量 INI_ALL,而不是本命名空间中的INI_ALL
$c = new \Exception('error');// 实例化全局类 Exception,而不是本命名空间中的Exception
?>

总结:

如果使用的是 完全限定名称即绝对命名空间(以 / 开始),使用时 直接解析到指定的 类、函数或常量。

如果使用的是非限定名称即纯名称,或限定名称 即 相对命名空间, 使用时,解析到 当前命名空间+它们的名称。

说明,上面的总结,仅仅限于非动态的类名称、函数名称或常量,对于动态的类名称、函数名称或常量请参考下面的说明。

(6)命名空间和动态语言特征

PHP 命名空间的实现受到其语言自身的动态特征的影响。这里先明确一个定义,即动态类、函数、常量。看下面的例子:

【动态访问元素】1.php, 这是一个普通的全局文件,使用了动态类、动态函数、动态常量。

<?php
class classname
{
    function __construct()
    {
        echo __METHOD__,"\n";
    }
}
function funcname()
{
    echo __FUNCTION__,"\n";
}
const constname = "global";

$a = 'classname';
$obj = new $a;      //使用了动态类,在全局空间正常打印 classname::__construct
$b = 'funcname';
$b();               //使用了动态函数,在全局空间正常打印 funcname
echo constant('constname'), "\n";      //使用了动态常量,在全局空间正常打印 global
?>

【动态访问命名空间的元素】2.php   这个文件中不仅动态访问元素,而且还包含在命名空间中。

<?php
namespace namespacename;
class classname
{
    function __construct()
    {
        echo __METHOD__,"<br>";
    }
}
function funcname()
{
    echo __FUNCTION__,"<br>";
}
const constname = "namespaced_global";

include '1.php';
//这里引用了1.php 会输出1.php文件总的三个信息:
// classname::__construct
// funcname
// global

// 动态访问类classname(非限定名称),解析到全局类,输出 classname::__construct
$a = 'classname';
$obj = new $a;  // 输出 classname::__construct
// 动态访问类classname(完全限定名称即有\),解析到全局类,输出 classname::__construct
$a = '\classname';
$obj = new $a;  // 输出 classname::__construct
// 没有使用动态访问,直接使用类, 解析到 当前命名空间+类, 输出 namespacename\classname::__construct (与使用动态类的方式不一样)
new classname();

// 动态访问函数funcname(非限定名称),解析到全局函数,输出 funcname
$b = 'funcname';
$b(); // 输出 funcname
// 动态访问函数funcname(完全限定名称即有\),解析到全局函数,输出 funcname
$b = '\funcname';
$b(); // 输出 funcname
// 没有使用动态访问,直接使用函数, 解析到 当前命名空间+函数, 输出 namespacename\funcname (与使用动态函数的方式不一样)
funcname(); // 输出 namespacename\funcname

// 动态访问常量constname(非限定名称),解析到全局常量,输出 global
echo constant('constname'), "<br>"; // 输出 global
// 动态访问常量constname(完全限定名称即有\),解析到全局常量,输出 global
echo constant('\constname'), "<br>"; // 输出 global
// 没有使用动态访问,直接使用常量, 解析到 当前命名空间+常量, 输出 
namespaced_global (与使用动态常量的方式不一样)
echo constname;

echo "<br>--------------<br>";

/* 如果使用双引号,用法如: "\\namespacename\\classname"  */
$a = '\namespacename\classname';
$obj = new $a; // 输出 namespacename\classname::__construct
$a = 'namespacename\classname';
$obj = new $a; // 同样输出 namespacename\classname::__construct
$b = 'namespacename\funcname';
$b(); // 输出 namespacename\funcname
$b = '\namespacename\funcname';
$b(); // 同样输出 namespacename\funcname
echo constant('\namespacename\constname'), "<br>"; // 输出 namespaced
echo constant('namespacename\constname'), "<br>";  // 同样输出 namespaced

总结,对于使用动态特征的类、函数、常量,使用时会映射到全局(有无\都是全局),如果不使用动态特征,会映射到当前空间 + 元素名称。

说明:命名空间只对接口、类、函数、常量起作用,如果使用动态特征,则将类、函数、常量等转化成了一个普通的变量,则命名空间不再对它起作用,因此会映射到全局。



  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: