對名稱空間的一點個人理解

junwind發表於2020-09-08

依託PHP官網結合自己的實際操作上,對namespace的一點理解;
如有錯誤的地方,歡迎大家來糾正,謝謝各位大佬!

什麼是namespace?

可以對比檔案系統,一個檔案一定存在於某個目錄下,同一個目錄下,也不能存在兩個相同的檔名稱,因此目錄就可以看做是檔案的名稱空間,可以使用絕對,相對等方式訪問到;

或者更直白的說,假如一個房間(全域性 \),裡面放置有很多箱子(namespace),每個箱子都有自己的編號(如 App, DB, Model),並且箱子中還可以巢狀其它箱子(如 App\User , App\DB\Mysql),每個箱子裡面存放有自己所屬的物品(程式碼), 現在假設有三個箱子中都有一個蘋果,讓你取一個,此時會產生矛盾,因為不知道應該拿哪個蘋果,所以還需要給定一個箱子的編號才行

下面用實際的程式碼來進一步體現出上面的問題:
class1.php

<?php
class class1{}

class2.php

<?php
class class1{}

index.php

<?php
include './class1.php';
include './class2.php';

//此時想例項化class1.php和class2.php中的class1類時,會報錯,因為有衝突
$obj1 = new class1();
$obj2 = new class1();
var_dump($obj1 , $obj2);

現在如何解決這個問題?
當然,直接改一下類名即可,但是在一個業務大的專案中,是難以直接改類名稱維護的,因此引入名稱空間就很方便的解決了這種問題

下面就分別給class1.php,class2.php加上namespace
class1.php

<?php
namespace class1;
class class1{}

class2.php

<?php
namespace class2;
class class1{}

index.php

<?php
include './class1.php';
include './class2.php';

// $obj1 = new class1();    //相當於 new \class1();  全域性下,找不到,會報錯
$obj1 = new class1\class1();    //object(class1\class1)#1 (0) { }
$obj2 = new class2\class1();    //object(class2\class1)#2 (0) { }
$obj3 = new \class2\class1();    //object(class2\class1)#3 (0) { }    這裡\class2\和class2\是相同的意思,因為當前指令碼是一個全域性名稱空間中
var_dump($obj1 , $obj2, $obj3);

PHP為什麼要引入namespace?

  1. 解決使用者寫的 類,函式,常量,以及內建的,第三方的,之間相互同名產生的衝突;
  2. 為很長的識別符號加一個別名,方便使用和可讀性;

使用示例

<?php
namespace my\name;

//下面就是所屬這個名稱空間的類,函式,常量
class MyClass {}                # my\name\MyClass{}
function myfunction() {}        # my\name\myfunction()
const MYCONST = 1;                # my\name\MYCONST

$a = new MyClass;                # object(my\name\MyClass)#1 (0) { }
$c = new \my\name\MyClass;         # object(my\name\MyClass)#2 (0) { }     
$a = strlen('hi');                 # \strlen('hi') 因為當前空間中沒有定義,找全域性
$d = namespace\MYCONST;            # my\name\MYCONST
$d = __NAMESPACE__ . '\MYCONST'; # my\name\MYCONST
echo constant($d);

注意:名為PHP或php的名稱空間,以及以這些名字開頭的名稱空間(例如PHP\Classes)被保留用作語言核心使用,而不應該在使用者空間的程式碼中使用。

子名稱空間

這個最好是結合目錄結構來說,不然意義不大,因為如果同一個目錄下,各指令碼採用不同的分層namespace名稱,則沒什麼含義

/App/Controller/Controller.php --> App\Controller;
/App/Model/User.php --> App\Model;
/App/Base.php --> App;

目前很多框架中,都是類似這樣的方式

官方文件是這樣舉例的:

<?php
namespace MyProject\Sub\Level;

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

在同一個指令碼檔案中申明多個名稱空間

這種方式是不建議的,難以管理維護,也不符合oop的思想;下面給出簡易的程式碼說明;

# 方式1
namespace Db\mysql;
//class,func,const

namespace Db\redis;
//class,func,const

# 方式2
namespace Db\mysql{
//class,func,const
}

namespace Db\redis{
//class,func,const
}

# 將全域性的非名稱空間中的程式碼與名稱空間中的程式碼組合在一起,只能使用大括號形式的語法。全域性程式碼必須用一個不帶名稱的 namespace 語句加上大括號括起來
namespace MyProject {
//class,func,const
}

namespace {
    //code
}

使用名稱空間

  • 問題:PHP是如何知道使用哪一個名稱空間中的class,function,const?
    簡單說,就是雖然引入了名稱空間,但是比如函式名都相同,如何知道就是某名稱空間下的函式?
    可以對比檔案系統,比如當前所在目錄是app:
    • 訪問index.php,就對應app/index.php; –> 非限定名稱
    • 訪問controller/index.php,對應 app/controller/index.php –> 限定名稱
    • 訪問/public/index.php,那就直接是/public/index.php –> 完全限定名稱

