php易錯筆記-類與對象,命名空間

類與對象

基本概念

new:若是在 new 以後跟着的是一個 包含有類名的字符串,則該類的一個實例被建立。 若是該類屬於一個名字空間,則必須使用其完整名稱

Example #3 建立一個實例php

<?php
$instance = new stdClass();

// 也能夠這樣作:
$className = 'stdClass';
$instance = new $className(); // Foo()
?>
在類定義內部,能夠用 new selfnew parent 建立新對象。

PHP 5.3.0 引進了兩個新方法來建立一個對象的實例:segmentfault

<?php
class Test
{
    static public function getNew()//單例模式可使用此方法
    {
        return new static;
    }
}    
class Child extends Test {}

$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);//bool(true)

$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);//bool(true)

$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);//bool(true)
?>

自 PHP 5.5 起,關鍵詞 class 也可用於類名的解析:ClassName::classsession

當把一個對象已經建立的實例賦給一個新變量時,新變量會訪問同一個實例,就和用該對象賦值同樣。此行爲和給函數傳遞入實例時同樣。能夠用克隆給一個已建立的對象創建一個新實例。ide

<?php 
Class Object{
   public $foo="bar";
};

$objectVar = new Object();
$reference =& $objectVar;
$assignment = $objectVar;
$cloneObj = clone $objectVar;


$objectVar->foo = "qux";
var_dump( $objectVar );
var_dump( $reference );
var_dump( $assignment );
var_dump( $cloneObj );

echo '--------------------', PHP_EOL;

$objectVar = null;
var_dump($objectVar);
var_dump($reference);
var_dump($assignment);
var_dump($cloneObj);

/*
Result:

object(Object)#1 (1) {
  ["foo"]=>
  string(3) "qux"
}
object(Object)#1 (1) {
  ["foo"]=>
  string(3) "qux"
}
object(Object)#1 (1) {
  ["foo"]=>
  string(3) "qux"
}
object(Object)#2 (1) {
  ["foo"]=>
  string(3) "bar"
}
--------------------
NULL
NULL
object(Object)#1 (1) {
  ["foo"]=>
  string(3) "qux"
}
object(Object)#2 (1) {
  ["foo"]=>
  string(3) "bar"
}
*/

類的自動加載

spl_autoload_register() 函數能夠註冊任意數量的自動加載器,當使用還沒有被定義的類(class)和接口(interface)時自動去加載。經過註冊自動加載器,腳本引擎在 PHP 出錯失敗前有了最後一個機會加載所需的類。函數

儘管 __autoload() 函數也能自動加載類和接口,但更建議使用 spl_autoload_register() 函數。 spl_autoload_register() 提供了一種更加靈活的方式來實現類的自動加載(同一個應用中,能夠支持任意數量的加載器,好比第三方庫中的)。所以,再也不建議使用 __autoload() 函數,在之後的版本中它可能被棄用。

Example #1 自動加載示例ui

本例嘗試分別從 MyClass1.php 和 MyClass2.php 文件中加載 MyClass1 和 MyClass2 類。this

<?php
spl_autoload_register(function ($class_name) {
    require_once $class_name . '.php';
});

$obj  = new MyClass1();
$obj2 = new MyClass2();
?>

構造函數和析構函數

Note: 若是子類中定義了構造函數則不會隱式調用其父類的構造函數。要執行父類的構造函數,須要在子類的構造函數中調用 parent::__construct()。若是子類沒有定義構造函數則會如同一個普通的類方法同樣從父類繼承(假如沒有被定義爲 private 的話)。

和構造函數同樣,父類的析構函數不會被引擎暗中調用。要執行父類的析構函數,必須在子類的析構函數體中顯式調用 parent::__destruct()。此外也和構造函數同樣,子類若是本身沒有定義析構函數則會繼承父類的。spa

析構函數即便在使用 exit() 終止腳本運行時也會被調用。在析構函數中調用 exit() 將會停止其他關閉操做的運行。.net

訪問控制(可見性)

類屬性必須定義爲公有受保護私有之一。若是用 var 定義,則被視爲公有。code

