知识点考古:php5的面向对象学习笔记

闲来无事翻看以前收藏的资料,考古到保存的这篇文章对php的OOP的整理还很系统。原链接已经打不开(http://www.cublog.cn/u/17686/showart.php?id=146562),搜索引擎也搜不到了. 为了以后偶尔还能看到,重新发布在此. 如有作者觉得侵权请联系我删除. 谢谢

正文开始:
一直没有系统的学习php5关于面向对象的新特性,因为他没有java那么严格。后来发现用java的语法写php在5里面运行的很好。于是今天翻翻手册,写点儿笔记。
因为有学习过面向对象的编程。也看过 java programming ,所以觉得一些东西只是更
OO了。写的比较简单,如果不懂建议先学学oo的编程基础。对抽象类、接口、重载、模式、对象克龙有比较多的介绍。

PHP 5 引入了新的对象模型(Object Model)。完全重写了 PHP 处理对象的方式,允许更佳性能和更多特性。

一、首先是定义:

每个类的定义都以关键字 class 开头,后面跟着类名,可以是任何非 PHP 保留字的名字。后面跟着一对花括号,里面包含有类成员和方法的定义。伪变量 $this 可以在当一个方法在对象内部调用时使用。 $this 是一个到调用对象(通常是方法所属于的对象,但也可以是另一个对象,如果该方法是从第二个对象内静态调用的话)的引用。看下面例子:

例子 1. 简单的类定义

<?php
class SimpleClass
{
   // 成员声明
   public $var = 'a default value';

   // 方法声明
   public function displayVar() {
       echo $this->var;
   }
}
?>
new

要创建一个对象的实例,必须创建一个新对象并将其赋给一个变量。当创建新对象时该对象总是被赋值,除非该对象定义了构造函数并且在出错时抛出了一个异常。

extends

一个类可以在声明中用 extends 关键字继承另一个类的方法和成员。不能扩展多个类,只能继承一个基类。

被继承的方法和成员可以通过用同样的名字重新声明被覆盖,除非父类定义方法时使用了 final 关键字。可以通过 parent:: 来访问被覆盖的方法或成员。

例子 2. 简单的类继承

<?php
class ExtendClass extends SimpleClass
{
   // Redefine the parent method
   function displayVar()
   {
       echo "Extending class\n";
       parent::displayVar();
   }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

上例将输出:

Extending class
a default value

二、接下来就是自动加载

自动加载对象

很多开发者写面向对象的应用程序时对每个类的定义建立一个 PHP 源文件。一个很大的烦恼是不得不在每个脚本(每个类一个文件)开头写一个常常的包含文件列表。

在 PHP 5 中,不再需要这样了。可以定义一个 __autoload 函数,它会在试图使用尚未被定义的类时自动调用。通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。

注: 在 __autoload 函数中抛出的异常不能被 catch 语句块捕获并导致致命错误。

我对这部分没理解清楚,我的感觉是,他是说在调用__autoload函数之前,必须已经声明了要引用文件的名字。

三、再就是构造函数和析构函数

void __construct ( [mixed args [, ...]] )

PHP 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

注: 如果子类中定义了构造函数则不会暗中调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。

另外php5为兼容以前的版本,当没有__construct()时,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。当然如果这时你声明了一个__construct()而什么都不做,那我也没办法了。
void __destruct ( void )
PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

接下来是可见性:visibility

成员和方法的可见性可以在定义时冠以关键字:public, protected or private来声明。这点和java/c++已经很相像,不再赘述。
在php5中,类成员应该指定public, protected,private中的一种可见性。这样使脚本更OO

四、再然后是::操作符。

使用::操作符,你可以访问 static, constant成员,并能访问重载类的成员或方法。
在类外部,可以classname::mumber/method 的方式访问类的成员/方法。
而在类的内部,可以使用 selfparent 这两个关键字来访问类的成员/方法。
此外在子类中要访问父类的成员/方法也要用::

五、再接下来是 static 关键字

Declaring class members or methods as static makes them accessible without needing an instantiation of the class. A member declared as static can not be accessed with an instantiated class object (though a static method can). Static members and methods cannot be re-defined in subclasses.

以static关键字声明类的成员/方法使他们可以无须实例即可访问。一个成员声明为static,那么他是不可以被这个类的实例来访问的(虽然静态方法可以)。静态成员和方法不能在子类中被重定义(重载),当然final关键字也有此功能。

The static declaration must be after the visibility declaration. For compatibility with PHP 4, if no visibility declaration is used, then the member or method will be treated as if it was declared as public.

这里要注意的是,static关键字的声明必须在可见性声明之后,为兼容php4,如果没有声明可见性,那么成员或方法将被以public对待。

Because static methods are callable without an instance of the object created, the pseudo variable $this is not available inside the method declared as static.

因为静态(static)方法不需要创建实例即可调用,因此伪$this变量,不能出现在静态(static)方法的定义中。这也是要注意的。

In fact static method calls are resolved at compile time. When using an explicit class name the method is already identified completely and no inheritance rules apply. If the call is done by self then self is translated to the current class, that is the class the code belongs to. Here also no inheritance rules apply.

事实上静态方法在便宜时即被调用,当直接以类名调用该方法前,他已经确定存在并不需实例化。如果调用是在类内部(self)那么self被转移到当前类。也是没有应用实例。(这段翻译不好,总之,静态方法是在编译时期就已经定义好的了,而不是在使用时才动态分配空间和设置参数。)

Static properties cannot be accessed through the object using the arrow operator ->.

静态属性不能通过对象用->访问。

Calling non-static methods statically generates an E_STRICT level warning.

静态的调用非静态方法会产生一个代码级警告。
例子 Static member example

<?php
class Foo
{
   public static $my_static = 'foo';

   public function staticValue() {
       return self::$my_static;
   }
}

class Bar extends Foo
{
   public function fooStatic() {
       return parent::$my_static;
   }
}


print Foo::$my_static . "\n";

$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n";      // Undefined "Property" my_static

// $foo::my_static is not possible

print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";
?>

例子 Static method example

<?php
class Foo {
   public static function aStaticMethod() {
       // ...
   }
}

Foo::aStaticMethod();
?>

六、类常量

It is possible to define constant values on a per-class basis remaining the same and unchangeable. Constants differ from normal variables in that you don't use the $ symbol to declare or use them. Like static members, constant values cannot be accessed from an instance of the object (using $object::constant).

定义不变值的类常量来承载不变的信息是可行的。因为像static成员一样,constant常量是不可以被类的实例(对象)用$object::constant的方式访问的。

例子. Defining and using a constant

<?php
class MyClass
{
   const constant = 'constant value';

   function showConstant() {
       echo  self::constant . "\n";
   }
}

echo MyClass::constant . "\n";

$class = new MyClass();
$class->showConstant();
// echo $class::constant;  is not allowed
?>

七、类的抽象

PHP 5 introduces abstract classes and methods. It is not allowed to create an instance of a class that has been defined as abstract. Any class that contains at least one abstract method must also be abstract. Methods defined as abstract simply declare the method's signature they cannot define the implementation.

在php5中引入了抽象类和抽象方法的概念。(如同java/c++中那样)他不允许创建抽象类的实例,至少含有一个抽象方法。抽象方法只定义方法名称(签名)而不定义具体实现,等等。

When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or weaker) visibillity. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public.

当抽象类被继承时,父类中所有的抽象方法必须在子类中定义。此外,这些方法的可见性定义必须与父类相同或比父类弱。

例子. Abstract class example

<?php
abstract class AbstractClass
{
   // Force Extending class to define this method
   abstract protected function getValue();
   abstract protected function prefixValue($prefix);

   // Common method
   public function printOut() {
       print $this->getValue() . "\n";
   }
}

class ConcreteClass1 extends AbstractClass
{
   protected function getValue() {
       return "ConcreteClass1";
   }

   public function prefixValue($prefix) {
       return "{$prefix}ConcreteClass1";
   }
}

class ConcreteClass2 extends AbstractClass
{
   public function getValue() {
       return "ConcreteClass2";
   }

   public function prefixValue($prefix) {
       return "{$prefix}ConcreteClass2";
   }
}

$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') ."\n";

$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') ."\n";
?>

上例将输出:

ConcreteClass1
FOO_ConcreteClass1
ConcreteClass2
FOO_ConcreteClass2

八、对象接口

Object interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are handled.

对象接口允许你在没有定义类的特定方法怎样处理之前创建类该方法的代码。
因为该方法需要执行。(翻译不好)

Interfaces are defined using the interface keyword, in the same way as a standard class, but without any of the methods having their contents defined.

接口使用interface关键字定义,类似于标准的类,但没有定义任何方法的具体实现(有点儿像抽象类,但不同,我是这么认为的,抽象类中至少有一个方法要是抽象的,而接口中所有的方法都必须是抽象的。
而且接口是针对方法而引进的,而抽象类是在类的级别上引进的,所以我们说继承一个抽象类,而说实现一个接口。这是不同的,再有就是我认为--专家也这么认为,接口是为实现多继承而引入的)。

All methods declared in an interface must be public, this is the nature of an interface.

此外,接口中所有的方法都应该定义成public的,对于接口这是显然的。不然定义个private的接口干嘛使!

implements

To implement an interface, the implements operator is used. All methods in the interface must be implemented within a class; failure to do so will result in a fatal error. Classes may implement more than one interface if desired by separating each interface with a comma.

用implements关键字来执行接口。而且可以实现多个接口,之间用','号隔开。

Note: A class cannot implement two interfaces that share function names, since it would cause ambiguity.
注意:一个类不能实现两个有相同函数名的接口,因为那样会引起混淆。

例子. Interface example

<?php
// Declare the interface 'iTemplate'
interface iTemplate
{
   public function setVariable($name, $var);
   public function getHtml($template);
}

// Implement the interface
// This will work
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;
   }
}

