dkd / php-populate
为 PHP 类提供的特性,允许属性从 JSON 等来源中通过获取器填充,但使用单个方法
Requires (Dev)
- phpunit/phpunit: @stable
- squizlabs/php_codesniffer: @stable
This package is not auto-updated.
Last update: 2024-09-14 16:29:26 UTC
README
这是一个简单的特性,用于 PHP 类,允许通过对象的获取器和设置器以单个方法来填充和导出属性。
PopulateTrait
非常棒,因为它
- 简单且快速 - 不使用代码生成等
- 使用获取器和设置器 - 总是尊重你的公共 API
- 不添加重载方法 - 避免魔法行为
- 双向的 - 填充和导出属性
- 可以进行映射 - 输入的属性名可以与目标的不同
- 可以选择性地填充和导出属性,带或不带映射
用法
实现如下
namespace MyNamespace; use Dkd\Populate\PopulateTrait; use Dkd\Populate\PopulateInterface; /** * My populatable class */ class MyClassWhichUsesPopulateTrait implements PopulateInterface { use PopulateTrait; /** * @var string */ protected $name; /** * @var boolean */ protected $before = true; /** * @var boolean */ protected $after = false; /** * @param string $name */ public function setName($name) { $this->name = $name } /** * @return string */ public function getName() { return $this->name; } /** * @param boolean $before */ public function setIsBefore($before) { $this->before = $before; } /** * @return boolean */ public function isBefore() { return $this->before; } /** * @param boolean $after */ public function setIsAfter($after) { $this->after = $after; } /** * @return boolean */ public function isAfter() { return $this->after; } }
PopulateInterface
是可选的 - 它被包含进来,以便你可以在方法中使用类型提示,并在填充时能够传递其他实例作为源数据。如果你计划将你的对象作为数据源传递给其他对象,你 必须 要么实现此接口 要么 在将它们传递给 populate()
之前手动调用 $source->exportGettableProperties();
以提取值。以下所有示例都说明了 $source
实现了该接口的使用场景。
在类中实现此特性,允许以下填充数据
$source = someFakeMethodWhichRetrievesAnObjectImplementingPopulate(); $copy = new MyClassWhichUsesPopulateTrait(); // copy all properties: $copy->populate($source); // copy properties from one property name to another: $copy->populate($source, array('before' => 'after')); // ...$copy's "after" property now contains $source's "before" property value // copy only a few properties: $copy->populate($source, array('before'), TRUE); // ...$copy was only populated with the value of $source's "before" property.
以下导出数据到简单的数组
$source = someFakeMethodWhichRetrievesAnObjectImplementingPopulate(); // export all properties: $array = $source->exportGettableProperties(); // export all properties but export "before" value as "after" key in array: $array = $source->exportGettableProperties(array('before' => 'after')); // export only some properties and map their names to other names: $array = $source->exportGettableProperties(array('before' => 'after'), TRUE); // export only some properties but keep their names: $array = $source->exportGettableProperties(array('before'), TRUE);
请注意,在两个示例中,当不需要属性名映射时,数组充当名称列表,而不是映射。两种方法都会智能地检测你是否使用了数值索引数组或字符串索引数组,并相应地执行;因为没有任何 PHP 版本允许类属性名称为整数值。当遇到数值索引数组时,使用 array_combine
创建一个新的属性名 "map"(用引号括起来),其中输入作为键和值。
因此,两个数组 array('before' => 'before')
和 array('before')
在填充或导出时具有相同的意义。
使用对象填充:引用还是克隆?
当你填充一个对象且输入数据包含其他对象实例时,你可能期望对每个对象使用 clone
以填充克隆而不是引用。
然而,Populate
的默认行为是 以引用填充任何对象值。如果你希望对象被克隆,请切换到替代方法
$copy->populateWithClones($source);
这将导致在设置到 $copy
之前,所有对象类型的值都被克隆。
如果你的要求是某些属性以克隆填充,而其他属性以引用填充,有两种方法可以达到这个目标
// Solution #1: populate everything with references, then overwrite // those properties that require clones by calling the alternative // cloning method with a list of property names and the "only map // specified properties" flag set to `true`: $copy->populate($source); $copy->populateWithClones($source, array('cloneProperty1', 'cloneProperty2'), true); // Solution #2: the reverse of the above; populate everything with // clones then overwrite those properties requiring references: $copy->populateWithClones($source); $copy->populate($source, array('referenceProperty1', 'referenceProperty2'), true);
自然地,你会选择在第二次填充操作中需要传递最少属性名的那个方法。
另一种更明显的方法超出了 Populate
的范围,因为它会手动后处理值以使用克隆/引用,但它同样有效
// Manual way #1: populate with references then clone selected properties:
$copy->populate($source);
$copy->setCloneProperty1(clone $copy->getCloneProperty1());
$copy->setCloneProperty2(clone $copy->getCloneProperty2());
// Manual way #2: populate with clones then overwrite selected properties
// with references to the original input value:
$copy->populateWithClones($source);
$copy->setReferenceProperty1($source['referenceProperty1']);
$copy->setReferenceProperty2($source['referenceProperty2']);
你可以使用最适合你应用程序设计的方法。 Populate
提供了适合通用用途的方法,但不会以任何方式阻止你使用现有的设置器和获取器。
常见错误
当使用其他对象作为源对象填充对象时,如果这些其他对象包含嵌套对象,则Populate
执行的克隆操作不会递归进行。为了控制每个对象的克隆行为,建议在需要控制的每个对象上定义__clone
方法。
请参阅官方PHP文档,克隆章节
支持的输入类型
populate()
方法支持以下输入类型
- 任何类型的
PopulateTrait
实现对象实例 - 使用属性=>值语法的字符串索引数组
- 任何
Iterator
+ArrayAccess
组合都完全支持 - 仅部分支持没有
Iterator
的ArrayAccess
请注意,最终类型,一个实现ArrayAccess
但不实现Iterator
的对象,仅在使用手动属性名列表以及“仅选择属性”标志时受支持。
支持的getter和setter
PopulateTrait
不关心你设置的值类型,但它确实关心你的getter和setter是如何构建的。
为了正确使用你的getter和setter,PopulateTrait
需要满足以下条件
- getter必须没有强制参数
- setter必须没有超过一个强制参数
- 有效的getter名称是(如果属性是
name
) getName
isName
getIsName
name
- 有效的setter名称是(如果属性是
name
) setName
setIsName
附加的is
样式方法名称仅在标准方法不存在时尝试 - 这是为了适应标准的PHP模式,使用is
来命名布尔类型的getter/setter。getter也可以只是属性名。这可能对属性名为$isFinished
的情况很有用。
特殊要求
每个属性只能有一个getter。如果发现多个getter,则抛出异常。这是为了确保属性的getter始终以相同的方式工作。
处理PopulateTrait的错误
PopulateTrait
方法抛出的错误以Dkd\Populate\Exception
的形式抛出,具有唯一的异常代码,用于错误类型,这些类型是:
1422045180
当给populate()
提供无效的输入类型时
并使用更具体的Dkd\Populate\AccessException
来处理
1422021211
当尝试填充没有setter的属性时1422021212
当尝试导出没有getter的属性时1424776261
当找到多个属性访问方法时
通常捕获Exception
类型
try { $populatable = new MyClassWhichUsesPopulateTrait(); $populatable->populate($valuesWithPotentialErrors); } catch (\Dkd\Populate\AccessException $error) { // attempt at illegal access - could be a security issue. } catch (\Dkd\Populate\Exception $error) { // general failure - do something about it. }
边缘情况
由于非常紧凑且不使用反射或任何类型的配置,Populate
无法处理一些边缘情况。
边缘情况和它们的解决方案
Populate
不会设置实现Trait的类的公共属性。为了解决这个问题,请手动处理你的公共属性。如果你的属性已经是公共的,那么你根本不需要PopulateTrait
提供的逻辑。Populate
不支持getter和setter的重载方法。唯一的“解决方案”是实现正确的getter和setter。Populate
不是递归的;populate()
不会在子属性值上调用。为了解决这个问题,在传递给populate()
之前转换任何值。你可以创建递归方法,这些方法消耗PopulateInterface
实例,并手动使用export()
递归这些值,然后再调用populate()
。这也意味着在调用populateWithClones
时,任何嵌套对象实例都不会自动克隆。当使用数组作为源时,你的自定义递归方法应该负责克隆。当使用对象作为源时,你可以实现__clone
方法来控制每个对象的克隆行为。Populate
只能使用正确的PHP类型提示来决定预期的输入类型。这意味着你的@param
注解将不会被考虑。执行额外输入参数验证的设置器仍然可能抛出错误。换句话说,Populate()
不会尝试捕获它调用的任何方法中出现的错误。为了解决这个问题,请在将输入数据传递给populate()
之前手动删除任何无效的值。