关于奇矩互动奇矩互动招贤纳士奇矩互动优质虚拟主机Discuz!商业用户享有本站VIP服务LAMP环境配置手册(CentOS5.1)
 12 12
发新话题
打印

PHP学习专题--第11期:PHP5 Web开发详解基础篇

本主题由 Edwin 于 2008-4-6 10:56 设置高亮

PHP学习专题--第11期:PHP5 Web开发详解基础篇

PHP5.0对象模型探索之对象串行化

串行化可以把变量包括对象,转化成连续bytes数据,你可以将串行化后的变量存在一个文件里或在网络上传输,然后再反串行化还原为原来的数据。你在反串行化类的对象之前定义的类,PHP可以成功地存储其对象的属性和方法. 有时你可能需要一个对象在反串行化后立即执行。为了这样的目的,PHP会自动寻找__sleep和__wakeup方法。

  当一个对象被串行化,PHP会调用__sleep方法(如果存在的话). 在反串行化一个对象后,PHP 会调用__wakeup方法. 这两个方法都不接受参数. __sleep方法必须返回一个数组,包含需要串行化的属性. PHP会抛弃其它属性的值。如果没有__sleep方法,PHP将保存所有属性。

  例子1显示了如何用__sleep和 __wakeup方法来串行化一个对象. Id属性是一个不打算保留在对象中的临时属性. __sleep方法保证在串行化的对象中不包含id属性. 当反串行化一个User对象,__wakeup方法建立id属性的新值. 这个例子被设计成自我保持. 在实际开发中,你可能发现包含资源(如图像或数据流)的对象需要这些方法。
复制内容到剪贴板
代码:
<?php
class User {
         public $name;
         public $id;

         function __construct()
          {
                  // give user a unique ID 赋予一个不同的ID
                  $this - >id = uniqid();
                 }

         function __sleep()
          {
                  // do not serialize this->id 不串行化id
                  return(array("name"));
                 }

         function __wakeup()
          {
                  // give user a unique ID
                  $this - >id = uniqid();
                 }
}
// create object 建立一个对象
$u = new User;
$u->name = "Leon";
// serialize it 串行化 注意不串行化id属性,id的值被抛弃
$s = serialize($u);
// unserialize it 反串行化 id被重新赋值
$u2 = unserialize($s);
// $u and $u2 have different IDs $u和$u2有不同的ID
print_r($u);
print_r($u2);

?>

TOP

PHP5.0对象模型探索之重载

PHP4中已经有了重载的语法来建立对于外部对象模型的映射,就像Java和COM那样. PHP5带来了强大的面向对象重载,允许程序员建立自定义的行为来访问属性和调用方法。

重载可以通过__get, __set, and __call几个特殊方法来进行. 当Zend引擎试图访问一个成员并没有找到时,PHP将会调用这些方法.

在下面的例子中,__get和__set代替所有对属性变量数组的访问. 如果必要,你可以实现任何类型你想要的过滤. 例如,脚本可以禁止设置属性值, 在开始时用一定的前缀或包含一定类型的值.

__call方法说明了你如何调用未经定义的方法. 你调用未定义方法时,方法名和方法接收的参数将会传给__call方法, PHP传递__call的值返回给未定义的方法.
复制内容到剪贴板
代码:
<?php
class Overloader {
         private $properties = array();

         function __get($property_name)
          {
                  if (isset($this -> properties[$property_name]))
                           {
                           return($this -> properties[$property_name]);
                          }
                  else
                           {
                           return(null);
                          }
                 }

         function __set($property_name, $value)
          {
                  $this -> properties[$property_name] = $value;
                 }

         function __call($function_name, $args)
          {
                  print("Invoking $function_name() n");
                  print("Arguments: ");
                  print_r($args);

                  return(true);
                 }
}
$o = new Overloader();
// invoke __set() 给一个不存在的属性变量赋值,激活__set()
$o -> dynaProp = "Dynamic Content";
// invoke __get() 激活__get()
print($o -> dynaProp . "n");
// invoke __call() 激活__call()
$o -> dynaMethod("Leon", "Zeev");

?>

TOP

PHP 5.0对象模型深度探索之绑定