類中的方法能夠被定義爲公有私有受保護。若是沒有設置這些關鍵字,則該方法默認爲公有

同一個類的對象即便不是同一個實例也能夠互相訪問對方的私有與受保護成員。這是因爲在這些對象的內部具體實現的細節都是已知的。

Example #3 訪問同一個對象類型的私有成員

<?php
class Test
{
    private $foo;

    public function __construct($foo)
    {
        $this->foo = $foo;
    }

    private function bar()
    {
        echo 'Accessed the private method.';
    }

    public function baz(Test $other)
    {
        // We can change the private property:
        $other->foo = 'hello';
        var_dump($other->foo);

        // We can also call the private method:
        $other->bar();
    }
}

$test = new Test('test');

$test->baz(new Test('other'));

//string(5) "hello"
//Accessed the private method.
?>

繼承和訪問控制:

<?php 
abstract class base { 
    public function inherited() { 
        $this->overridden(); 
    } 
    private function overridden() { 
        echo 'base'; 
    } 
} 

class child extends base { 
    private function overridden() { 
        echo 'child'; 
    } 
} 

$test = new child(); 
$test->inherited(); 
?> 

Output will be "base". 

If you want the inherited methods to use overridden functionality in extended classes but public sounds too loose, use protected. That's what it is for:) 

A sample that works as intended: 

<?php 
abstract class base { 
    public function inherited() { 
        $this->overridden(); 
    } 
    protected function overridden() { 
        echo 'base'; 
    } 
} 

class child extends base { 
    protected function overridden() { 
        echo 'child'; 
    } 
} 

$test = new child(); 
$test->inherited(); 
?> 
Output will be "child".

範圍解析操做符 (::)

範圍解析操做符(也可稱做 Paamayim Nekudotayim)或者更簡單地說是一對冒號,能夠用於訪問靜態成員類常量,還能夠用於覆蓋類中的屬性和方法

訪問靜態變量,靜態方法,常量:

<?php    
class A {    
    public static $B = '1'; # Static class variable.    
    const B = '2'; # Class constant.        
    public static function B() { # Static class function.
        return '3';
    }
    
}

echo A::$B . A::B . A::B(); # Outputs: 123

Example #3 調用父類的方法

<?php
class MyClass
{
    protected function myFunc() {
        echo "MyClass::myFunc()\n";
    }
}

class OtherClass extends MyClass
{
    // 覆蓋了父類的定義
    public function myFunc()
    {
        // 但仍是能夠調用父類中被覆蓋的方法
        parent::myFunc();
        echo "OtherClass::myFunc()\n";
    }
}

$class = new OtherClass();
$class->myFunc();
?>

Static(靜態)關鍵字

用 static 關鍵字來定義 靜態方法屬性。static 也可用於 定義靜態變量以及 後期靜態綁定

聲明類屬性或方法爲靜態,就能夠不實例化類而直接訪問。靜態屬性不能經過一個類已實例化的對象來訪問(但靜態方法能夠)

抽象類

繼承一個抽象類的時候:
1.子類必須定義父類中的全部抽象方法
2.這些方法的訪問控制必須和父類中同樣(或者更爲寬鬆)。例如某個抽象方法被聲明爲受保護的,那麼子類中實現的方法就應該聲明爲受保護的或者公有的,而不能定義爲私有的;
3.方法的調用方式必須匹配,即類型和所需參數數量必須一致。例如,子類定義了一個可選參數,而父類抽象方法的聲明裏沒有,則二者的聲明並沒有衝突。 這也適用於 PHP 5.4 起的構造函數。在 PHP 5.4 以前的構造函數聲明能夠不同的;

對象接口

接口中定義的全部方法都必須是公有,這是接口的特性。
實現類必須實現接口中定義的全部方法
能夠實現多個接口,用逗號來分隔多個接口的名稱。
實現多個接口時,接口中的方法不能有重名
類要實現接口,必須使用和接口中所定義的方法徹底一致的方式
接口中也能夠定義常量。接口常量和類常量的使用徹底相同,可是不能被子類或子接口所覆蓋