// This will not work
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (iTemplate::getHtml)
class BadTemplate implements iTemplate
{
   private $vars = array();
 
   public function setVariable($name, $var)
   {
       $this->vars[$name] = $var;
   }
}

?>

九、超载(overloading)

我认为超载之所以重要是因为有了这个机制,你可以不必为某个方法所实现的功能与你想要的功能不同而再定义一个其他名称去实现这个功能。而只需使用这个办法是你在使用有你认为意义的名字的同时实现你所认为他应该有的功能。

Both method calls and member accesses can be overloaded via the __call, __get and __set methods. These methods will only be triggered when your object or inherited object doesn't contain the member or method you're trying to access. All overloading methods must not be defined as static.

方法调用和成员访问都可以通过__call,__get 和__set方法来超载。
这些方法只有在你的对象或继承的对象不包含你试图访问的该成员或方法时才被触发。所有的超载方法必须不能声明为static型。

In PHP 5.0.x, all overloading methods must be defined as public.

在PHP 5.0.X中,所有的超载方法必须定义为public类型。

Since PHP 5.1.0 it is also possible to overload the isset() and unset() functions via the __isset and __unset methods respectively.

从PHP5.1.0起,isset()和unset() 也可以通过__isset和__unset方法被超载。

成员超载
void __set ( string name, mixed value )
mixed __get ( string name )
bool __isset ( string name )
void __unset ( string name )

