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()

相关文章
相关标签/搜索