Trait

自 PHP 5.4.0 起,PHP 實現了一種代碼複用的方法,稱爲 trait

Trait 是爲相似 PHP 的單繼承語言而準備的一種代碼複用機制。Trait 爲了減小單繼承語言的限制,使開發人員可以自由地在不一樣層次結構內獨立的類中複用 method。Trait 和 Class 組合的語義定義了一種減小複雜性的方式,避免傳統多繼承和 Mixin 類相關典型問題。

Trait 和 Class 類似,但僅僅旨在用細粒度和一致的方式來組合功能。 沒法經過 trait 自身來實例化。它爲傳統繼承增長了水平特性的組合;也就是說,應用的幾個 Class 之間不須要繼承。

優先級:從基類繼承的成員會被 trait 插入的成員所覆蓋。優先順序是來自當前類的成員覆蓋了 trait 的方法,而 trait 則覆蓋了被繼承的方法。

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();//Hello World!
?>

多個 trait:經過逗號分隔,在 use 聲明列出多個 trait,能夠都插入到一個類中。

衝突的解決:爲了解決多個 trait 在同一個類中的命名衝突,須要使用 insteadof 操做符來明確指定使用衝突方法中的哪個

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>

修改方法的訪問控制使用 as 語法還能夠用來調整方法的訪問控制。

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// 修改 sayHello 的訪問控制
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 給方法一個改變了訪問控制的別名
// 原版 sayHello 的訪問控制則沒有發生變化
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

trait 來組成 trait在 trait 定義時經過使用一個或多個 trait,可以組合其它 trait 中的部分或所有成員。

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

Trait 的抽象成員爲了對使用的類施增強制要求,trait 支持抽象方法的使用。

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}
?>

Trait 的靜態成員Traits 能夠被靜態成員靜態方法定義。

<?php
class TestClass {
    public static $_bar;
}
class Foo1 extends TestClass { }
class Foo2 extends TestClass { }
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: World World
?>

Example using trait:
<?php
trait TestTrait {
    public static $_bar;
}
class Foo1 {
    use TestTrait;
}
class Foo2 {
    use TestTrait;
}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: Hello World
?>

屬性Trait 一樣能夠定義屬性。
Trait 定義了一個屬性後,類就不能定義一樣名稱的屬性,不然會產生 fatal error。 有種狀況例外:屬性是兼容的(一樣的訪問可見度、初始默認值)。 在 PHP 7.0 以前,屬性是兼容的,則會有 E_STRICT 的提醒。

Example #12 解決衝突

<?php
trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // PHP 7.0.0 後沒問題,以前版本是 E_STRICT 提醒
    public $different = true; // 致命錯誤
}
?>

重載(術語濫用)

PHP所提供的"重載"(overloading)是指動態地"建立"類屬性和方法。咱們是經過魔術方法(magic methods)來實現的。

當調用當前環境下未定義或不可見的類屬性或方法時,重載方法會被調用。本節後面將使用"不可訪問屬性(inaccessible properties)"和"不可訪問方法(inaccessible methods)"來稱呼這些未定義或不可見的類屬性或方法。

This is a misuse of the term overloading. This article should call this technique "interpreter hooks".

參加魔術方法php超全局變量,魔術常量,魔術方法

Note:
由於 PHP 處理賦值運算的方式, __set() 的返回值將被忽略。相似的, 在下面這樣的鏈式賦值中, __get() 不會被調用: $a = $obj->b = 8;

Note:
在除 isset() 外的其它語言結構中沒法使用重載的屬性,這意味着當對一個重載的屬性使用 empty() 時,重載魔術方法將不會被調用。
爲避開此限制,必須將重載屬性賦值到本地變量再使用 empty()

遍歷對象

1.用 foreach 語句。默認狀況下,全部可見屬性都將被用於遍歷。