除了限制访问,访问方式也决定哪个方法将被子类调用或哪个属性将被子类访问. 函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定。

  在计算机语言中有两种主要的绑定方式—静态绑定和动态绑定。静态绑定发生于数据结构和数据结构间,程序执行之前. 静态绑定发生于编译期,因此不能利用任何运行期的信息。它针对函数调用与函数的主体,或变量与内存中的区块。因为PHP是一种动态语言,它不使用静态绑定。但是可以模拟静态绑定。

  动态绑定则针对运行期产生的访问请求,只用到运行期的可用信息。在面向对象的代码中,动态绑定意味着决定哪个方法被调用或哪个属性被访问,将基于这个类本身而不基于访问范围。

  Public和protected成员的动作类似于PHP的前几个版本中函数的动作,使用动态绑定。这意味着如果一个方法访问一个在子类中被覆写的类成员,并是一个子类的实例,子类的成员将被访问(而不是访问父类中的成员)。

  看例子6.10. 这段代码输出” Hey! I am Son.” 因为当PHP调用getSalutation,是一个Son的实例,是将Father中的salutation覆写而来. 如果salutation是public的,PHP将产生相同的结果. 覆写方法的操作很类似。在Son中,对于identify的调用绑定到那个方法。

  即使在子类中访问方式被从protected削弱成public, 动态绑定仍然会发生. 按照访问方式使用的原则,增强对于类成员的访问限制是不可能的,所以把访问方式从public改变成protected不可能进行。

Listing 6.10 Dynamic binding 动态绑定
复制内容到剪贴板
代码:
<?php
class Father {
        protected $salutation = "Hello there!"; //问候
       
        public function getSalutation() {
                print("$this->salutationn");
                $this -> identify();
        }

        protected function identify() {
                print("I am Father.
n");
        }
} ;

class Son extends Father {
        protected $salutation = "Hey!"; //父类中的protected $salutation 被覆写
       
        protected function identify() { // 父类中的protected identify() 被覆写
                print("I am Son.
n");
        }
} ;

$obj = new Son();
$obj -> getSalutation(); //输出Hey! I am Son.

?>
//注: 在子类中没有覆写getSalutation(),但实际上仍然存在一个getSalutation().这个类中的$salutation和identify()
//与Son子类的实例中的getSalutation()方法动态绑定,所以调用Son的实例的getSalutation()方法,
//将调用Son类中的成员salutation及identify(),而不是父类中的成员salutation及identify().

Private成员只存在于它们所在的类内部. 不像public和protected成员那样,PHP模拟静态绑定. 看例子6.11。它输出”Hello there! I am Father.”,尽管子类覆写了salutation的值,脚本将this->salutation和当前类Father绑定. 类似的原则应用于private方法identify()。

  Listing 6.11 Binding and private members
复制内容到剪贴板
代码:
<?php
class Father {
        private $salutation = "Hello there!";

        public function getSalutation() {
                print("$this->salutationn");
                $this -> identify();
        }

        private function identify() {
                print("I am Father.
n");
        }
}

class Son extends Father {
        private $salutation = "Hey!";
        private function identify() {
                print("I am Son.
n");
        }
}

$obj = new Son();
$obj -> getSalutation(); //输出Hello there! I am Father.


?>
动态绑定的好处是允许继承类来改变父类的行为,同时可以保持父类的接口和功能,看例子6.12. 由于使用了动态绑定,在deleteUser中被调用的isAuthorized的version 可以由对象的类型来确定。如果是一个普通的user,PHP调用User::isAuthorized会返回FALSE.如果是一个 AuthorizedUser的实例,PHP调用AuthorizedUser::isAuthorized,将允许deleteUser顺利执行。

  //haohappy注:用一句话说清楚,就是对象类型与方法,属性绑定. 调用一个父类与子类中都存在的方法或访问一个属性时,会先判断实例属于哪种对象类型,再调用相应的类中的方法和属性.

  Listing 6.12 动态绑定的好处
复制内容到剪贴板
代码:
<?php
class User { // 用户
        protected function isAuthorized() { // 是否是验证用户
                return(false);
        }

        public function getName() { // 获得名字
                return($this -> name);
        }

