[PHP中文手册][二.语言参考-类与对象]
标签(空格分隔): 未分类
基本概念
class
<?php
class SimpleClass
{
// property declaration
public $var = 'a default value';
// method declaration
public function displayVar() {
echo $this->var;
}
}
?>
this
当一个方法在类定义内部被调用时,有一个可用的伪变量$this
new
要创建一个类的实例,必须使用 new classname()
关键字,可以使用可变变量来声明。
$name='hello';
$newclass=new $hello();
对象赋值
- 默认是浅复制,指向的实例是同一个。
- 可以用克隆给一个已创建的对象建立一个新实例。
<?php
class SimpleClass{
public $test="111";
}
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->test = "assigned will have this value\n";
var_dump($instance);
var_dump($reference);
var_dump($assigned);
/*D:\wamp64\www\1.php:12:
object(SimpleClass)[1]
public 'test' => string 'assigned will have this value
' (length=30)
D:\wamp64\www\1.php:13:
object(SimpleClass)[1]
public 'test' => string 'assigned will have this value
' (length=30)
D:\wamp64\www\1.php:14:
object(SimpleClass)[1]
public 'test' => string 'assigned will have this value
' (length=30)
*/
$instance = null;
var_dump($instance);
var_dump($reference);
var_dump($assigned);
/*
D:\wamp64\www\1.php:18:null
D:\wamp64\www\1.php:19:null
D:\wamp64\www\1.php:20:
object(SimpleClass)[1]
public 'test' => string 'assigned will have this value
' (length=30)
*/
?>
PHP 5.3.0引入的两个新方法来创建实例。
<?php
class Test
{
static public function getNew()
{
return new static;
}
}
class Child extends Test
{}
$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);
?>
extends 继承
- 跟JAVA一样不支持多继承。
- 父类定义方法时使用了 final,则该方法不可被覆盖。
- 可以通过 parent::来访问被覆盖的方法或属性。
- 除了构造函数以外,其他覆盖的方法必须参数一致。
::class
自 PHP 5.5 起,关键词 class
也可用于类名的解析。使用 ClassName::class
你可以获取一个字符串,包含了类 ClassName
的完全限定名称。这对使用了 命名空间 的类尤其有用。
属性
属性声明是由关键字 public
,protected
或者 private
开头。
var
会被认定为public
->(对象运算符)
访问非静态变量,::
访问静态变量。nowdocs
可用于初始化变量,heredoc
不行
<?php
class SimpleClass
{
// 错误的属性声明
public $var1 = 'hello ' . 'world';
public $var2 = <<<EOD //Heredoc
hello world
EOD;
public $var3 = 1+2;
public $var4 = self::myStaticMethod();
public $var5 = $myVar;
// 正确的属性声明
public $var6 = myConstant;
public $var7 = array(true, false);
//在 PHP 5.3.0 及之后,下面的声明也正确
public $var8 = <<<'EOD'//Newdoc
hello world
EOD;
}
?>
自动加载类
在 PHP 5 中,可以定义一个 __autoload()
函数,它会在试图使用尚未被定义的类时自动调用。
spl_autoload_register()
提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用__autoload()
函数,在以后的版本中它可能被弃用。
构造函数和析构函数
void __construct ([ mixed $args [, $... ]] )
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用
parent::__construct()
。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为private
的话)。
还有一种旧式的类似C的构造函数。现在不被经常使用。
自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。
析构函数
void __destruct ( void )
- 父类的
析构函数
不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用parent::__destruct()
。
<?php
class MyDestructableClass {
function __construct() {
print "In constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "Destroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
?>
访问控制(可见性)
对属性或方法的访问控制,是通过在前面添加关键字 public
(公有),protected
(受保护)或 private
(私有)来实现的。
- 被定义为公有的类成员可以在任何地方被访问。
- 被定义为受保护的类成员则可以被其自身以及其子类和父类访问。
- 被定义为私有的类成员则只能被其定义所在的类访问。
其它对象的访问控制
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
范围解析操作符 (::)
范围解析操作符,可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。
Static静态关键字
- 声明类属性或方法为静态,就可以不实例化类而直接访问。
- 静态属性不能通过一个类已实例化的对象来访问(但静态方法可以 )
- 伪变量
$this
在静态方法中不可用。 - 静态属性不可以由对象通过
-> 操作符
来访问。
<?php
class Foo
{
public static $my_static = 'foo';
public function staticValue() {
return self::$my_static;
}
}
抽象类
PHP 5 支持抽象类和抽象方法。定义为抽象的类不能被实例化。
- 这些方法的访问控制必须和父类中一样(或者更为宽松)
- 子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突
<?php
abstract class AbstractClass
{
// 强制要求子类定义这些方法
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// 普通方法(非抽象方法)
public function printOut() {
print $this->getValue() . "\n";
}
}
对象接口
使用接口(interface
),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
- 接口中定义的所有方法都必须是公有,这是接口的特性。
实现(使用implements操作符)
- 类中必须实现接口中定义的所有方法,否则会报一个致命错误。
- 类可以实现多个接口,用逗号来分隔多个接口的名称。
- 实现多个接口时,接口中的方法不能有重名。
- 接口也可以继承,通过使用 extends 操作符。
<?php
// 声明一个'iTemplate'接口
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 实现接口
// 下面的写法是正确的
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
//继承多个接口
interface c extends a, b
{
public function baz();
}
Trait
自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait
。
trait
声明use
复用
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
优先级
- 从基类继承的成员会被
trait
插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
多个 trait
通过逗号分隔,在 use
声明列出多个 trait
,可以都插入到一个类中。
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
冲突的解决
- 使用
insteadof操作符
来明确指定使用冲突方法中的哪一个 as 操作符
可以将其中一个冲突的方法以另一个名称来引入。
<?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';
}
}
trait C{
public function smallTalk(){
echo 'c';
}
public function bigTalk(){
echo 'C';
}
}
class Aliased_Talker{
use A,B,C{
B::bigTalk as BTalk;
A::bigTalk insteadof B,C;
B::smallTalk insteadof A,C;
}
}
$a=new Aliased_Talker();
$a->bigTalk();
$a->smallTalk();
$a-> BTalk();
?>
从 trait 来组成 trait
正如class
能够使用 trait
一样,其它 trait
也能够使用 trait
。在 trait
定义时通过使用一个或多个 trait
,能够组合其它 trait
中的部分或全部成员。
Trait 的抽象成员
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
Trait 的静态成员
<?php
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
Trait的静态方法
<?php
trait StaticExample {
public static function doSomething() {
return 'Doing something';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
?>
属性
-Trait
同样可以定义属性。
-如果 trait
定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。
匿名类
PHP 7 开始支持匿名类。匿名类很有用,可以创建一次性的简单对象。
<?php
//PHP7 之前
class Logger
{
public function log($msg)
{
echo $msg;
}
}
$util->setLogger(new Logger());
//使用匿名类
$util->setLogger(new class{
public function log($msg)
{
echo $msg;
}
})
可以传递参数到匿名类的构造器
,也可以扩展(extend)
其他类、实现接口(implement interface)
,以及像其他普通的类一样使用 trait
:
<?php
class SomeClass {}
interface SomeInterface {}
trait SomeTrait {}
var_dump(new class(10) extends SomeClass implements SomeInterface {
private $num;
public function __construct($num)
{
$this->num = $num;
}
use SomeTrait;
});
- 匿名类被嵌套进普通
Class
后,不能访问这个外部类(Outer class)的private
(私有)、protected
(受保护)方法或者属性。
- 为了访问外部类(Outer class)
protected
属性或方法,匿名类可以extend
(扩展)此外部类。 - 为了使用外部类(Outer class)的
private
属性,必须通过构造器
传进来。
- 为了访问外部类(Outer class)
重载
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用
属性重载
public void __set ( string $name , mixed $value )
public mixed __get ( string $name )
public bool __isset ( string $name )
public void __unset ( string $name )
在给不可访问属性赋值时,__set()
会被调用。
读取不可访问属性的值时,__get()
会被调用。
当对不可访问属性调用 isset()
或 empty()
时,__isset()会被调用。
当对不可访问属性调用 unset()
时,__unset()
会被调用。
<?php
class PropertyTest {
/** 被重载的数据保存在此 */
private $data = array();
/** 重载不能被用在已经定义的属性 */
public $declared = 1;
/** 只有从类外部访问这个属性时,重载才会发生 */
private $hidden = 2;
public function __set($name, $value)
{
echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}
public function __get($name)
{
echo "Getting '$name'\n";
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/** PHP 5.1.0之后版本 */
public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
/** PHP 5.1.0之后版本 */
public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
/** 非魔术方法 */
public function getHidden()
{
return $this->hidden;
}
}
echo "<pre>\n";
$obj = new PropertyTest;
$obj->a = 1;
echo $obj->a . "\n\n";
var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
echo "\n";
echo $obj->declared . "\n\n";
echo "Let's experiment with the private property named 'hidden':\n";
echo "Privates are visible inside the class, so __get() not used...\n";
echo $obj->getHidden() . "\n";
echo "Privates not visible outside of class, so __get() is used...\n";
echo $obj->hidden . "\n";
?>
方法重载
public mixed __call ( string $name , array $arguments )
public static mixed __callStatic ( string $name , array $arguments )
- 在对象中调用一个不可访问方法时,__call() 会被调用。
- 在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
<?php
class MethodTest
{
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
/** PHP 5.3.0之后版本 */
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context'); // PHP 5.3.0之后版本
?>
//以上例程会输出:
//Calling object method 'runTest' in object context
//Calling static method 'runTest' in static context
遍历对象
PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach
语句。默认情况下,所有可见属性都将被用于遍历。
<?php
class MyClass
{
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';
protected $protected = 'protected var';
private $private = 'private var';
function iterateVisible() {
echo "MyClass::iterateVisible:\n";
foreach($this as $key => $value) {
print "$key => $value\n";
}
}
}
$class = new MyClass();
foreach($class as $key => $value) {
print "$key => $value\n";
}
echo "\n";
$class->iterateVisible();
?>
以上例程会输出:
var1 => value 1
var2 => value 2
var3 => value 3
MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var
- 可以实现 Iterator接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。
- 可以用 IteratorAggregate接口以替代实现所有的 Iterator 方法。
以上以后需要的时候研究
魔术方法
__construct()
,__destruct()
, __call()
,__callStatic()
, __get()
, __set()
, __isset()
, __unset()
, __sleep()
, __wakeup()
, __toString()
, __invoke()
, __set_state()
, __clone()
和 __debugInfo()
等方法在 PHP 中被称为"魔术方法"(Magic methods)
。
在命名自己的类方法时不能直接使用(必须通过其规定的方式)这些方法名,除非是想使用其魔术功能。
__sleep() 和 __wakeup()
public array __sleep ( void )
void __wakeup ( void )
serialize()
函数会检查类中是否存在一个魔术方法 __sleep()
。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL
被序列化,并产生一个 E_NOTICE
级别的错误。
与之相反,unserialize()
会检查是否存在一个 __wakeup()
方法。如果存在,则会先调用__wakeup()
方法,预先准备对象需要的资源。
需要的时候可以仔细研读
__toString()
__toString()
方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
<?php
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
?>
需要指出的是在 PHP 5.2.0 之前,
__toString()
方法只有在直接使用于echo
或printf()
,使用%s
修饰符),但不能用于非字符串环境(如使用%d
修饰符)。自 PHP 5.2.0 起,如果将一个未定义__toString()
方法的对象转换为字符串,会产生E_RECOVERABLE_ERROR
级别的错误。
__invoke()
mixed __invoke ([ $... ] )
当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被自动调用。
- 本特性只在 PHP 5.3.0 及以上版本有效。
<?php
class CallableClass
{
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
__set_state()
static object __set_state ( array $properties )
自 PHP 5.1.0 起当调用 var_export()
导出类时,此静态 方法会被调用。
本方法的唯一参数是一个数组,其中包含按 array('property' => value, ...)
格式排列的类属性
<?php
class A
{
public $var1;
public $var2;
public static function __set_state($an_array) // As of PHP 5.1.0
{
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
eval('$b = ' . var_export($a, true) . ';');
// $b = A::__set_state(array(
// 'var1' => 5,
// 'var2' => 'foo',
// ));
var_dump($b);
?>
以上例程会输出:
object(A)#2 (2) {
["var1"]=>
int(5)
["var2"]=>
string(3) "foo"
}
__debugInfo()
array __debugInfo ( void )
- PHP 5.6.0. 加入
- 在调用
var_dump()
时使用
<?php
class C {
private $prop;
public function __construct($val) {
$this->prop = $val;
}
public function __debugInfo() {
return [
'propSquared' => $this->prop ** 2,
];
}
}
var_dump(new C(42));
?>
以上例程会输出:
object(C)#1 (1) {
["propSquared"]=>
int(1764)
}
Final 关键字
方法或类定义为Final之后,对于 Final
方法 无法被子类覆盖,对于Final
类,无法被继承
<?php
class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
对象复制
进行深复制
对象复制可以通过 clone
关键字来完成(如果可能,这将调用对象的 __clone()
魔术方法)
$copy_of_object = clone $object;
<?php
class SubObject
{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// 强制复制一份this->object, 否则仍然指向同一个对象
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();
$obj->object2 = new SubObject();
$obj2 = clone $obj;
print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
$obj2->object2->instance=0;
print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
?>
输出
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 0
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 0
对象比较
当使用比较运算符(==)
比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
而如果使用全等运算符(===)
,这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
<?php
function bool2str($bool)
{
if ($bool === false) {
return 'FALSE';
} else {
return 'TRUE';
}
}
function compareObjects(&$o1, &$o2)
{
echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
}
class Flag
{
public $flag;
function Flag($flag = true) {
$this->flag = $flag;
}
}
class OtherFlag
{
public $flag;
function OtherFlag($flag = true) {
$this->flag = $flag;
}
}
$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag();
echo "Two instances of the same class\n";
compareObjects($o, $p);
echo "\nTwo references to the same instance\n";
compareObjects($o, $q);
echo "\nInstances of two different classes\n";
compareObjects($o, $r);
?>
以上例程会输出:
Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUETwo references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSEInstances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
类型约束
PHP 5 可以使用类型约束
。函数的参数可以指定必须为对象,接口,数组(PHP 5.1 起)或者 callable
(PHP 5.4 起)。
不过如果使用
NULL
作为参数的默认值,那么在调用函数的时候依然可以使用NULL
作为实参。类型约束不能用于标量类型如
int
或string
。Traits
也不允许。
<?php
//如下面的类
class MyClass
{
/**
* 测试函数
* 第一个参数必须为 OtherClass 类的一个对象
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}
/**
* 另一个测试函数
* 第一个参数必须为数组
*/
public function test_array(array $input_array) {
print_r($input_array);
}
}
/**
* 第一个参数必须为递归类型
*/
public function test_interface(Traversable $iterator) {
echo get_class($iterator);
}
/**
* 第一个参数必须为回调类型
*/
public function test_callable(callable $callback, $data) {
call_user_func($callback, $data);
}
}
// OtherClass 类定义
class OtherClass {
public $var = 'Hello World';
}
?>
后期静态绑定
self:: 的限制
使用self::
或者__CLASS__
对当前类的静态引用,取决于定义当前方法所在的类:
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
以上例程会输出:
A
后期静态绑定的用法
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 后期静态绑定从这里开始
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
以上例程会输出:
B
转发和非转发调用
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent::
或者 self::
将转发调用信息。
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo(); //非转发调用
parent::foo();// 转发调用
self::foo();// 转发调用
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
?>
以上例程会输出:
A
C
C
对象序列化
<?php
// classa.inc:
class A {
public $one = 1;
public function show_one() {
echo $this->one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
// 把变量$s保存起来以便文件page2.php能够读到
file_put_contents('store', $s);
// page2.php:
// 要正确了解序列化,必须包含下面一个文件
include("classa.inc");
$s = file_get_contents('store');
$a = unserialize($s);
// 现在可以使用对象$a里面的函数 show_one()
$a->show_one();
?>