<?php
foreach(new class(10) {
    public $public = [];
    protected $protected = [3, 4, 5];
    private $private = [6, 7, 8];
    function __construct($value = 1)
    {
        for ($i=0; $i < $value; $i++) { 
            $this->public[] = $i;
        }
    }
    function __destruct()
    {
        foreach ($this as $key => list($a, $b, $c)) {
            print "$key => [$a, $b, $c]\n";
        }
    }
} as $key => list($a, $b, $c)) {
    print "$key => [$a, $b, $c]\n";
}
echo "\n";

//Result:
/*
public => [0, 1, 2]
public => [0, 1, 2]
protected => [3, 4, 5]
private => [6, 7, 8]
 */

2.實現 Iterator 接口。可讓對象自行決定如何遍歷以及每次遍歷時那些值可用。

<?php
class MyIterator implements Iterator
{
    private $var = array();

    public function __construct($array)
    {
        if (is_array($array)) {
            $this->var = $array;
        }
    }

    public function rewind() {
        echo "rewinding\n";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var\n";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var\n";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var\n";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}\n";
        return $var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
    print "$a: $b\n";
}
?>

能夠用 IteratorAggregate 接口以替代實現全部的 Iterator 方法。IteratorAggregate 只須要實現一個方法 IteratorAggregate::getIterator()其應返回一個實現了 Iterator 的類的實例

Example #3 經過實現 IteratorAggregate 來遍歷對象

<?php
class MyCollection implements IteratorAggregate
{
    private $items = array();
    private $count = 0;

    // Required definition of interface IteratorAggregate
    public function getIterator() {
        return new MyIterator($this->items);
    }

    public function add($value) {
        $this->items[$this->count++] = $value;
    }
}

$coll = new MyCollection();
$coll->add('value 1');
$coll->add('value 2');
$coll->add('value 3');

foreach ($coll as $key => $val) {
    echo "key/value: [$key -> $val]\n\n";
}
?>
PHP 5.5 及之後版本的用戶也可參考 生成器,其提供了另外一方法來定義 Iterators。

魔術方法

參見php超全局變量,魔術常量,魔術方法

final

若是父類中的方法被聲明爲 final,則子類沒法覆蓋該方法。若是一個類被聲明爲 final,則不能被繼承

屬性不能被定義爲 final,只有類和方法才能被定義爲 final。可使用 const 定義爲常量來代替。

對象複製(clone)

對象複製能夠經過 clone 關鍵字來完成(若是可能,這將調用對象的 __clone() 方法)。對象中的 __clone() 方法不能被直接調用。

當對象被複制後,PHP 5 會對對象的全部屬性執行一個 淺複製shallow copy)。 全部的引用屬性 仍然會是一個指向原來的變量的引用

對象比較

==:若是兩個對象的屬性和屬性值都相等,並且兩個對象是同一個類的實例,那麼這兩個對象變量相等。
===:這兩個對象變量必定要指向某個類的同一個實例(即同一個對象,但不須要引用賦值)。

<?php
function compareObjects(&$o1, &$o2)
{
    echo 'o1 == o2 : ' , var_export($o1 == $o2) . "\r\n";
    echo 'o1 === o2 : ' , var_export($o1 === $o2) . "\r\n";
}

class Flag
{
    public $flag;
    function __construct($flag = true) { $this->flag = $flag; }
}

class OtherFlag
{
    public $flag;
    function __construct($flag = true) { $this->flag = $flag; }
}

$o = new Flag;
$p = new Flag;
$q = $o;
$r = new OtherFlag;
$s = new Flag(false);

echo "Two instances of the same class\r\n";
compareObjects($o, $p);

echo "\r\nTwo references to the same instance\r\n";
compareObjects($o, $q);

echo "\r\nInstances of two different classes\r\n";
compareObjects($o, $r);

echo "Two instances of the same class\r\n";
compareObjects($o, $s);

//Result:
/*
Two instances of the same class
o1 == o2 : true
o1 === o2 : false

Two references to the same instance
o1 == o2 : true
o1 === o2 : true

Instances of two different classes
o1 == o2 : false
o1 === o2 : false

Two instances of the same class
o1 == o2 : false
o1 === o2 : false
 */
?>

後期靜態綁定