Class members can be overloaded to run custom code defined in your class by defining these specially named methods. The $name parameter used is the name of the variable that should be set or retrieved. The __set() method's $value parameter specifies the value that the object should set the $name.

类成员可以被超载用来运行客户定义的指定名字的方法。用__set()方法设定指定方法的名称和值,具体如下例。

Example. overloading with __get, __set, __isset and __unset example

<?php
class Setter
{
   public $n;
   private $x = array("a" => 1, "b" => 2, "c" => 3);

   private function __get($nm)
   {
       echo "Getting [$nm]\n";

       if (isset($this->x[$nm])) {
           $r = $this->x[$nm];
           print "Returning: $r\n";
           return $r;
       } else {
           echo "Nothing!\n";
       }
   }

   private function __set($nm, $val)
   {
       echo "Setting [$nm] to $val\n";

       if (isset($this->x[$nm])) {
           $this->x[$nm] = $val;
           echo "OK!\n";
       } else {
           echo "Not OK!\n";
       }
   }

   private function __isset($nm)
   {
       echo "Checking if $nm is set\n";

       return isset($this->x[$nm]);
   }

   private function __unset($nm)
   {
       echo "Unsetting $nm\n";

       unset($this->x[$nm]);
   }
}

$foo = new Setter();
$foo->n = 1;
$foo->a = 100;
$foo->a++;
$foo->z++;

var_dump(isset($foo->a)); //true
unset($foo->a);
var_dump(isset($foo->a)); //false

// this doesn't pass through the __isset() method
// because 'n' is a public property
var_dump(isset($foo->n));

var_dump($foo);
?>

The above example will output:

Setting [a] to 100
OK!
Getting [a]
Returning: 100
Setting [a] to 101
OK!
Getting [z]
Nothing!
Setting [z] to 1
Not OK!

Checking if a is set
bool(true)
Unsetting a
Checking if a is set
bool(false)
bool(true)

object(Setter)#1 (2) {
  ["n"]=>
  int(1)
  ["x:private"]=>
  array(2) {
   ["b"]=>
   int(2)
   ["c"]=>
   int(3)
  }
}
Method overloading

mixed __call ( string name, array arguments )

Class methods can be overloaded to run custom code defined in your class by defining this specially named method. The $name parameter used is the name as the function name that was requested to be used. The arguments that were passed in the function will be defined as an array in the $arguments parameter. The value returned from the __call() method will be returned to the caller of the method.

也就是说,php提供了__call()函数来返回你要调用的方法。就是你定义的特定名字的方法,如果没有,那么就会调用__call()方法,并把你的方法的名字做为第一个参数$name,而你的方法中的参量作为__call()的第二个参数$arguments(他通常定义为一个数组)。然后用__call()的执行体来作为你的特定的函数的执行体执行,他的返回结果作为你要的返回结果返回。

Example . overloading with __call example

<?php
class Caller
{
   private $x = array(1, 2, 3);

   private function __call($m, $a)
   {
       print "Method $m called:\n";
       var_dump($a);
       return $this->x;
   }
}

$foo = new Caller();
$a = $foo->test(1, "2", 3.4, true);
var_dump($a);
?>

The above example will output:

Method test called:

array(4) {
   [0]=>
   int(1)
   [1]=>
   string(1) "2"
   [2]=>
   float(3.4)
   [3]=>
   bool(true)
}
array(3) {
   [0]=>
   int(1)
   [1]=>
   int(2)
   [2]=>
   int(3)
}

十、Object Iteration 对象反复?我也不清楚咋翻译

PHP 5 provides a way for objects to be defined so it is possible to iterate through a list of items, with, for example a foreach statement. By default, all visible properties will be used for the iteration.

php5提供了一种对于对象的可以进行循环操作列出其元素的途径。例如使用foreach,默认的,所有的可见属性都将用在循环中。(翻译不好,还是看例子吧)

Example. Simple Object Iteration

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

?>

The above example will output:

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

As the output shows, the foreach iterated through all visible variables that can be accessed. To take it a step further you can implement one of PHP 5's internal interface named Iterator. This allows the object to decide what and how the object will be iterated.

foreach循环只能显示可访问属性(public的)。更进一步,你可以执行php5的一个名为Iterator的内部接口。
他允许对象决定什么(what)及怎样(how)被循环/反复(iterate)

Example . Object Iteration implementing 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";
}
?>

The above example will output:

rewinding
current: 1
valid: 1
current: 1
key: 0
0: 1
next: 2
current: 2
valid: 1
current: 2
key: 1
1: 2
next: 3
current: 3
valid: 1
current: 3
key: 2
2: 3
next:
current:
valid:

You can also define your class so that it doesn't have to define all the Iterator functions by simply implementing the PHP 5 IteratorAggregate interface.

你还可以定义你的类,来通过执行php5的IteratorAggregate接口,而不必定义所有的Iterator接口的函数。

十一、模式(Patterns)

Patterns are ways to describe best practices and good designs. They show a flexible solution to common programming problems.

模式总是被描述成最好的实践和优秀的设计。他们为普遍的项目问题定义了一种灵活的解决方案。

Factory 工厂

这里说的比较简单,建议还是买本国外的书看看吧。最好是有有经验的工程师当面以实例讲解。我是不行~自己还没整明白,不能在这儿误人子弟~!

The Factory pattern allows for the instantiation of objects at runtime. It is called a Factory Pattern since it is responsible for "manufacturing" an object. A Parameterized Factory receives the name of the class to instantiate as argument.

Example. Parameterized Factory Method

<?php
class Example
{
   // The parameterized factory method
   public static function factory($type)
   {
       if (include_once 'Drivers/' . $type . '.php') {
           $classname = 'Driver_' . $type;
           return new $classname;
       } else {
           throw new Exception ('Driver not found');
       }
   }
}
?>

Defining this method in a class allows drivers to be loaded on the fly. If the Example class was a database abstraction class, loading a MySQL and SQLite driver could be done as follows:

<?php
// Load a MySQL Driver
$mysql = Example::factory('MySQL');

// Load a SQLite Driver
$sqlite = Example::factory('SQLite');
?>
Singleton 单例

The Singleton pattern applies to situations in which there needs to be a single instance of a class. The most common example of this is a database connection. Implementing this pattern allows a programmer to make this single instance easily accessible by many other objects.

Example Singleton Function

<?php
class Example
{
   // Hold an instance of the class
   private static $instance;

   // A private constructor; prevents direct creation of object
   private function __construct()
   {
       echo 'I am constructed';
   }

   // The singleton method
   public static function singleton()
   {
       if (!isset(self::$instance)) {
           $c = __CLASS__;
           self::$instance = new $c;
       }

       return self::$instance;
   }

   // Example method
   public function bark()
   {
       echo 'Woof!';
   }

   // Prevent users to clone the instance
   public function __clone()
   {
       trigger_error('Clone is not allowed.', E_USER_ERROR);
   }
}
?>

This allows a single instance of the Example class to be retrieved.

<?php
// This would fail because the constructor is private
$test = new Example;

// This will always retrieve a single instance of the class
$test = Example::singleton();
$test->bark();

// This will issue an E_USER_ERROR.
$test_clone = clone($test);
?>

十二、魔术方法(Magic Methods)

The function names __construct, __destruct (see Constructors and Destructors), __call, __get, __set, __isset, __unset (see Overloading), __sleep, __wakeup, __toString, __set_state, __clone and __autoload are magical in PHP classes. You cannot have functions with these names in any of your classes unless you want the magic functionality associated with them.

Caution
PHP reserves all function names starting with __ as magical. It is recommended that you do not use function names with __ in PHP unless you want some documented magic functionality.

__sleep and __wakeup
serialize() checks if your class has a function with the magic name __sleep. If so, that function is executed prior to any serialization. It can clean up the object and is supposed to return an array with the names of all variables of that object that should be serialized.

The intended use of __sleep is to close any database connections that the object may have, commit pending data or perform similar cleanup tasks. Also, the function is useful if you have very large objects which do not need to be saved completely.

Conversely, unserialize() checks for the presence of a function with the magic name __wakeup. If present, this function can reconstruct any resources that the object may have.

The intended use of __wakeup is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.

Example 19-27. Sleep and wakeup

<?php
class Connection {
   protected $link;
   private $server, $username, $password, $db;
  
   public function __construct($server, $username, $password, $db)
   {
       $this->server = $server;
       $this->username = $username;
       $this->password = $password;
       $this->db = $db;
       $this->connect();
   }

   private function connect()
   {
       $this->link = mysql_connect($this->server, $this->username, $this->password);
       mysql_select_db($this->db, $this->link);
   }

   public function __sleep()
   {
       mysql_close($this->link);
   }

   public function __wakeup()
   {
       $this->connect();
   }
}
?>
__toString

The __toString method allows a class to decide how it will react when it is converted to a string.

Example 19-28. Simple example

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

The above example will output:

Hello

It is worth noting that the __toString method will only be called when it is directly combined with echo() or print().

Example 19-29. Cases where __toString is called

<?php
// __toString called
echo $class;

// __toString called (still a normal parameter for echo)
echo 'text', $class;

// __toString not called (concatenation operator used first)
echo 'text' . $class;

// __toString not called (cast to string first)
echo (string) $class;

// __toString not called (cast to string first)
echo "text $class";
?>
__set_state

This static method is called for classes exported by var_export() since PHP 5.1.0.

The only parameter of this method is an array containing exported properties in the form array('property' => value, ...).

十三、关键字final (Final Keyword)

PHP 5 introduces the final keyword, which prevents child classes from overriding a method by prefixing the definition with final. If the class itself is being defined final then it cannot be extended.

也就是说定义为final的类或方法不能被继承。

Example 19-30. Final methods example

<?php
class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }

   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called\n";
   }
}
// Results in Fatal error: Cannot override final method BaseClass::moreTesting()
?>