三種引入namespace的方式:

  1. 非限定名稱,或不包含字首的類名稱 : $a=new foo();foo::staticmethod();
    如果當前名稱空間是 current,foo 將被解析為 current\foo ;
    如果是全域性的,則解析為 foo\foo
    注意:如果名稱空間中的函式或常量未定義,則該非限定的函式名稱或常量名稱會被解析為全域性函式名稱或常量名稱

  2. 限定名稱,或包含字首的名稱,如 $a = new subnamespace\foo();subnamespace\foo::staticmethod(); , 的名稱空間是 current,則 foo 會被解析為current\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()
foo::staticmethod();     // 解析為類 Foo\Bar\foo 的靜態方法staticmethod
echo FOO;                 // 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
$b = \INI_ALL; // 訪問全域性常量 INI_ALL
$c = new \Exception('error'); // 例項化全域性類 Exception
?>

注意一個點,函式,常量不加\,首先會找當前名稱空間中的,找不到,才使用全域性,類則不行,找不到直接報錯

名稱空間和動態語言特徵

簡單的說,當使用動態的方式載入類,函式,常量時,名稱空間前面會自動加\
如官網文件的示例:
example1.php

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

$a = 'classname';
$obj = new $a;                         // prints classname::__construct
$b = 'funcname';
$b();                                 // prints funcname
echo constant('constname'), "\n";     // prints global
?>

example2.php

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

include 'example1.php';


$a = 'classname';
$obj = new $a; // prints classname::__construct
$b = 'funcname';
$b(); // prints funcname
echo constant('constname'), "\n"; // prints global

/* note that if using double quotes, "\\namespacename\\classname" must be used */
//如果使用了雙引號,必須得這麼寫 "\\namespacename\\classname"
$a = '\namespacename\classname';
$obj = new $a; // prints namespacename\classname::__construct
$a = 'namespacename\classname';
$obj = new $a; // also prints namespacename\classname::__construct
$b = 'namespacename\funcname';
$b(); // prints namespacename\funcname
$b = '\namespacename\funcname';
$b(); // also prints namespacename\funcname
echo constant('\namespacename\constname'), "\n"; // prints namespaced
echo constant('namespacename\constname'), "\n"; // also prints namespaced
?>

namespace關鍵字和__NAMESPACE__常量

  • PHP支援兩種抽象的訪問當前名稱空間內部元素的方法,__NAMESPACE__ 魔術常量和namespace關鍵字。
  • 常量__NAMESPACE__的值是包含當前名稱空間名稱的字串。在全域性的,不包括在任何名稱空間中的程式碼,它包含一個空的字串。
<?php
namespace Myproject{
    echo __NAMESPACE__;        //Myproject
}

namespace{
    echo __NAMESPACE__;        //empty
}
  • 使用__NAMESPACE__動態建立名稱
<?php
namespace MyProject;

class Car{}
class Pet{}
function get($classname)
{
    $a = __NAMESPACE__ . '\\' . $classname;
    return new $a;
}
$benz = get('Car');
$dog =  get('Pet');
print_r($benz);        //MyProject\car Object ( )
print_r($dog);        //MyProject\Pet Object ( )
  • 關鍵字 namespace 可用來顯式訪問當前名稱空間或子名稱空間中的元素。它等價於類中的 self 操作符。
    簡單說就是指向當前的名稱空間名稱,使用它來訪問的話,根據當前名稱空間不同,自動引用
<?php
namespace MyProject;

use blah\blah as mine; // see "Using namespaces: importing/aliasing"

blah\mine(); // calls function MyProject\blah\mine()
namespace\blah\mine(); // calls function MyProject\blah\mine()

namespace\func(); // calls function MyProject\func()
namespace\sub\func(); // calls function MyProject\sub\func()
namespace\cname::method(); // calls static method "method" of class MyProject\cname
$a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
$b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
?>
<?php

namespace\func(); // calls function func()
namespace\sub\func(); // calls function sub\func()
namespace\cname::method(); // calls static method "method" of class cname
$a = new namespace\sub\cname(); // instantiates object of class sub\cname
$b = namespace\CONSTANT; // assigns value of constant CONSTANT to $b
?>

使用名稱空間:別名/匯入

  1. 允許通過別名引用或匯入外部的完全限定名稱
  2. 為類名稱使用別名
  3. 為介面使用別名
  4. 為名稱空間名稱使用別名
  5. 匯入函式或常量或者為它們設定別名
本作品採用《CC 協議》,轉載必須註明作者和本文連結
六月的風

相關文章