後期靜態綁定static:: 再也不被解析爲定義當前方法所在的類,而是在實際運行時計算的。也能夠稱之爲「靜態綁定」,由於它能夠用於(但不限於)靜態方法的調用。

Example #2 static:: 簡單用法

<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
        static::who(); // 後期靜態綁定從這裏開始
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();
//AB
?>

Finally we can implement some ActiveRecord methods:

<?php 

class Model 
{ 
    public static function find() 
    { 
        echo static::$name; 
    } 
} 

class Product extends Model 
{ 
    protected static $name = 'Product'; 
} 

Product::find(); 

?> 

Output: 'Product'

對象和引用

Notes on reference:
A reference is not a pointer. However, an object handle IS a pointer. Example:
<?php
class Foo {
  private static $used;
  private $id;
  public function __construct() {
    $id = $used++;
  }
  public function __clone() {
    $id = $used++;
  }
}

$a = new Foo; // $a is a pointer pointing to Foo object 0
$b = $a; // $b is a pointer pointing to Foo object 0, however, $b is a copy of $a
$c = &$a; // $c and $a are now references of a pointer pointing to Foo object 0
$a = new Foo; // $a and $c are now references of a pointer pointing to Foo object 1, $b is still a pointer pointing to Foo object 0
unset($a); // A reference with reference count 1 is automatically converted back to a value. Now $c is a pointer to Foo object 1
$a = &$b; // $a and $b are now references of a pointer pointing to Foo object 0
$a = NULL; // $a and $b now become a reference to NULL. Foo object 0 can be garbage collected now
unset($b); // $b no longer exists and $a is now NULL
$a = clone $c; // $a is now a pointer to Foo object 2, $c remains a pointer to Foo object 1
unset($c); // Foo object 1 can be garbage collected now.
$c = $a; // $c and $a are pointers pointing to Foo object 2
unset($a); // Foo object 2 is still pointed by $c
$a = &$c; // Foo object 2 has 1 pointers pointing to it only, that pointer has 2 references: $a and $c;
const ABC = TRUE;
if(ABC) {
  $a = NULL; // Foo object 2 can be garbage collected now because $a and $c are now a reference to the same NULL value
} else {
  unset($a); // Foo object 2 is still pointed to $c
}

命名空間

定義命名空間

雖然任意合法的PHP代碼均可以包含在命名空間中,但只有如下類型的代碼受命名空間的影響,它們是:(包括抽象類traits)、接口函數常量

命名空間經過關鍵字 namespace 來聲明。若是一個文件中包含命名空間,它必須在其它全部代碼以前聲明命名空間,除了一個之外:declare關鍵字

define() will define constants exactly as specified:

Regarding constants defined with define() inside namespaces...

define() will define constants exactly as specified.  So, if you want to define a constant in a namespace, you will need to specify the namespace in your call to define(), even if you're calling define() from within a namespace.  The following examples will make it clear.

The following code will define the constant "MESSAGE" in the global namespace (i.e. "\MESSAGE").

<?php
namespace test;
define('MESSAGE', 'Hello world!');
?>

The following code will define two constants in the "test" namespace.

<?php
namespace test;
define('test\HELLO', 'Hello world!');
define(__NAMESPACE__ . '\GOODBYE', 'Goodbye cruel world!');

echo \test\HELLO, PHP_EOL;//Hello world!
echo namespace\HELLO, PHP_EOL;//Hello world!
echo constant('\\'. __NAMESPACE__ . '\HELLO') , PHP_EOL;//Hello world!
echo constant(__NAMESPACE__ . '\HELLO') , PHP_EOL;//Hello world!
echo HELLO, PHP_EOL;//Hello world!
echo __NAMESPACE__, PHP_EOL;//test
?>

在同一個文件中定義多個命名空間(不建議)

也能夠在同一個文件中定義多個命名空間。在同一個文件中定義多個命名空間有兩種語法形式:簡單組合語法大括號語法

Example #4 定義多個命名空間和不包含在命名空間中的代碼

<?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();
}
?>

使用命名空間:基礎