Example 19-31. Final class example

<?php
final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }

   // Here it doesn't matter if you specify the function as final or not
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
}
// Results in Fatal error: Class ChildClass may not inherit from final class (BaseClass)
?>

十四、克龙对象(Object cloning)

Creating a copy of an object with fully replicated properties is not always the wanted behavior. A good example of the need for copy constructors, is if you have an object which represents a GTK window and the object holds the resource of this GTK window, when you create a duplicate you might want to create a new window with the same properties and have the new object hold the resource of the new window. Another example is if your object holds a reference to another object which it uses and when you replicate the parent object you want to create a new instance of this other object so that the replica has its own separate copy.

An object copy is created by using the clone keyword (which calls the object's __clone() method if possible). An object's __clone() method cannot be called directly.

$copy_of_object = clone $object;
When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. Any properties that are references to other variables, will remain references. If a __clone() method is defined, then the newly created object's __clone() method will be called, to allow any necessary properties that need to be changed.

Example 19-32. Cloning an 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()
   {
       // Force a copy of this->object, otherwise
       // it will point to same 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);

?>

The above example will output:

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
       )

)

十五、 Comparing objects

In PHP 5, object comparison is more complicated than in PHP 4 and more in accordance to what one will expect from an Object Oriented Language (not that PHP 5 is such a language).

When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values, and are instances of the same class.

On the other hand, when using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

An example will clarify these rules.

Example 19-33. Example of object comparison in PHP 5

<?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);
?>
The above example will output:

Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE

Two references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE

Instances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE

十六、Reflection

Introduction
PHP 5 comes with a complete reflection API that adds the ability to reverse-engineer classes, interfaces, functions and methods as well as extensions. Additionally, the reflection API also offers ways of retrieving doc comments for functions, classes and methods.

The reflection API is an object-oriented extension to the Zend Engine, consisting of the following classes:

<?php
class Reflection { }
interface Reflector { }
class ReflectionException extends Exception { }
class ReflectionFunction implements Reflector { }
class ReflectionParameter implements Reflector { }
class ReflectionMethod extends ReflectionFunction { }
class ReflectionClass implements Reflector { }
class ReflectionObject extends ReflectionClass { }
class ReflectionProperty implements Reflector { }
class ReflectionExtension implements Reflector { }
?>

Note: For details on these classes, have a look at the next chapters.

If we were to execute the code in the example below:

Example 19-34. Basic usage of the reflection API

<?php
Reflection::export(new ReflectionClass('Exception'));
?>

The above example will output:

Class [ <internal> class Exception ] {

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [6] {
    Property [ <default> protected $message ]
    Property [ <default> private $string ]
    Property [ <default> protected $code ]
    Property [ <default> protected $file ]
    Property [ <default> protected $line ]
    Property [ <default> private $trace ]
  }

  - Methods [9] {
    Method [ <internal> final private method __clone ] {
    }

    Method [ <internal> <ctor> public method __construct ] {

      - Parameters [2] {
        Parameter #0 [ <required> $message ]
        Parameter #1 [ <required> $code ]
      }
    }

    Method [ <internal> final public method getMessage ] {
    }

    Method [ <internal> final public method getCode ] {
    }

    Method [ <internal> final public method getFile ] {
    }

    Method [ <internal> final public method getLine ] {
    }

    Method [ <internal> final public method getTrace ] {
    }

    Method [ <internal> final public method getTraceAsString ] {
    }

    Method [ <internal> public method __toString ] {
    }
  }
}
ReflectionException

ReflectionException extends the standard Exception and is thrown by Reflection API. No specific methods or properties are introduced.

ReflectionFunction

The ReflectionFunction class lets you reverse-engineer functions.

<?php
class ReflectionFunction implements Reflector
{
   final private __clone()
   public object __construct(string name)
   public string __toString()
   public static string export(string name, bool return)
   public string getName()
   public bool isInternal()
   public bool isUserDefined()
   public string getFileName()
   public int getStartLine()
   public int getEndLine()
   public string getDocComment()
   public array getStaticVariables()
   public mixed invoke(mixed args)
   public mixed invokeArgs(array args)
   public bool returnsReference()
   public ReflectionParameter[] getParameters()
   public int getNumberOfParameters()
   public int getNumberOfRequiredParameters()
}
?>

Note: getNumberOfParameters() and getNumberOfRequiredParameters() were added in PHP 5.0.3, while invokeArgs() was added in PHP 5.1.0.

To introspect a function, you will first have to create an instance of the ReflectionFunction class. You can then call any of the above methods on this instance.

Example 19-35. Using the ReflectionFunction class

<?php
/**
 * A simple counter
 *
 * @return    int
 */
function counter()
{
   static $c = 0;
   return $c++;
}

// Create an instance of the Reflection_Function class
$func = new ReflectionFunction('counter');

// Print out basic information
printf(
   "===> The %s function '%s'\n".
   "    declared in %s\n".
   "    lines %d to %d\n",
   $func->isInternal() ? 'internal' : 'user-defined',
   $func->getName(),
   $func->getFileName(),
   $func->getStartLine(),
   $func->getEndline()
);

// Print documentation comment
printf("---> Documentation:\n %s\n", var_export($func->getDocComment(), 1));

