资讯专栏INFORMATION COLUMN

php易错笔记-类与对象,命名空间

MartinHan / 1456人阅读

摘要:类与对象基本概念如果在之后跟着的是一个包含有类名的字符串,则该类的一个实例被创建。如果该类属于一个名字空间,则必须使用其完整名称。如果一个类被声明为,则不能被继承。命名空间通过关键字来声明。

类与对象 基本概念
new:如果在 new 之后跟着的是一个包含有类名的字符串,则该类的一个实例被创建。如果该类属于一个名字空间,则必须使用其完整名称

Example #3 创建一个实例


在类定义内部,可以用 new selfnew parent 创建新对象。

PHP 5.3.0 引进了两个新方法来创建一个对象的实例:


自 PHP 5.5 起,关键词 class 也可用于类名的解析:ClassName::class

当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用克隆给一个已创建的对象建立一个新实例。

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 自动加载示例

本例尝试分别从 MyClass1.php 和 MyClass2.php 文件中加载 MyClass1 和 MyClass2 类。


构造函数和析构函数
Note: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。

和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。

析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。

访问控制(可见性)

类属性必须定义为公有受保护私有之一。如果用 var 定义,则被视为公有。

类中的方法可以被定义为公有私有受保护。如果没有设置这些关键字,则该方法默认为公有

同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。

Example #3 访问同一个对象类型的私有成员

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

继承和访问控制:

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: 

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)或者更简单地说是一对冒号,可以用于访问静态成员类常量,还可以用于覆盖类中的属性和方法

访问静态变量,静态方法,常量:


Example #3 调用父类的方法

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 则覆盖了被继承的方法。

sayHello();//Hello World!
?>

多个 trait:通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。

冲突的解决:为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个


修改方法的访问控制使用 as 语法还可以用来调整方法的访问控制。


trait 来组成 trait在 trait 定义时通过使用一个或多个 trait,能够组合其它 trait 中的部分或全部成员。

sayHello();
$o->sayWorld();
?>

Trait 的抽象成员为了对使用的类施加强制要求,trait 支持抽象方法的使用。

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 可以被静态成员静态方法定义。



Example using trait:

属性Trait 同样可以定义属性。
Trait 定义了一个属性后,类就不能定义同样名称的属性,否则会产生 fatal error。 有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。

Example #12 解决冲突


重载(术语滥用)

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 语句。默认情况下,所有可见属性都将被用于遍历。

public[] = $i;
        }
    }
    function __destruct()
    {
        foreach ($this as $key => list($a, $b, $c)) {
            print "$key => [$a, $b, $c]
";
        }
    }
} as $key => list($a, $b, $c)) {
    print "$key => [$a, $b, $c]
";
}
echo "
";

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

2.实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。

var = $array;
        }
    }

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

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

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

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

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

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

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

可以用 IteratorAggregate 接口以替代实现所有的 Iterator 方法。IteratorAggregate 只需要实现一个方法 IteratorAggregate::getIterator()其应返回一个实现了 Iterator 的类的实例

Example #3 通过实现 IteratorAggregate 来遍历对象

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]

";
}
?>
PHP 5.5 及以后版本的用户也可参考生成器,其提供了另一方法来定义 Iterators。
魔术方法

参见php超全局变量,魔术常量,魔术方法。

final

如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承

属性不能被定义为 final,只有类和方法才能被定义为 final。可以使用 const 定义为常量来代替。
对象复制(clone)

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。

当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用
对象比较

==:如果两个对象的属性和属性值都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
===:这两个对象变量一定要指向某个类的同一个实例(即同一个对象,但不需要引用赋值)。

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
";
compareObjects($o, $p);

echo "
Two references to the same instance
";
compareObjects($o, $q);

echo "
Instances of two different classes
";
compareObjects($o, $r);

echo "Two instances of the same class
";
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:: 简单用法


Finally we can implement some ActiveRecord methods:

 

Output: "Product"
对象和引用
Notes on reference:
A reference is not a pointer. However, an object handle IS a pointer. Example:

命名空间
定义命名空间

虽然任意合法的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").



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


在同一个文件中定义多个命名空间(不建议)

也可以在同一个文件中定义多个命名空间。在同一个文件中定义多个命名空间有两种语法形式:简单组合语法大括号语法

Example #4 定义多个命名空间和不包含在命名空间中的代码


使用命名空间:基础

(PHP 5 >= 5.3.0, PHP 7)
在讨论如何使用命名空间之前,必须了解 PHP 是如何知道要使用哪一个命名空间中的元素的。可以将 PHP 命名空间与文件系统作一个简单的类比。在文件系统中访问一个文件有三种方式:

相对文件名形式foo.txt。它会被解析为 currentdirectory/foo.txt,其中 currentdirectory 表示当前目录。因此如果当前目录是 /home/foo,则该文件名被解析为/home/foo/foo.txt

相对路径名形式subdirectory/foo.txt。它会被解析为 currentdirectory/subdirectory/foo.txt

绝对路径名形式/main/foo.txt。它会被解析为/main/foo.txt

PHP 命名空间中的元素使用同样的原理。例如,类名可以通过三种方式引用:

非限定名称,或不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespacefoo 将被解析为 currentnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。详情参见 使用命名空间:后备全局函数名称/常量名称。

限定名称,或包含前缀的名称,例如 $a = new subnamespacefoo(); 或 subnamespacefoo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespacesubnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespacefoo

完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new currentnamespacefoo(); 或 currentnamespacefoo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespacefoo

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

file1.php


file2.php


注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 strlen()ExceptionINI_ALL

Example #1 在命名空间内部访问全局类、函数和常量