        public function deleteUser($username) { // 删除用户
                if (!$this -> isAuthorized()) {
                        print("You are not authorized.
n");
                        return(false);
                }
                // delete the user
                print("User deleted.
n");
        }
}

class AuthorizedUser extends User { // 认证用户
        protected function isAuthorized() { // 覆写isAuthorized()
                return(true);
        }
}

$user = new User;
$admin = new AuthorizedUser;
// not authorized
$user -> deleteUser("Zeev");
// authorized
$admin -> deleteUser("Zeev");

?>
为什么private的类成员模拟静态绑定? 为了回答这个问题, 你需要回忆一下为什么需要有private成员.什么时候用它们来代替protected成员是有意义的?

  private成员只有当你不想让子类继承改变或特殊化父类的行为时才用到,这种情况比你想像的要少,通常来说,一个好的对象分层结构应当允许绝大多数功能被子类特殊化,改进,或改变—这是面向对象编程的基础之一。一定的情况下需要private方法或变量,例如当你确信你不想允许子类改变父类中的某个特定的部份。

TOP

PHP 5.0对象模型深度探索之访问方式

PHP5的访问方式允许限制对类成员的访问。这是在PHP5中新增的功能,但在许多面向对象语言中都早已存在。有了访问方式,才能开发一个可靠的面向对象应用程序,并且构建可重用的面向对象类库。

  像C++和Java一样,PHP有三种访问方式:public,private和protected. 对于一个类成员的访问方式,可以是其中之一. 如果你没有指明访问方式,默认地访问方式为public. 你也可以为静态成员指明一种访问方式,将访问方式放在static关键字之前(如public static).

  Public成员可以被毫无限制地访问.类外部的任何代码都可以读写public属性. 你可以从脚本的任何地方调用一个public方法。在PHP的前几个版本中,所有方法和属性都是public, 这让人觉得对象就像是结构精巧的数组。

  Private(私有)成员只在类的内部可见,你不能在一个private属性所在的类方法之外改变或读取它的值。同样地,只有在同一个类中的方法可以调用一个private方法,继承的子类也不能访问父类中的private 成员。

  要注意,类中的任何成员和类的实例都可以访问private成员。看例子6.8,equals方法将两个widget进行比较.==运算符比较同一个类的两个对象,但这个例子中每个对象实例都有唯一的ID.equals方法只比较name和price。注意equals方法如何访问另一个 Widget实例的private属性,Java和C都允许这样的操作。

Listing 6.8 Private members
复制内容到剪贴板
代码:
<?php
class Widget {
         private $name;
         private $price;
         private $id;

         public function __construct($name, $price)
          {
                  $this -> name = $name;
                  $this -> price = floatval($price);
                  $this -> id = uniqid();
                 }
         // checks if two widgets are the same 检查两个widget是否相同
         public function equals($widget)
          {
                  return(($this -> name == $widget -> name)AND ($this -> price == $widget -> price));
                 }
}
$w1 = new Widget('Cog', 5.00);
$w2 = new Widget('Cog', 5.00);
$w3 = new Widget('Gear', 7.00);
// TRUE
if ($w1 -> equals($w2)) {
         print("w1 and w2 are the same n");
}
// FALSE
if ($w1 -> equals($w3)) {
        print("w1 and w3 are the same n");
}
// FALSE, == includes id in comparison
if ($w1 == $w2) { // 不等,因为ID不同
                print("w1 and w2 are the same n");
}

?>
如果你对面向对象编程不熟悉,你可能想知道用private成员的目的是什么. 你可以回忆一下封装和耦合的想法,这在本章开头我们有讨论过。Private成员有助于封装数据,他们可以隐藏在一个类内部而不被类外部的代码接触到,同时他们还有助于实现松散的耦合. 如果数据结构外的代码不能直接访问内部属性,那么就不会产生一个隐性的关联性。

  当然,大部分private属性仍然可以被外部代码共享. 解决方法是用一对public方法,一个是get(获取属性的值),另一个是set(设置属性的值)。构造函数也接受属性的初始值. 这使得成员间的交流通过一个狭窄的,经过良好限定的接口来进行. 这也提供改变传递给方法的值的机会,注意在例子6.8中,构造函数如何强制使price成为一个float数(floadval())。

  Protected(受保护的) 成员能被同个类中的所有方法和继承出的类的中所有方法访问到。Public属性有违封装的精神,因为它们允许子类依赖于一个特定的属性来书写, protected方法则不会带来这方面的担忧,一个使用protected方法的子类需要很清楚它的父类的结构才行。

  例子6.9由例子6.8改进而得到,包含了一个Widget的子类Thing。注意Widget现在有一个叫作getName的protected方法. 如果Widget的实例试图调用protected方法将会出错: $w1->getName()产生了一个错误,但子类Thing中的getName方法可以调用这个protected方法.当然对于证明Widget::getName方法是protected,这个例子显得过于简单,在实际情况下,使用protected方法要依赖于对对象的内部结构的理解。

  Listing 6.9 Protected members
复制内容到剪贴板
代码:
<?php
class Widget {
         private $name;
         private $price;
         private $id;

         public function __construct($name, $price)
          {
                  $this -> name = $name;
                  $this -> price = floatval($price);
                  $this -> id = uniqid();
                 }

         // checks if two widgets are the same
         public function equals($widget)
          {
                  return(($this -> name == $widget -> name)AND($this -> price == $widget -> price));
                 }

         protected function getName()
          {
                  return($this -> name);
                 }
}

class Thing extends Widget {
         private $color;

         public function setColor($color)
          {
                  $this -> color = $color;
                 }

         public function getColor()
          {
                  return($this -> color);
                 }

         public function getName()
          {
                  return(parent :: getName());
                 }
}

$w1 = new Widget('Cog', 5.00);
$w2 = new Thing('Cog', 5.00);
$w2 -> setColor('Yellow');
// TRUE (still!) 结果仍然为真
if ($w1 -> equals($w2)) {
         print("w1 and w2 are the same n");
}
// print Cog 输出 Cog
print($w2 -> getName());

?>
一个子类可能改变通过覆写父类方法来改变方法的访问方式,尽管如此,仍然有一些限制。如果你覆写了一个public类成员,他子类中必须保持 public. 如果你覆写了一个protected成员,它可保持protected或变成public.Private成员仍然只在当前类中可见。声明一个与父类的 private成员同名的成员将简单地在当前类中建立一个与原来不同的成员. 因此,在技术上你不能覆写一个private成员。

  Final关键字是限制访问成员方法的另一个方法。子类不能覆写父类中标识为final的方法,Final关键字不能用于属性。

TOP

PHP 5.0对象模型深度探索之类的静态成员

类的静态成员与一般的类成员不同: 静态成员与对象的实例无关,只与类本身有关。他们用来实现类要封装的功能和数据,但不包括特定对象的功能和数据,静态成员包括静态方法和静态属性。

  静态属性包含在类中要封装的数据,可以由所有类的实例共享。实际上,除了属于一个固定的类并限制访问方式外,类的静态属性非常类似于函数的全局变量。

  我们在下例中使用了一个静态属性Counter :: $count。它属于Counter类,而不属于任何Counter的实例。你不能用 this来引用它,但可以用self或其它有效的命名表达。在例子中,getCount方法返回self :: $count,而不是Counter :: $count。

  静态方法则实现类需要封装的功能,与特定的对象无关. 静态方法非常类似于全局函数. 静态方法可以完全访问类的属性,也可以由对象的实例来访问,不论访问的限定语是否是什么.

  在前文的例子中,getCount是一个普通的方法,用->来调用. PHP建立一个this变量,尽管方法没有使用到.但是,getCount不属于任何对象.在有些情况下,我们甚至希望在不存在有效的对象时调用它,那么就应该使用静态方法. PHP将不在静态方法内部建立this变量,即使你从一个对象中调用它们.

  例子6.7由6.3改变getCount为静态方法而来. Static关键字不能阻止一个实例用->运算符来调用getCount,但PHP将不在方法内部建立this变量.如果你使用this->来调用,将会出错.

//6.3例指第四节--构造函数和析构函数中的例子(参看前文),通过两个例子的比较,你可以很好掌握
//static方法与普通方法之间的区别.

  你可以写一个方法通过判断this是否建立来显示是否它被静态地或者非静态地调用. 当然,如果你用了static 关键字,不管它怎样被调用,这个方法总是静态的.

  你的类也可以定义常量属性,不需要使用public static,只需要用const关键字即可. 常量属性总是静态的.它们是类的属性,而不是实例化该类的对象的属性.
复制内容到剪贴板
代码:
<?php
class Counter {
         private static $count = 0;
         const VERSION = 2.0;

         function __construct()
          {
                  self :: $count++;
                 }

         function __destruct()
          {
                  self :: $count--;
                 }

         static function getCount()
          {
                  return self :: $count;
                 }
} ;
// 创建一个实例,则__construct()将执行
$c = new Counter();
// 输出 1
print(Counter :: getCount() . "n");
// 输出类的版本属性
print("Version used: " . Counter :: VERSION . "n");

?>
[ 本帖最后由 aming 于 2008-4-6 10:32 编辑 ]

TOP

PHP 5.0对象模型深度探索之对象复制

PHP5中的对象模型通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP定义了一个特殊的方法,称为__clone. 像__construct和__destruct一样,前面有两个下划线。

  默认地,用__clone方法将建立一个与原对象拥有相同属性和方法的对象. 如果你想在克隆时改变默认的内容,你要在__clone中覆写(属性或方法)。

  克隆的方法可以没有参数,但它同时包含this和that指针(that指向被复制的对象)。如果你选择克隆自己,你要小心复制任何你要你的对象包含的信息,从that到this,如果你用__clone来复制,PHP不会执行任何隐性的复制,下面显示了一个用系列序数来自动化对象的例子:
复制内容到剪贴板
代码:
<?php
class ObjectTracker { // 对象跟踪器
         private static $nextSerial = 0;
         private $id;
         private $name;

         function __construct($name) // 构造函数
          {
                  $this -> name = $name;
                  $this -> id = ++self :: $nextSerial;
                 }

         function __clone() // 克隆
          {
                  $this -> name = "Clone of $this->name";
                  $this -> id = ++self :: $nextSerial;
                 }

         function getId() // 获取id属性的值
          {
                  return($this -> id);
                 }

         function getName() // 获取name属性的值
          {
                  return($this -> name);
                 }
}

$ot = new ObjectTracker("Zeev's Object");
$ot2 = clone$ot;
// 输出: 1 Zeev's Object
print($ot -> getId() . " " . $ot -> getName() . "");
// 输出: 2 Clone of Zeev's Object
print($ot2 -> getId() . " " . $ot2 -> getName() . "");

?>

TOP

PHP 5.0 中的对象重载技术研究

一、简介

  很幸运,PHP 5.0中引入了对象重载技术。本文将探讨对于方法__call(),__set()以及__get()进行重载的可能性。在对重载理论作简单介绍后,我们将通过两个例子直奔主题:第一例,实现持续存储类;第二例,找到一种实现动态的getter/setter的方法。

  二、什么是对象重载?

  在PHP中谈到对象重载时,我们要区别两种类型:

  ·方法重载

  ·属性重载

  在方法重载的情况下,我们要定义一个魔术般的方法__call(),它将实现一个在相应类中对未定义方法的笼统调用。只有当你想存取类中未定义的方法时,这种笼统方法才会被调用。在没有方法重载的情况下,下面的例子将导致PHP显示一条致命错误信息:Call to undefined method ThisWillFail::bar() in/some/directory/example.php on line 9并流产程序的执行:
复制内容到剪贴板
代码:
<?php
class ThisWillFail {
          public function foo() {
                   return "Hello World!";
                  }
         }
 $class = new ThisWillFail;
 $class->bar();

?>
借助方法重载的帮助,代码能够捕获到这种调用且能够体面地给以处理。

  属性重载与方法重载差不多。这种情况下,类把读/写操作重定向(亦可称代理)到类的属性,这些属性在类中没有显式定义。这里的专门方法是__set() 和__get()。依赖于错误报告等级,PHP翻译器通常在存取一个未定义的属性时,或者发出一个通知,或者推迟一下并潜在地定义这个变量。而如果使用属性重载,翻译器却可以在设置一个未定义的属性时调用__set(),而在存取一个未定义的属性值时调用__get()。
综上所述,利用重载技术可以实现在象用PHP这样的动态语言进行时软件开发时间的大大缩短。

  理论介绍至此,下面分析具体编码。

  三、持续性存储类举例

  下列代码,通过使用属性重载技术,用少于50行的PHP代码实现了上面所提到的持续性存储类。术语persistable意味着类可以从一个数据结构中描述一个元素,并保持与底端存储系统的同步。用编码的解释就是,外部代码可以使用类来实现从一个数据库表中选定一行。这样,在程序运行时,可以直接存取类的属性来操纵该行中的元素(读/取)。在脚本结束时,PHP将负责把更新的行数据回送到数据库中去。

  精心研读下面代码将有助于你理解什么是属性重载。
复制内容到剪贴板
代码:
<?php
// 装入PEAR的 <a href="http://pear.php.net/package/DB/">DB package</a>
 require_once "DB.php";
 class Persistable {
          private $data = array();
          private $table = "users";
          public function __construct($user) {
                   $this -> dbh = DB :: Connect("mysql://user:password@localhost/database");
                   $query = "SELECT id, name, email, country FROM " .    $this -> table . " WHERE name = ?";
                   $this -> data = $this -> dbh -> getRow($query, array($user),
                           DB_FETCHMODE_ASSOC);
                  }
          public function __get($member) {
                   if (isset($this -> data[$member])) {
                            return $this -> data[$member];
                           }
                  }
          public function __set($member, $value) {
                   // dataset的ID是只读的
                   if ($member == "id") {
                            return;
                           }
                   if (isset($this -> data[$member])) {
                            $this -> data[$member] = $value;
                           }
                  }
          public function __destruct() {
                   $query = "UPDATE " . $this -> table . " SET name = ?,
   email = ?, country = ? WHERE id = ?";
                   $this -> dbh -> query($query, $this -> name, $this -> email,
                           $this -> country, $this -> id);
                  }
         }
 $class = new Persistable("Martin Jansen");
 $class -> name = "John Doe";
 $class -> country = "United States";
 $class -> email = "john@example.com";

?>
你遇到的第一个问题可能是__construct(),这是PHP 5中引入的新的构造器方法。在PHP 4时代,构造器总是与它们的类名相匹配。在PHP 5中已不再是这样。你不需要对构造器方法有过多的了解,除了调用它可以创建一个类的实例外;并注意到,这里使用了一个参数 - 执行一个基于此参数的数据库。此构造器把查询结果赋值给类属性$data。

  接下来,程序定义了两个特别的方法__get()和__set()。你应该对它们早已熟悉:__get()用于读取未定义的属性值,__set()用于修改未定义的属性值。

  这意味着无论什么时候从持续性存储类中读取/写入一个未定义的属性,由这些专门方法来负责管理在属性数组变量$data中的信息,而不是直接改变类的属性(切记:变量$data包含着来自于数据库中的一行!)。

  类中的最后一个方法是__construct()的对立者- 析构器__destruct()。PHP在"脚本关闭阶段"调用析构器,典型地这是在PHP脚本执行快要结束的时候。析构器把来自于$data属性的信息写回到数据库中去。这正是前面同步(synchronization )术语的含义。

  你可能早已注意到,这里的代码使用了PEAR的数据库抽象层包(database abstraction layer package)。其实这无所谓,通过别的方式与数据库通讯也一样能说明本文的主题。

  如果你细心观察,会发现该持续性存储类的描述比较简单。例子中仅涉及了一个数据库表,而没有考虑更复杂的数据模型,如使用LEFT JOIN和其它复杂的数据库操作技术。然而你不必受此约束,借助于属性重载,你可以使用你自己理想的数据库模型。只需要加入少许代码,你即可以在该持续性存储类中运用复杂的数据库特性。

  还存在一个小问题 - 当在析构器中查询失败时并没有引入错误处理机制。是析构器的天性导致在这种情况下不可能显示相应的错误信息,因为构建HTML标志常常在PHP调用构析器之前就已经结束了。

  为解决这个问题,你可以把__destruct()重命名为象saveData()这样的名字并在调用脚本的某处手工执行这一方法。这对于类的持续性存储的概念并没有任何改变;仅是多写几行代码而已。作为选择,你还可以在析构器中使用函数error_log()来记录下属于系统范围的错误记录文件中的错误信息。

  属性重载的工作机制就是这样。下面我们讨论一下方法重载。

TOP

PHP 5.0对象模型深度探索之属性和方法

一个对象实例的属性是变量,就像PHP的其他变量一样。但是你必须使用->运算符来引用它们。不需要在属性前使用美元符$。

  可以联用->,如果一个对象的属性包含了一个对象,你可以使用两个->运算符来得到内部对象的属性. 你甚至可以用双重引用的字符串来放置这些表达式. 下面的例子中,对象House中的属性room包含了一组Room对象。

  访问方法和访问属性类似。->运算符用来指向实例的方法. 在下面的中调用getLastLogin就是。方法执行起来和类外的函数几乎相同.

  如果一个类从另一类中继承而来,父类中的属性和方法将在子类中都有效,即使在子类中没有声明. 像以前提到过的,继承是非常强大的. 如果你想访问一个继承的属性,你只需要像访问基类自己的属性那样引用即可,使用::运算符.
复制内容到剪贴板
代码:
<?php
class Room {
         public $name;

         function __construct($name = "unnamed")
          {
                  $this ->name = $name;
                 }
}

class House {
         // array of rooms
         public $room;
}
// create empty house
$home = new house;
// add some rooms
$home ->room[] = new Room("bedroom");
$home ->room[] = new Room("kitchen");
$home ->room[] = new Room("bathroom");
// show the first room of the house
print($home ->room[0] ->name);

?>
PHP有两个特殊的命名空间:parent命名空间指向父类,self命名空间指向当前的类。下面的例子中显示了如何用parent命名空间来调用父类中的构造函数. 同时也用self来在构造函数中调用另一个类方法。
复制内容到剪贴板
代码:
<?php
class Animal { // 动物
         public $blood; //热血or冷血属性
         public $name;
         public function __construct($blood, $name = null)
          {
                  $this -> blood = $blood;
                  if ($name)
                           {
                           $this -> name = $name;
                          }
                 }
}

class Mammal extends Animal { // 哺乳动物
         public $furColor; //皮毛颜色
         public $legs;

         function __construct($furColor, $legs, $name = null)
          {
                  parent :: __construct("warm", $name);
                  $this -> furColor = $furColor;
                  $this -> legs = $legs;
                 }
}

class Dog extends Mammal {
         function __construct($furColor, $name)
          {
                  parent :: __construct($furColor, 4, $name);

                  self :: bark();
                 }

         function bark()
          {
                  print("$this->name says 'woof!'");
                 }
}

$d = new Dog("Black and Tan", "Angus");

?>
对于对象的成员来是这样调用的:如果你需要在运行时确定变量的名称,你可以用$this->$Property这样的表达式。 如果你想调用方法,可以用$obj->$method()。

  你也可以用->运算符来返回一个函数的值,这在PHP以前的版本中是不允许的。例如,你可以写一个像这样的表达式: $obj->getObject()->callMethod()。这样避免了使用一个中间变量,也有助于实现某些设计模式,如Factory模式。

TOP

PHP 5.0对象模型深度探索之构造和析构

如果你在一个类中声明一个函数,命名为__construct,这个函数将被当成是一个构造函数并在建立一个对象实例时被执行。清楚地说,__是两个下划线。就像其它任何函数一样,构造函数可能有参数或者默认值. 你可以定义一个类来建立一个对象并将其属性全放在一个语句(statement)中。

你也可以定义一个名为__destruct的函数,PHP将在对象被销毁前调用这个函数. 它称为析构函数.

继承是类的一个强大功能。一个类(子类/派生类)可以继承另一类(父类/基类)的功能. 派生类将包含有基类的所有属性和方法,并可以在派生类中加上其他属性和方法。你也可以覆写基类的方法和属性。就像前文中显示的,你可以用extends关键字来继承一个类。

你可能想知道构造函数是如何被继承的。当它们和其它方法一起被继承时,他们不会在创建对象时被执行。

如果你需要这个功能,你需要用第二章提到的::运算符. 它允许你指向一块命名空间. parent指向父类命名空间,你可以用parent::__construct来调用父类的构造函数。

一些面向对象语言在类之后命名构造函数。PHP的前几个版本也是如此,到现在这种方法仍然有效.也就是:如果你把一个类命名为Animal并且在其中建立一个命名也是Animal的方法,则这个方法就是构造函数.如果一个类的同时拥有__construt构造函数和与类名相同的函数,PHP将把 __construct看作构造函数。这使得用以前的PHP版本所写的类仍然可以使用. 但新的脚本(PHP5)应当使用__construct。

PHP的这种新的声明构造函数的方法可以使构造函数有一个独一无二的名称,无论它所在的类的名称是什么。这样你在改变类的名称时,就不需要改变构造函数的名称。

你可能在PHP中给构造函数一个像其它类方法一样的访问方式。访问方式将会影响从一定范围内实例化对象的能力。这允许实现一些固定的设计模式,如Singleton模式。

析构函数,相反于构造函数。PHP调用它们来将一个对象从内存中销毁。默认地,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源。析构函数允许你在使用一个对象之后执行任意代码来清除内存。

当PHP决定你的脚本不再与对象相关时,析构函数将被调用. 在一个函数的命名空间内,这会发生在函数return的时候. 对于全局变量,这发生于脚本结束的时候. 如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值. 通常将变量赋值勤为NULL或者调用unset。

下面的例子中,计算从类中实例化的对象的个数. Counter类从构造函数开始增值,在析构函数减值。

一旦你定义了一个类,你可以用new来建立一个这个类的实例. 类的定义是设计图,实例则是放在装配线上的元件. New需要类的名称,并返回该类的一个实例。如果构造函数需要参数,你应当在new后输入参数。
复制内容到剪贴板
代码:
<?php
class Counter {
         private static $count = 0;

         function __construct()
          {
                  self :: $count++;
                 }

         function __destruct()
          {
                  self :: $count--;
                 }

         function getCount()
          {
                  return self :: $count;
                 }
}
// 建立第一个实例
$c = new Counter();
// 输出1
print($c ->getCount() . "n");
// 建立第二个实例
$c2 = new Counter();
// 输出2
print($c ->getCount() . "n");
// 销毁实例
$c2 = null;
// 输出1
print($c ->getCount() . "n");

?>

TOP

PHP 5.0对象模型深度探索之定义一个类

当你声明一个类,你需要列出对象应有的所有变量和所有函数—被称为属性和方法。列表1中显示了一个类的构成. 注意在大括号({})内你只能声明变量或者函数。列表2中显示了如何在一个类中定义三个属性和两个方法。
复制内容到剪贴板
代码:
class Name extends Another Class {
 Access Variable Declaration
 Access Function Declaration
}
复制内容到剪贴板
代码:
<?php
// 定义一个跟踪用户的类
class User {
         // 属性
         public $name;
         private $password, $lastLogin;

         // 方法
         public function __construct($name, $password)
          {
                  $this -> name = $name;
                  $this -> password = $password;
                  $this -> lastLogin = time();
                  $this -> accesses++;
                 }

         // 获取最后访问的时间
         function getLastLogin()
          {
                  return(date("M d Y", $this -> lastLogin));
                 }
}
// 创建一个对象的实例
$user = new User("Leon", "sdf123");
// 获取最后访问的时间
print($user -> getLastLogin() . "
n");
// 打印用户名
print("$user->name n");

?>
当你声明属性,你不需要指明数据类型。变量可能是整型,字符串或者是另一个对象,这取决于实际情况。在声明属性时增加注释是一个好主意,标记上属性的含义和数据类型。

  当你声明一个方法,你所做的和在类外部定义一个函数是一样的。方法和属性都有各自的命名空间。这意味着你可以安全地建立一个与类外部函数同名的方法,两者不会冲突。例如,一个类中可以定义一个名为date()的方法。但是你不能将一个方法命名为PHP的关键字,如for或者while。

  类方法可能包含PHP中所谓的type hint. Type hint 是另一个传递参数给方法的类的名字. 如果你的脚本调用方法并传递一个不是类的实例的变量,PHP将产生一个”致命(fatal)错误” 。你可能没有给其它类型给出type hint,就像整型,字符串,或者布尔值. 在书写的时候, type hint是否应当包含数组类型仍存在争议。

  Type hint是测试函数参数或者运算符的实例的数据类型的捷径. 你可能总是返回这个方法. 确认你强制让一个参数必须是哪种数据类型,如整型. 列表3确保编译类只产生Widget的实例。
复制内容到剪贴板
代码:
<?php
// 组件
class Widget {
        public $name = 'none';
        public $created = false;
}
// 装配器
class Assembler {
        public function make(Widget $w) {
                print("Making $w->name
n");
                $w -> created = true;
        }
}
// 建立一个组件对象
$thing = new Widget;
$thing -> name = 'Gadget';
// 装配组件
Assembler :: make($thing);

?>
 除了传递参数的变量外,方法含有一个特殊的变量. 它代表类的个别实例. 你应当用这个来指向对象的属性和其它方法.一些面向对象的语言假设一个不合格的变量提交给本地属性,但在PHP中方法的任何变量只是在方法的一定范围内. 注意在User类的构造函数中这个变量的使用(3.1.2).

  PHP在属性和方法声明前定义一个访问限定语,如public,private和protected. 另外,你可以用”static”来标记一个成员. 你也可以在类中声明常量。以后稍后会有不同访问方式的相关讨论。

  你可以在一行中列出相同访问方式的几个属性,用逗号来分隔它们. 在3.1.2中,User类有两个private属性--$password和$lastLogin。

TOP

 12 12
发新话题