您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)PHP中命名空間和自動加載類的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
PHP的命名空間(namespace)是php5.3之后才有的。這個概念在C#中已經(jīng)很早就有了,php中的namespace其實和c#的概念是一樣的。
為什么php中要使用namespace?
假設(shè)如果不使用namespace,那么每個類在一個項目中的名字就必須是固定的。因為php在new的時候不管是調(diào)用autoload還是調(diào)用已加載過的類,都存在一個類名對應(yīng)的文件。所以在沒有namespace的時候,我們會想各種命名規(guī)則來區(qū)分不同的類,比如project1_school1_class1_Student或者project2_school_class_Student。
引入namespace之后就可以將這個有效規(guī)避了,一個namespace就相當(dāng)于對應(yīng)一個文件路徑,查找這個類的時候,就會去對應(yīng)的文件路徑查找類定義文件了。
背景
最近有個朋友問我 PHP 命名空間是咋樣的,但是由于長期不做開發(fā),筆者實際上也已經(jīng)忘得差不多了,所以也回答不出來。只是記得和 Java 挺像的。事后重新查了一下 PHP 的官方文檔,并且和 Java 做對比,Java 的命名空間實際上來自于 JVM 本身的機(jī)制,JVM 是基于 class 字節(jié)碼文件加載類,由于類很容易出現(xiàn)重名的情況,換言之 class 字節(jié)碼文件也會出現(xiàn)重名情況,所以就需要使用目錄來管理不同的字節(jié)碼文件,而為了保證加載正常,所以就需要命名空間這種機(jī)制。當(dāng)然,也可以說是由于命名空間的存在才有了目錄管理的方式。但是 PHP 和 Java 不一樣,PHP 是一種動態(tài)腳本語言,它的代碼分散在所有腳本中,當(dāng)需要的時候才會使用 include 函數(shù)加載對應(yīng)的文件,所以 PHP 的命名空間,實際上是基于 PHP 的自動加載類,自動加載類實現(xiàn)了才能保證 PHP 命名空間存在的意義。
命名空間概述
命名空間據(jù)筆者所知應(yīng)該最早源于 C++ 語言,在 C++98 標(biāo)準(zhǔn)以后,為了保證各種命名不重合所推出的一種解決方案。現(xiàn)在的面向?qū)ο笳Z言基本都有這種機(jī)制,當(dāng)然除了命名空間以外,還有很多種方式,比如模塊化,不過實際上這些機(jī)制都是用來解決封裝問題的,所以筆者個人認(rèn)為并無好壞之分。先把 PHP 官方文檔代碼拉出來溜溜
<?php namespace my\name; // 參考 "定義命名空間" 小節(jié) class MyClass {} function myfunction() {} const MYCONST = 1; $a = new MyClass; $c = new \my\name\MyClass; // 參考 "全局空間" 小節(jié) $a = strlen('hi'); // 參考 "使用命名空間:后備全局函數(shù)/常量" 小節(jié) $d = namespace\MYCONST; // 參考 "namespace操作符和__NAMESPACE__常量” 小節(jié) $d = __NAMESPACE__ . '\MYCONST'; echo constant($d); // 參考 "命名空間和動態(tài)語言特征" 小節(jié) ?>
非常容易理解的代碼,從上面的代碼中可以看到 PHP 定義的命名空間是怎么樣的,不過筆者個人認(rèn)為其定義非常反人類,居然使用反斜杠來分隔命名空間路徑。不過有一點需要注意,名為 PHP 或 php 的命名空間,以及以這些名字開頭的命名空間(例如PHP\Classes)被保留用作語言內(nèi)核使用,而不應(yīng)該在用戶空間的代碼中使用。
定義命名空間
PHP 命名空間功能只能在 PHP5.3.0 以上版本使用,對于一個命名空間,只有類、接口、函數(shù)和常量會被包含在命名空間中。
<?php namespace MyProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } ?>
當(dāng)然,也可以使用花括號來包含所有需要的內(nèi)容,就像這樣。
<?php declare(encoding='UTF-8'); namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } namespace { // 全局代碼 session_start(); $a = MyProject\connect(); echo MyProject\Connection::start(); } ?>
不過這樣很容易造成縮進(jìn)上的問題,所以筆者不推薦使用,并且一般情況下,一個文件包含一個類,所以也不需要花括號來分割命名空間范圍。
使用命名空間
對于命名空間路徑來說,存在著三種形式
非限定名稱,或者說不包含前綴的類名稱。例如 $a=new foo(); 或 foo::staticmethod(); 。如果當(dāng)前命名空間是 currentnamespace , foo 將被解析為 currentnamespace\foo 。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,則 foo 會被解析為 foo`。
限定名稱,或包含前綴的名稱,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod(); 。如果當(dāng)前的命名空間是 currentnamespace ,則 foo 會被解析為 currentnamespace\subnamespace\foo 。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼, foo 會被解析為 subnamespace\foo 。
完全限定名稱,或包含了全局前綴操作符的名稱,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod(); 。在這種情況下, foo 總是被解析為代碼中的文字名 (literal name)currentnamespace\foo 。
由于 PHP 本身動態(tài)語言的特性,所以完全可以使用字符串動態(tài)訪問命名空間內(nèi)的元素。
<?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 */ $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 ?>
不過有一點需要注意,就是單雙引號之間的區(qū)別,單引號可以不需要處理 \ 的轉(zhuǎn)譯處理,而雙引號就必須使用 \\ 等轉(zhuǎn)譯符號。
Java 語言使用 import 機(jī)制引入命名空間,由于 Java 可以指定到類名,所以 Java 最多只能導(dǎo)入到具體類,而 PHP 則可以指定到一個命名空間內(nèi)的類、常量、方法等,并且支持命名空間別名。
<?php namespace foo; use My\Full\Classname as Another; // 下面的例子與 use My\Full\NSname as NSname 相同 use My\Full\NSname; // 導(dǎo)入一個全局類 use ArrayObject; // importing a function (PHP 5.6+) use function My\Full\functionName; // aliasing a function (PHP 5.6+) use function My\Full\functionName as func; // importing a constant (PHP 5.6+) use const My\Full\CONSTANT $obj = new namespace\Another; // 實例化 foo\Another 對象 $obj = new Another; // 實例化 My\Full\Classname 對象 NSname\subns\func(); // 調(diào)用函數(shù) My\Full\NSname\subns\func $a = new ArrayObject(array(1)); // 實例化 ArrayObject 對象 // 如果不使用 "use \ArrayObject" ,則實例化一個 foo\ArrayObject 對象 func(); // calls function My\Full\functionName echo CONSTANT; // echoes the value of My\Full\CONSTANT ?>
名稱解析規(guī)則
首先就是前面講過的三種名稱類型,名稱解析遵循以下規(guī)則:
對完全限定名稱的函數(shù),類和常量的調(diào)用在編譯時解析。例如 new \A\B 解析為類 A\B。
所有的非限定名稱和限定名稱(非完全限定名稱)根據(jù)當(dāng)前的導(dǎo)入規(guī)則在編譯時進(jìn)行轉(zhuǎn)換。例如,如果命名空間 A\B\C 被導(dǎo)入為 C,那么對 C\D\e() 的調(diào)用就會被轉(zhuǎn)換為 A\B\C\D\e()。
在命名空間內(nèi)部,所有的沒有根據(jù)導(dǎo)入規(guī)則轉(zhuǎn)換的限定名稱均會在其前面加上當(dāng)前的命名空間名稱。例如,在命名空間 A\B 內(nèi)部調(diào)用 C\D\e(),則 C\D\e() 會被轉(zhuǎn)換為 A\B\C\D\e() 。
非限定類名根據(jù)當(dāng)前的導(dǎo)入規(guī)則在編譯時轉(zhuǎn)換(用全名代替短的導(dǎo)入名稱)。例如,如果命名空間 A\B\C 導(dǎo)入為C,則 new C() 被轉(zhuǎn)換為 new A\B\C() 。
在命名空間內(nèi)部(例如A\B),對非限定名稱的函數(shù)調(diào)用是在運行時解析的。例如對函數(shù) foo() 的調(diào)用是這樣解析的:
在當(dāng)前命名空間中查找名為 A\B\foo() 的函數(shù)
嘗試查找并調(diào)用 全局(global) 空間中的函數(shù) foo()。
在命名空間(例如A\B)內(nèi)部對非限定名稱或限定名稱類(非完全限定名稱)的調(diào)用是在運行時解析的。下面是調(diào)用 new C() 及 new D\E() 的解析過程:
new C()的解析:
在當(dāng)前命名空間中查找A\B\C類。
嘗試自動裝載類A\B\C。
new D\E()的解析:
在類名稱前面加上當(dāng)前命名空間名稱變成:A\B\D\E,然后查找該類。
嘗試自動裝載類 A\B\D\E。
為了引用全局命名空間中的全局類,必須使用完全限定名稱 new \C()。
從上面的規(guī)則來看,實際上 PHP 的導(dǎo)入規(guī)則和 Java 有點類似,但是卻有不一樣,主要是因為 Java 是完全面向?qū)ο蟮模?PHP 本質(zhì)上還只是一種基于對象的語言。
自動加載類
在早期 PHP 開發(fā)中,開發(fā)者最煩的就是一堆 include 函數(shù)包含了一大堆文件,而且早期時候 PHP 面向?qū)ο蟮母拍畲_實太差了,因為 PHP 作為一種腳本語言,不存在程序入口,所以腳本順序化執(zhí)行的誘惑力實在是很大,即使面向?qū)ο箝_發(fā),但是缺少極佳的模塊劃分導(dǎo)入機(jī)制,代碼可以說很難有美感,最大的代表就是 Wordpress。如果有朋友看過這個典型項目,可以覺得非常痛苦,因為各種初始化、業(yè)務(wù)流程都分散在各個不同的文件中,使用 include 函數(shù)進(jìn)行銜接,然后每次頁面渲染都是同樣的要走一趟流程。當(dāng)然,這是 Wordpress 的歷史包袱,而在支持老版本 PHP 的情況下 Wordpress 代碼已經(jīng)寫得足夠優(yōu)化了。
在 PHP5 中就不需要這么麻煩了,因為可以定義一個 __autoload() 函數(shù),當(dāng)調(diào)用一個未定義的類的時候就會啟動此函數(shù),從而在拋出錯誤之前做最后的補(bǔ)救,不過這個函數(shù)的本意已經(jīng)被完全曲解使用了,現(xiàn)在都用來做自動加載。
注意,這個函數(shù)實際上已經(jīng)不被推薦使用了,相反,現(xiàn)在應(yīng)當(dāng)使用 spl_autoload_register() 來注冊類的自動加載函數(shù)。
bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
autoload_function 是需要注冊的自動裝載函數(shù),如果此項為空,則會注冊 spl_autoload 函數(shù),
throw 此參數(shù)設(shè)置了 autoload_function 無法成功注冊時, spl_autoload_register() 是否拋出異常。
prepend 如果是 true, spl_autoload_register() 會添加函數(shù)到隊列之首,而不是隊列尾部。
上面提到了 spl_autoload 函數(shù),實際上注冊函數(shù)的規(guī)范就應(yīng)當(dāng)遵循此函數(shù),函數(shù)聲明如下:
void spl_autoload ( string $class_name [, string $file_extensions ] )
由于這個函數(shù)默認(rèn)實現(xiàn)是通過 C 語言,所以這里給出一個 PHP 語言的實現(xiàn)規(guī)范。
<?php // Your custom class dir define('CLASS_DIR', 'class/') // Add your class dir to include path set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR); // You can use this trick to make autoloader look for commonly used "My.class.php" type filenames spl_autoload_extensions('.class.php'); // Use default autoload implementation spl_autoload_register(); ?>
大致上就和這個是類似的。實際上命名空間和自動加載類的結(jié)合就基本是通過路徑形式
function __autoload(){ $dir = './libralies'; set_include_path(get_include_path(). PATH_SEPARATOR. $dir); $class = str_replace('\\', '/', $class) . '.php'; require_once($class); }
將命名空間路徑替換為實際路徑。
關(guān)于“PHP中命名空間和自動加載類的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。