命名空间和动态语言特征

php.php


test.php


namespace关键字和__NAMESPACE__常量

常量__NAMESPACE__的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。


使用命名空间:别名/导入

别名是通过操作符 use 来实现的:所有支持命名空间的PHP版本支持三种别名或导入方式:为类名称使用别名为接口使用别名为命名空间名称使用别名

PHP 5.6开始允许导入函数常量或者为它们设置别名。

Example #1 使用use操作符导入/使用别名


对命名空间中的名称(包含命名空间分隔符的完全限定名称如 FooBar以及相对的不包含命名空间分隔符的全局名称如 FooBar)来说,前导的反斜杠是不必要的也不推荐的,因为导入的名称必须是完全限定的,不会根据当前的命名空间作相对解析。

导入操作只影响非限定名称和限定名称。完全限定名称由于是确定的,故不受导入的影响。

全局空间

如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。

使用命名空间:后备全局函数/常量

在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称,例如:

getMessage();
} finally {
    echo PHP_EOL, "finally do";
}

//Result
/*
My Exception
finally do
 */
名称解析规则

在说明名称解析规则之前,我们先看一些重要的定义:

命名空间名称定义

非限定名称Unqualified name

名称中不包含命名空间分隔符的标识符,例如 Foo

限定名称Qualified name

名称中含有命名空间分隔符的标识符,例如 FooBar

完全限定名称Fully qualified name

名称中包含命名空间分隔符,并以命名空间分隔符开始的标识符,例如 FooBarnamespaceFoo 也是一个完全限定名称。

名称解析遵循下列规则:

对完全限定名称的函数,类和常量的调用在编译时解析。例如 new AB 解析为类 AB

所有的非限定名称和限定名称(非完全限定名称)根据当前的导入规则在编译时进行转换。例如,如果命名空间 ABC 被导入为 C,那么对 CDe() 的调用就会被转换为 ABCDe()

在命名空间内部,所有的没有根据导入规则转换的限定名称均会在其前面加上当前的命名空间名称。例如,在命名空间 AB 内部调用 CDe(),则 CDe() 会被转换为 ABCDe()

非限定类名根据当前的导入规则在编译时转换(用全名代替短的导入名称)。例如,如果命名空间 ABC 导入为C,则 new C() 被转换为 new ABC()

在命名空间内部(例如AB),对非限定名称的函数调用是在运行时解析的。例如对函数 foo()
的调用是这样解析的:

在当前命名空间中查找名为 ABfoo() 的函数

尝试查找并调用 全局(global) 空间中的函数 foo()

在命名空间(例如AB)内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的。下面是调用 new C()new DE() 的解析过程: new C()的解析:

在当前命名空间中查找ABC类。

尝试自动装载类ABC

new DE()的解析:

在类名称前面加上当前命名空间名称变成:ABDE,然后查找该类。

尝试自动装载类 ABDE

为了引用全局命名空间中的全局类,必须使用完全限定名称 new C()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28191.html

相关文章

  • php易错笔记-类型

    摘要:非法下标类型会产生一个级别错误。用负数下标写入字符串时会产生一个级别错误,用负数下标读取字符串时返回空字符串。浮点数也会被转换为整型,意味着其小数部分会被舍去。 Boolean 当转换为 boolean 时,以下值被认为是 FALSE:布尔值 FALSE 本身整型值 0(零)浮点型值 0.0(零)空字符串,以及字符串 0不包括任何元素的数组[]特殊类型 NULL(包括尚未赋值的变量)从...

    tinyq 评论0 收藏0
  • php.类与对象

    摘要:接口可以使用常量,叫接口常量,和类的常量使用方法相同类可以同时继承多个接口使用接口常量抽象类不能被实例化。继承抽象类,子类必须实现父类中所有的抽象方法。 访问控制 属性和方法的访问控制(可见标识):public 任何地方private 类自身protected 类自身,自子类及自父类 this this 可以理解为这个类的一个实例 self self 代表类本身 __construc...

    scq000 评论0 收藏0
  • php易错笔记-流程控制,函数

    摘要:的语法和其它流程控制结构相似部分允许设定代码段的行为。返回值在失败时返回并且发出警告。当一个函数是有条件被定义时,必须在调用函数之前定义。有条件的函数不能在此处调用函数,因为它还不存在,但可以调用函数。 流程控制 PHP 提供了一些流程控制的替代语法,包括 if,while,for,foreach 和 switch。替代语法的基本形式是把左花括号({)换成冒号(:),把右花括号(})分...

    mrcode 评论0 收藏0
  • PHP 手册阅读笔记 - 语言参考篇

    摘要:最近计划把手册,认真的先过一遍。语言参考类型新认知强制转换类型用。后期静态绑定从这里开始语言参考生成器新认知生成器汗水的核心是关键字。语言参考预定义变量超全局变量前一个错误信息原始数据以上 showImg(https://segmentfault.com/img/remote/1460000010147451); 最近计划把 PHP手册,认真的先过一遍。记录一些以前不知道,不明确的知识...

    Developer 评论0 收藏0
  • PHP面试常考内容之面向对象(3)

    摘要:面试专栏正式起更,每周一三五更新,提供最好最优质的面试内容。继上一篇面试常考内容之面向对象发表后,今天更新面向对象的最后一篇。面向对象的主要特征为封装继承多态。为了提高内聚性减少引起变化,单一原则是低耦合高内聚的面向原则上的引申。 PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。继上一篇PHP面试常考内容之面向对象(2)发表后,今天更新面向对象的最后一篇(3...

    xfee 评论0 收藏0

发表评论

0条评论

MartinHan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<