// Print static variables if existant
if ($statics = $func->getStaticVariables())
{
   printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Invoke the function
printf("---> Invokation results in: ");
var_dump($func->invoke());


// you may prefer to use the export() method
echo "\nReflectionFunction::export() results:\n";
echo ReflectionFunction::export('counter');
?>

Note: The method invoke() accepts a variable number of arguments which are passed to the function just as in call_user_func().

ReflectionParameter

The ReflectionParameter class retrieves information about a function's or method's parameters.

<?php
class ReflectionParameter implements Reflector
{
   final private __clone()
   public object __construct(string name)
   public string __toString()
   public static string export(mixed function, mixed parameter, bool return)
   public string getName()
   public bool isPassedByReference()
   public ReflectionFunction getDeclaringFunction()
   public ReflectionClass getDeclaringClass()
   public ReflectionClass getClass()
   public bool isArray()
   public bool allowsNull()
   public bool isPassedByReference()
   public bool getPosition()
   public bool isOptional()
   public bool isDefaultValueAvailable()
   public mixed getDefaultValue()
}
?>

Note: getDefaultValue(), isDefaultValueAvailable() and isOptional() were added in PHP 5.0.3, while isArray() was added in PHP 5.1.0. getDeclaringFunction() and getPosition() were added in PHP 5.1.3.

To introspect function parameters, you will first have to create an instance of the ReflectionFunction or ReflectionMethod classes and then use their getParameters() method to retrieve an array of parameters.

Example 19-36. Using the ReflectionParameter class

<?php
function foo($a, $b, $c) { }
function bar(Exception $a, &$b, $c) { }
function baz(ReflectionFunction $a, $b = 1, $c = null) { }
function abc() { }

// Create an instance of Reflection_Function with the
// parameter given from the command line.   
$reflect = new ReflectionFunction($argv[1]);

echo $reflect;

foreach ($reflect->getParameters() as $i => $param) {
   printf(
       "-- Parameter #%d: %s {\n".
       "  Class: %s\n".
       "  Allows NULL: %s\n".
       "  Passed to by reference: %s\n".
       "  Is optional?: %s\n".
       "}\n",
       $i,
       $param->getName(),
       var_export($param->getClass(), 1),
       var_export($param->allowsNull(), 1),
       var_export($param->isPassedByReference(), 1),
       $param->isOptional() ? 'yes' : 'no'
   );
}
?>
ReflectionClass

The ReflectionClass class lets you reverse-engineer classes.

<?php
class ReflectionClass implements Reflector
{
   final private __clone()
   public object __construct(string name)
   public string __toString()
   public static string export(mixed class, bool return)
   public string getName()
   public bool isInternal()
   public bool isUserDefined()
   public bool isInstantiable()
   public bool hasConstant(string name)
   public bool hasMethod(string name)
   public bool hasProperty(string name)
   public string getFileName()
   public int getStartLine()
   public int getEndLine()
   public string getDocComment()
   public ReflectionMethod getConstructor()
   public ReflectionMethod getMethod(string name)
   public ReflectionMethod[] getMethods()
   public ReflectionProperty getProperty(string name)
   public ReflectionProperty[] getProperties()
   public array getConstants()
   public mixed getConstant(string name)
   public ReflectionClass[] getInterfaces()
   public bool isInterface()
   public bool isAbstract()
   public bool isFinal()
   public int getModifiers()
   public bool isInstance(stdclass object)
   public stdclass newInstance(mixed args)
   public stdclass newInstanceArgs(array args)
   public ReflectionClass getParentClass()
   public bool isSubclassOf(ReflectionClass class)
   public array getStaticProperties()
   public mixed getStaticPropertyValue(string name [, mixed default])
   public void setStaticPropertyValue(string name, mixed value)
   public array getDefaultProperties()
   public bool isIterateable()
   public bool implementsInterface(string name)
   public ReflectionExtension getExtension()
   public string getExtensionName()
}
?>

Note: hasConstant(), hasMethod(), hasProperty(), getStaticPropertyValue() and setStaticPropertyValue() were added in PHP 5.1.0, while newInstanceArgs() was added in PHP 5.1.3.

To introspect a class, you will first have to create an instance of the ReflectionClass class. You can then call any of the above methods on this instance.

Example 19-37. Using the ReflectionClass class

<?php
interface Serializable
{
   // ...
}

class Object
{
   // ...
}

/**
 * A counter class
 */
class Counter extends Object implements Serializable
{
   const START = 0;
   private static $c = Counter::START;

   /**
     * Invoke counter
     *
     * @access  public
     * @return  int
     */
   public function count() {
       return self::$c++;
   }
}

// Create an instance of the ReflectionClass class
$class = new ReflectionClass('Counter');

// Print out basic information
printf(
   "===> The %s%s%s %s '%s' [extends %s]\n" .
   "    declared in %s\n" .
   "    lines %d to %d\n" .
   "    having the modifiers %d [%s]\n",
       $class->isInternal() ? 'internal' : 'user-defined',
       $class->isAbstract() ? ' abstract' : '',
       $class->isFinal() ? ' final' : '',
       $class->isInterface() ? 'interface' : 'class',
       $class->getName(),
       var_export($class->getParentClass(), 1),
       $class->getFileName(),
       $class->getStartLine(),
       $class->getEndline(),
       $class->getModifiers(),
       implode(' ', Reflection::getModifierNames($class->getModifiers()))
);

// Print documentation comment
printf("---> Documentation:\n %s\n", var_export($class->getDocComment(), 1));

// Print which interfaces are implemented by this class
printf("---> Implements:\n %s\n", var_export($class->getInterfaces(), 1));

// Print class constants
printf("---> Constants: %s\n", var_export($class->getConstants(), 1));

// Print class properties
printf("---> Properties: %s\n", var_export($class->getProperties(), 1));

// Print class methods
printf("---> Methods: %s\n", var_export($class->getMethods(), 1));

// If this class is instantiable, create an instance
if ($class->isInstantiable()) {
   $counter = $class->newInstance();

   echo '---> $counter is instance? ';
   echo $class->isInstance($counter) ? 'yes' : 'no';

   echo "\n---> new Object() is instance? ";
   echo $class->isInstance(new Object()) ? 'yes' : 'no';
}
?>

Note: The method newInstance() accepts a variable number of arguments which are passed to the function just as in call_user_func().

Note: $class = new ReflectionClass('Foo'); $class->isInstance($arg) is equivalent to $arg instanceof Foo or is_a($arg, 'Foo').

ReflectionObject

The ReflectionObject class lets you reverse-engineer objects.

<?php
class ReflectionObject extends ReflectionClass
{
   final private __clone()
   public object __construct(mixed object)
   public string __toString()
   public static string export(mixed object, bool return)
}
?>
ReflectionMethod

The ReflectionMethod class lets you reverse-engineer class methods.

<?php
class ReflectionMethod extends ReflectionFunction
{
   public __construct(mixed class, string name)
   public string __toString()
   public static string export(mixed class, string name, bool return)
   public mixed invoke(stdclass object, mixed args)
   public mixed invokeArgs(stdclass object, array args)
   public bool isFinal()
   public bool isAbstract()
   public bool isPublic()
   public bool isPrivate()
   public bool isProtected()
   public bool isStatic()
   public bool isConstructor()
   public bool isDestructor()
   public int getModifiers()
   public ReflectionClass getDeclaringClass()

   // Inherited from ReflectionFunction
   final private __clone()
   public string getName()
   public bool isInternal()
   public bool isUserDefined()
   public string getFileName()
   public int getStartLine()
   public int getEndLine()
   public string getDocComment()
   public array getStaticVariables()
   public bool returnsReference()
   public ReflectionParameter[] getParameters()
   public int getNumberOfParameters()
   public int getNumberOfRequiredParameters()
}
?>

To introspect a method, you will first have to create an instance of the ReflectionMethod class. You can then call any of the above methods on this instance.

Example 19-38. Using the ReflectionMethod class

<?php
class Counter
{
   private static $c = 0;

   /**
     * Increment counter
     *
     * @final
     * @static
     * @access  public
     * @return  int
     */
   final public static function increment()
   {
       return ++self::$c;
   }
}

// Create an instance of the Reflection_Method class
$method = new ReflectionMethod('Counter', 'increment');

// Print out basic information
printf(
   "===> The %s%s%s%s%s%s%s method '%s' (which is %s)\n" .
   "    declared in %s\n" .
   "    lines %d to %d\n" .
   "    having the modifiers %d[%s]\n",
       $method->isInternal() ? 'internal' : 'user-defined',
       $method->isAbstract() ? ' abstract' : '',
       $method->isFinal() ? ' final' : '',
       $method->isPublic() ? ' public' : '',
       $method->isPrivate() ? ' private' : '',
       $method->isProtected() ? ' protected' : '',
       $method->isStatic() ? ' static' : '',
       $method->getName(),
       $method->isConstructor() ? 'the constructor' : 'a regular method',
       $method->getFileName(),
       $method->getStartLine(),
       $method->getEndline(),
       $method->getModifiers(),
       implode(' ', Reflection::getModifierNames($method->getModifiers()))
);

// Print documentation comment
printf("---> Documentation:\n %s\n", var_export($method->getDocComment(), 1));

// Print static variables if existant
if ($statics= $method->getStaticVariables()) {
   printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Invoke the method
printf("---> Invokation results in: ");
var_dump($method->invoke(NULL));
?>

Note: Trying to invoke private, protected or abstract methods will result in an exception being thrown from the invoke() method.

Note: For static methods as seen above, you should pass NULL as the first argument to invoke(). For non-static methods, pass an instance of the class.

ReflectionProperty

The ReflectionProperty class lets you reverse-engineer class properties.

<?php
class ReflectionProperty implements Reflector
{
   final private __clone()
   public __construct(mixed class, string name)
   public string __toString()
   public static string export(mixed class, string name, bool return)
   public string getName()
   public bool isPublic()
   public bool isPrivate()
   public bool isProtected()
   public bool isStatic()
   public bool isDefault()
   public int getModifiers()
   public mixed getValue(stdclass object)
   public void setValue(stdclass object, mixed value)
   public ReflectionClass getDeclaringClass()
   public string getDocComment()
}
?>

Note: getDocComment() was added in PHP 5.1.0.

To introspect a property, you will first have to create an instance of the ReflectionProperty class. You can then call any of the above methods on this instance.

Example 19-39. Using the ReflectionProperty class

<?php
class String
{
   public $length  = 5;
}

// Create an instance of the ReflectionProperty class
$prop = new ReflectionProperty('String', 'length');

// Print out basic information
printf(
   "===> The%s%s%s%s property '%s' (which was %s)\n" .
   "    having the modifiers %s\n",
       $prop->isPublic() ? ' public' : '',
       $prop->isPrivate() ? ' private' : '',
       $prop->isProtected() ? ' protected' : '',
       $prop->isStatic() ? ' static' : '',
       $prop->getName(),
       $prop->isDefault() ? 'declared at compile-time' : 'created at run-time',
       var_export(Reflection::getModifierNames($prop->getModifiers()), 1)
);

// Create an instance of String
$obj= new String();

// Get current value
printf("---> Value is: ");
var_dump($prop->getValue($obj));

// Change value
$prop->setValue($obj, 10);
printf("---> Setting value to 10, new value is: ");
var_dump($prop->getValue($obj));

// Dump object
var_dump($obj);
?>

Note: Trying to get or set private or protected class property's values will result in an exception being thrown.

ReflectionExtension

The ReflectionExtension class lets you reverse-engineer extensions. You can retrieve all loaded extensions at runtime using the get_loaded_extensions().

<?php
class ReflectionExtension implements Reflector {
   final private __clone()
   public __construct(string name)
   public string __toString()
   public static string export(string name, bool return)
   public string getName()
   public string getVersion()
   public ReflectionFunction[] getFunctions()
   public array getConstants()
   public array getINIEntries()
   public ReflectionClass[] getClasses()
   public array getClassNames()
}
?>

To introspect an extension, you will first have to create an instance of the ReflectionExtension class. You can then call any of the above methods on this instance.

Example 19-40. Using the ReflectionExtension class

<?php
// Create an instance of the ReflectionProperty class
$ext = new ReflectionExtension('standard');

// Print out basic information
printf(
   "Name        : %s\n" .
   "Version    : %s\n" .
   "Functions  : [%d] %s\n" .
   "Constants  : [%d] %s\n" .
   "INI entries : [%d] %s\n" .
   "Classes    : [%d] %s\n",
       $ext->getName(),
       $ext->getVersion() ? $ext->getVersion() : 'NO_VERSION',
       sizeof($ext->getFunctions()),
       var_export($ext->getFunctions(), 1),

       sizeof($ext->getConstants()),
       var_export($ext->getConstants(), 1),

       sizeof($ext->getINIEntries()),
       var_export($ext->getINIEntries(), 1),

       sizeof($ext->getClassNames()),
       var_export($ext->getClassNames(), 1)
);
?>
Extending the reflection classes

In case you want to create specialized versions of the built-in classes (say, for creating colorized HTML when being exported, having easy-access member variables instead of methods or having utility methods), you may go ahead and extend them.

Example 19-41. Extending the built-in classes

<?php
/**
 * My Reflection_Method class
 */
class My_Reflection_Method extends ReflectionMethod
{
   public $visibility = '';

   public function __construct($o, $m)
   {
       parent::__construct($o, $m);
       $this->visibility= Reflection::getModifierNames($this->getModifiers());
   }
}

/**
 * Demo class #1
 *
 */
class T {
   protected function x() {}
}

/**
 * Demo class #2
 *
 */
class U extends T {
   function x() {}
}

// Print out information
var_dump(new My_Reflection_Method('U', 'x'));
?>

Note: Caution: If you're overwriting the constructor, remember to call the parent's constructor before any code you insert. Failing to do so will result in the following: Fatal error: Internal error: Failed to retrieve the reflection object。

十七Type Hinting
PHP 5 introduces Type Hinting. Functions are now able to force parameters to be objects (by specifying the name of the class in the function prototype) or arrays (since PHP 5.1).

Example 19-42. Type Hinting examples

<?php
// An example class
class MyClass
{
   /**
     * A test function
     *
     * First parameter must be an object of type OtherClass
     */
   public function test(OtherClass $otherclass) {
       echo $otherclass->var;
   }


   /**
     * Another test function
     *
     * First parameter must be an array
     */
   public function test_array(array $input_array) {
       print_r($input_array);
   }
}

// Another example class
class OtherClass {
   public $var = 'Hello World';
}
?>

Failing to satisfy the type hint results in a fatal error.

<?php
// An instance of each class
$myclass = new MyClass;
$otherclass = new OtherClass;

// Fatal Error: Argument 1 must be an object of class OtherClass
$myclass->test('hello');

// Fatal Error: Argument 1 must be an instance of OtherClass
$foo = new stdClass;
$myclass->test($foo);

// Fatal Error: Argument 1 must not be null
$myclass->test(null);

// Works: Prints Hello World
$myclass->test($otherclass);

// Fatal Error: Argument 1 must be an array
$myclass->test_array('a string');

// Works: Prints the array
$myclass->test_array(array('a', 'b', 'c'));
?>

Type hinting also works with functions:

<?php
// An example class
class MyClass {
   public $var = 'Hello World';
}

/**
 * A test function
 *
 * First parameter must be an object of type MyClass
 */
function MyFunction (MyClass $foo) {
   echo $foo->var;
}

// Works
$myclass = new MyClass;
MyFunction($myclass);
?>

Type Hints can only be of the object and array (since PHP 5.1) type. Traditional type hinting with int and string isn't supported.

posted @ 2023-11-05 08:21  鹰的翅膀让我翱翔  阅读(4)  评论(0编辑  收藏  举报