perlucas / phpwrapper
一组简单的PHP包装器,用于扩展功能
This package is auto-updated.
Last update: 2024-09-11 01:47:26 UTC
README
此库提供了一组通用包装类,可用于在PHP中实现适配器、代理、空对象、数据传输对象或延迟加载设计模式。以下是关于每个包装类的说明。
内容
包装器
Wrapper
类是最基本的包装器实现,提供增强包装对象功能的方法。我们必须扩展此类才能将其用于包装对象(即将被包装的对象)。
首先,我们必须通过返回包装对象类名来实现 getWrapeeClass
方法。这是必要的,因为包装器实例化会检查包装对象是否是该类的子类型(或类型)。
use SimpleWrapper\Wrapper; class ProductWrapper extends Wrapper { protected function getWrapeeClass() { return "Product"; } }
我们的包装器现在可以使用了,我们只需要一个将被包装的 Product
实例。假设我们的 Product
类定义如下
class Product { protected $id; protected $name; protected $price; public function __construct($id, $name, $price) { $this->id = $id; $this->name = $name; $this->price = $price; } public function getName() {return $this->name;} public function getPrice() {return $this->price;} public function calculateCost($q) {return $this->price * $q;} }
然后我们可以从包装器实例访问产品的这些方法
$product = new Product(11, "Apple", 2.30); $productWrapper = new ProductWrapper($product); echo $productWrapper->getName(); // Apple echo $productWrapper->calculateCost(10); // 23
我们可以通过在包装器上重新实现产品方法来扩展产品的功能
use SimpleWrapper\Wrapper; class ProductWrapper extends Wrapper { protected function getWrapeeClass() { return "Product"; } public function calculateCost($q) { return "The cost is: " . $this->wrapee->calculateCost($q); } }
这段额外的代码会导致以下结果
$product = new ProductWrapper(new Product(11, "Apple", 2.30)); echo $product->getName(); // Apple echo $product->calculateCost(10); // The cost is: 23
由于所有包装对象的方法都可以从包装器类中调用,我们可以将包装器用作包装对象的代理。
设置获取包装器
SetterGetterWrapper
是 Wrapper
类的扩展,可用于访问包装对象的设置器和获取器。
我们可以像使用 Wrapper
类一样使用它
use SimpleWrapper\SetterGetterWrapper; class ProductWrapper extends SetterGetterWrapper { protected function getWrapeeClass() { return "Product"; } public function calculateCost($q) { return "The cost is: " . $this->wrapee->calculateCost($q); } } ////////////////////////// $product = new ProductWrapper(new Product(11, "Apple", 2.30)); echo $product->getName(); // Apple echo $product->calculateCost(10); // The cost is: 23
假设我们的 Product
类有一些如下设置的属性
class Product { protected $id; protected $name; protected $price; public function __construct($id, $name, $price) { $this->id = $id; $this->name = $name; $this->price = $price; } public function getName() {return $this->name;} public function getPrice() {return $this->price;} public function setName($n) {$this->name = $n;} public function setPrice($p) {$this->price = $p;} public function calculateCost($q) {return $this->price * $q;} }
然后,我们可以通过执行 setName()
或 getName
来从包装器访问它们。相反,SetterGetterWrapper
介绍了以下用于访问包装对象属性的功能
$product = new ProductWrapper(new Product(11, "Apple", 2.30)); echo $product->name; // Apple echo $product->calculateCost(10); // The cost is: 23 $product->price = 5.60; echo $product->price; // 5.60
要访问属性 $product->name
,必须在包装器或包装对象类的任何地方定义名为 getName
或 getname
的方法,或者必须在包装对象上定义 name
属性为 public
。这也适用于执行 $product-><another_property> = <a_value>;
。
多实例包装器
正如我们可以推断出的,MultipleInstancesWrapper
可以用于包装/扩展包装对象的职能。此类可以用作 数据传输对象,因为它允许我们访问可以在多个对象上定义的方法。
让我们定义以下类
class Person { protected $id; protected $name; protected $age; public function __construct($id, $name, $age) { $this->id = $id; $this->name = $name; $this->age = $age; } public function getName(){return $this->name;} public function getAge(){return $this->age;} public function isOld(){return $this->age > 60;} }
我们可以直接使用 MultipleInstancesWrapper
,因为它不是 abstract
类。然而,更好的做法是扩展它,这样我们就可以提供额外的代码来增强包装类的通用能力
use SimpleWrapper\MultipleInstancesWrapper; class PPWrapper extends MultipleInstancesWrapper { /** * @Override */ protected validateWrapees(array $objects) { parent::validateWrapees($objects); // do extra validation, e.g: validate that the first object is a Person object } }
PPWrapper
代表 "人和产品包装器"。我们可以按如下方式使用它
$wrapper = new PPWrapper( new Person(111, 'Peter', 45), new Product(222, 'Apple', 5.60) ); echo $wrapper->calculateCost(100); // 560 echo $wrapper->isOld(); // false
如果我们尝试访问 getName
方法会发生什么?该方法在两个包装对象上都定义了,因此我们需要通过在包装器上实现 getProviderClassForMethod
来指定哪个类将作为该方法的提供者
use SimpleWrapper\MultipleInstancesWrapper; class PPWrapper extends MultipleInstancesWrapper { protected function getProviderClassForMethod($method) { if ($method === 'getName') { return 'Product'; } } } /////////////////// $wrapper = new PPWrapper( new Person(111, 'Peter', 45), new Product(222, 'Apple', 5.60) ); echo $wrapper->calculateCost(100); // 560 echo $wrapper->isOld(); // false echo $wrapper->getName(); // Apple
如果没有实现 getProviderClassForMethod
,包装器就会在定义了该方法的第一个对象上调用该方法。
通用空对象
GenericNullObject
类为实现 空对象 设计模式提供了基础。首先,我们需要通过定义 getClassName
方法来扩展此类
use SimpleWrapper\GenericNullObject; class NullProduct extends GenericNullObject { protected function getClassName() { return "Product"; } }
现在我们已经准备好使用空产品实现了。默认情况下,所有方法都返回 null
$product = new NullProduct(); echo $product->getName(); // null echo $product->getPrice(); // null
我们可以通过覆盖 GenericNullObject
上定义的 getDefaultReturnType
方法来为所有方法定义默认返回类型
use SimpleWrapper\GenericNullObject; class NullProduct extends GenericNullObject { protected function getClassName(){return "Product";} protected function getDefaultReturnType(){return false;} } /// ------------------- $product = new NullProduct(); echo $product->getName(); // false echo $product->getPrice(); // false
此外,我们可以在GenericNullObject
提供的getReturnTypes
方法中,为每个方法名更具体地定义具体的返回类型。在那里,我们必须定义一个如下所示的关联数组
use SimpleWrapper\GenericNullObject; class NullProduct extends GenericNullObject { protected function getClassName(){return "Product";} protected function getReturnTypes() { return [ 'getName' => '', 'getPrice' => 0 ]; } } /// ------------------- $product = new NullProduct(); echo $product->getName(); // '' echo $product->getPrice(); // 0
幽灵包装器
GhostWrapper
类让我们能够在内存中加载昂贵的对象时实现延迟加载模式。GhostWrapper
是SetterGetterWrapper
的扩展。
让我们按照以下方式定义我们的Product
类
class Product { protected $id; protected $name = null; protected $price = null; protected $vendors = null; public function __construct($id) {$this->id = $id;} public function getName() {return $this->name;} public function getPrice() {return $this->price;} public function fetchVendors() {return $this->vendors;} public function loadProductProperties() { // load properties from a data repository using the id } public function loadVendors() { // load vendors from data repository using the id } }
Product
类是一个幽灵。在特定时间,它可以处于三种状态之一:已完全加载、部分加载或未加载。通过使用GhostWrapper
类,我们可以为不应意识到加载机制的产品类用户提供一些透明度。
我们必须实现各种方法以扩展GhostWrapper
类。如果包装对象可以执行方法,则wrapeeMethodLoaded
必须返回true
。如果不能,则调用loadWrapeeMethod
来设置包装对象可以执行它。当定义方法wrapeeGetterLoaded
和loadWrapeeGetter
时,同样的想法也适用。以下是一个示例
use SimpleWrapper\GhostWrapper; class ProductGhost extends GhostWrapper { protected function getWrapeeClass(){return 'Product';} protected function wrapeeMethodLoaded($method, $args) { if ($method === 'fetchVendors') return $this->wrapee->fetchVendors() !== null; if ($method === 'getName') return $this->wrapeeGetterLoaded('name'); if ($method === 'getPrice') return $this->wrapeeGetterLoaded('price'); return true; } protected function loadWrapeeMethod($method, $args) { if ($method === 'fetchVendors') return $this->wrapee->loadVendors(); return $this->wrapee->loadProductProperties(); } protected function wrapeeGetterLoaded($property) { return $this->wrapee->{$property} !== null; } protected function loadWrapeeGetter($property) { return $this->wrapee->loadProductProperties(); } }
这种基本实现让我们可以与产品包装器一起工作,而不必担心对象数据的加载。默认情况下,对象的设置器始终是加载的(因为我们总是可以设置对象的属性值),但是如果有需要,我们可以覆盖方法wrapeeSetterLoaded
和loadWrapeeSetter
。
以下我们可以看到幽灵包装器的作用
$product = new ProductGhost( new Product(111) ); echo $product->name; // this line causes the loading of the name and price print_r($product->fetchVendors()); // this line causes the loading of the vendors
产品仅通过对象的ID进行实例化。对$product->name
的调用会导致调用wrapeeGetterLoaded('name')
,它返回false
。由于它是false,因此调用loadWrapeeGetter('name')
方法。这导致加载名称和价格属性。
当调用fetchVendors
时,相同的逻辑适用。在这种情况下,加载供应商是在loadWrapeeMethod
方法上触发的。