(PHP 5 >= 5.3.0, PHP 7)
在討論如何使用命名空間以前,必須瞭解 PHP 是如何知道要使用哪個命名空間中的元素的。能夠將 PHP 命名空間與文件系統做一個簡單的類比。在文件系統中訪問一個文件有三種方式:

  1. 相對文件名形式foo.txt。它會被解析爲 currentdirectory/foo.txt,其中 currentdirectory 表示當前目錄。所以若是當前目錄是 /home/foo,則該文件名被解析爲/home/foo/foo.txt
  2. 相對路徑名形式subdirectory/foo.txt。它會被解析爲 currentdirectory/subdirectory/foo.txt
  3. 絕對路徑名形式/main/foo.txt。它會被解析爲/main/foo.txt

PHP 命名空間中的元素使用一樣的原理。例如,類名能夠經過三種方式引用:

  1. 非限定名稱,或不包含前綴的類名稱,例如 $a=new foo(); 或 foo::staticmethod();。若是當前命名空間是 currentnamespacefoo 將被解析爲 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。resolves to class Foo\Bar\foo, method 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

Example #1 在命名空間內部訪問全局類、函數和常量

<?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
?>

命名空間和動態語言特徵

php.php

<?php
namespace testt;

class cls {}
function func($value='') {}
const VAL = __NAMESPACE__;
 ?>

test.php

<?php
namespace test {
    include 'php.php';
    //new testt\cls;    //Fatal error: Uncaught Error: Class 'test\testt\cls' not found in D:\php\test\test.php:4
    //new cls;  //Fatal error: Uncaught Error: Class 'test\cls' not found in D:\php\test\test.php:5
    new \testt\cls;
    echo \testt\VAL, PHP_EOL;
    \testt\func();
}

namespace tests {
    use testt\cls;
    use testt\VAL;//未報錯,也沒效果
    use testt\func;//未報錯,也沒效果
    //new testt\cls;    //Fatal error: Uncaught Error: Class 'test\testt\cls' not found in D:\php\test\test.php:4
    new cls;
    new \testt\cls;
    echo \testt\VAL, PHP_EOL;
    //echo VAL, PHP_EOL;    //Notice:  Use of undefined constant VAL - assumed 'VAL' in D:\php\test\test.php on line 19
    \testt\func();
    //func();   //Fatal error:  Uncaught Error: Call to undefined function test\func() in D:\php\test\test.php:21
}

namespace {    
    //new cls;  //Fatal error: Uncaught Error: Class 'cls' not found in D:\php\test\test.php:12
    new \testt\cls;
    new testt\cls;
    echo testt\VAL, PHP_EOL;
    testt\func();
}

namespace {
    use testt\cls;
    use testt\VAL;//未報錯,也沒效果
    use testt\func;//未報錯,也沒效果
    new cls;
    new \testt\cls;
    new testt\cls;
    echo testt\VAL, PHP_EOL;
    testt\func();
    //func();   //Fatal error:  Uncaught Error: Call to undefined function func() in D:\php\test\test.php:41
}

//Result
/*
testt
testt
testt
testt
 */

namespace關鍵字和__NAMESPACE__常量

常量__NAMESPACE__的值是包含當前命名空間名稱的字符串。在全局的,不包括在任何命名空間中的代碼,它包含一個空的字符串。
關鍵字 namespace 可用來顯式訪問當前命名空間或子命名空間中的元素。它等價於類中的 self 操做符。

<?php
namespace test;

class cls {}
function func($value='') {}
const VAL = __NAMESPACE__;

namespace\func();
echo namespace\VAL;//test
echo constant(__NAMESPACE__ . '\VAL');//test
new namespace\cls;

使用命名空間:別名/導入

別名是經過操做符 use 來實現的:全部支持命名空間的PHP版本支持三種別名或導入方式:爲類名稱使用別名爲接口使用別名爲命名空間名稱使用別名

PHP 5.6開始容許導入 函數常量或者爲它們設置別名。

Example #1 使用use操做符導入/使用別名

