perlucas/phpwrapper

该软件包最新版本(dev-master)没有可用的许可证信息。

一组简单的PHP包装器,用于扩展功能

dev-master 2020-04-10 00:41 UTC

This package is auto-updated.

Last update: 2024-09-11 01:47:26 UTC


README

Generic badge Generic badge

此库提供了一组通用包装类,可用于在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

由于所有包装对象的方法都可以从包装器类中调用,我们可以将包装器用作包装对象的代理。

设置获取包装器

SetterGetterWrapperWrapper 类的扩展,可用于访问包装对象的设置器和获取器。

我们可以像使用 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,必须在包装器或包装对象类的任何地方定义名为 getNamegetname 的方法,或者必须在包装对象上定义 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类让我们能够在内存中加载昂贵的对象时实现延迟加载模式。GhostWrapperSetterGetterWrapper的扩展。

让我们按照以下方式定义我们的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来设置包装对象可以执行它。当定义方法wrapeeGetterLoadedloadWrapeeGetter时,同样的想法也适用。以下是一个示例

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

这种基本实现让我们可以与产品包装器一起工作,而不必担心对象数据的加载。默认情况下,对象的设置器始终是加载的(因为我们总是可以设置对象的属性值),但是如果有需要,我们可以覆盖方法wrapeeSetterLoadedloadWrapeeSetter

以下我们可以看到幽灵包装器的作用

$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方法上触发的。