<?php
namespace foo;
use My\Full\Classname as Another;

//爲命名空間設置別名
// 下面的例子與 use My\Full\NSname as NSname 相同
use My\Full\NSname;

// 導入一個全局類
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(); // 調用函數 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
?>
對命名空間中的名稱(包含命名空間分隔符的徹底限定名稱如 Foo\Bar以及相對的不包含命名空間分隔符的全局名稱如 FooBar)來講, 前導的反斜槓是沒必要要的也不推薦的,由於導入的名稱必須是徹底限定的,不會根據當前的命名空間做相對解析。

導入操做隻影響非限定名稱和限定名稱。徹底限定名稱因爲是肯定的,故不受導入的影響。

全局空間

若是沒有定義任何命名空間,全部的類與函數的定義都是在全局空間,與 PHP 引入命名空間概念前同樣。在名稱前加上前綴 \ 表示該名稱是全局空間中的名稱,即便該名稱位於其它的命名空間中時也是如此。

使用命名空間:後備全局函數/常量

在一個命名空間中,當 PHP 遇到一個非限定的類、函數或常量名稱時,它使用不一樣的優先策略來解析該名稱。類名稱老是解析到當前命名空間中的名稱。所以在訪問系統內部或不包含在命名空間中的類名稱時,必須使用徹底限定名稱,例如:

<?php
namespace test;

class Exception extends \Exception {
    protected $message = 'My Exception';
}

try {
    throw new Exception;//若是未定義Exception類,則會報致命錯誤。
} catch (\Exception $e) {
    echo $e->getMessage();
} finally {
    echo PHP_EOL, "finally do";
}

//Result
/*
My Exception
finally do
 */

名稱解析規則

在說明名稱解析規則以前,咱們先看一些重要的定義:

命名空間名稱定義

  • 非限定名稱Unqualified name

名稱中不包含命名空間分隔符的標識符,例如 Foo

  • 限定名稱Qualified name

名稱中含有命名空間分隔符的標識符,例如 Foo\Bar

  • 徹底限定名稱Fully qualified name

名稱中包含命名空間分隔符,並以命名空間分隔符開始的標識符,例如 \Foo\Barnamespace\Foo 也是一個徹底限定名稱。

名稱解析遵循下列規則:

  1. 對徹底限定名稱的函數,類和常量的調用在編譯時解析。例如 new \A\B 解析爲類 A\B
  2. 全部的非限定名稱和限定名稱(非徹底限定名稱)根據當前的導入規則在編譯時進行轉換。例如,若是命名空間 A\B\C 被導入爲 C,那麼對 C\D\e() 的調用就會被轉換爲 A\B\C\D\e()
  3. 在命名空間內部,全部的沒有根據導入規則轉換的限定名稱均會在其前面加上當前的命名空間名稱。例如,在命名空間 A\B 內部調用 C\D\e(),則 C\D\e() 會被轉換爲 A\B\C\D\e()
  4. 非限定類名根據當前的導入規則在編譯時轉換(用全名代替短的導入名稱)。例如,若是命名空間 A\B\C 導入爲C,則 new C() 被轉換爲 new A\B\C()
  5. 在命名空間內部(例如A\B),對非限定名稱的函數調用是在運行時解析的。例如對函數 foo()
    的調用是這樣解析的:

    1. 在當前命名空間中查找名爲 A\B\foo() 的函數
    2. 嘗試查找並調用 全局(global) 空間中的函數 foo()
  6. 在命名空間(例如A\B)內部對非限定名稱或限定名稱類(非徹底限定名稱)的調用是在運行時解析的。下面是調用 new C()new D\E() 的解析過程: new C()的解析:

    1. 在當前命名空間中查找A\B\C類。
    2. 嘗試自動裝載類A\B\C

new D\E()的解析:

  1. 在類名稱前面加上當前命名空間名稱變成:A\B\D\E,而後查找該類。
  2. 嘗試自動裝載類 A\B\D\E

爲了引用全局命名空間中的全局類,必須使用徹底限定名稱 new \C()

相關文章
相關標籤/搜索