为此,所谓的水合器的概念非常普遍。特别是对于 DateTime 类型的数据字段,很可能会在其他模型中重复,使用 hydrator 是有意义的。这使逻辑远离模型并使用可重用的代码。
为什么选择保湿剂?
如果您正在考虑将您的整个开发过程与另一个数据库系统一起使用,或者您只是想通过数据模型保持最大可能的灵活性,那么 Hydrator 非常有意义。如前所述,水合器可以确保模型没有任何逻辑。此外,水合器可用于表示灵活的场景。此外,仅基于 PHP PDO 类提供的可能性的数据水合非常薄弱。只需将数据库中的原始数据作为数组处理,然后让 hydrator 发挥作用。
水合器背后的逻辑
每个水化器可以对要水化的对象的属性应用不同的策略。这些水化器策略可用于在实际水化之前更改模型中的值或执行其他功能。
<?php
declare(strict_types=1);
namespace Marcel\Hydrator;
interface HydratorInterface
{
public function hydrate(array $data, object $model): object;
public function extract(object $model): array;
}
上面显示的接口应该在每个 hydrator 类中实现。每个 hydrator 都应该有一个 hydrate 方法,它将给定的数据数组推送到给定的模型中。此外,必须有一个转折点,即 extract 方法,它将模型中的数据提取到一个数组中。
<?php
declare(strict_types=1);
namespace Marcel\Hydrator\Strategy;
interface StrategyInterface
{
public function hydrate($value);
}
这两个接口都定义了 hydrator 和 hydrator 策略必须带来的方法。这些接口主要用于实现对象识别的安全类型提示。
补水策略
<?php
declare(strict_types=1);
namespace Marcel\Hydrator\Strategy;
use DateTime;
class DateTimeStrategy implements StrategyInterface
{
public function hydrate($value)
{
$value = new DateTime($value);
return $value;
}
}
这个简单的 hydrator 策略示例只不过是获取原始值并用它初始化一个新的 DateTime 对象。为了简单说明,我这里省略了错误处理。在生产中,此时您应该始终检查 DateTime 对象是否真的被创建并且没有产生任何错误。
水合器
<?php
declare(strict_types=1);
namespace Marcel\Hydrator;
use Marcel\Hydrator\Strategy\StrategyInterface;
use ReflectionClass;
class ClassMethodsHydrator implements HydratorInterface
{
protected ?ReflectionClass $reflector = null;
protected array $strategies = [];
public function hydrate(array $data, object $model): object
{
if ($this->reflector === null) {
$this->reflector = new ReflectionClass($model);
}
foreach ($data as $key => $value) {
if ($this->hasStrategy($key)) {
$strategy = $this->strategies[$key];
$value = $strategy->hydrate($value);
}
$methodName = 'set' . ucfirst($key);
if ($this->reflector->hasMethod($methodName)) {
$model->{$methodName}($value);
}
}
return $model;
}
public function extract(object $model): array
{
return get_object_vars($model);
}
public function addStrategy(string $name, StrategyInterface $strategy): void
{
$this->strategies[$name] = $strategy;
}
public function hasStrategy(string $name): bool
{
return array_key_exists($name, $this->strategies);
}
}
此 hydrator 要求您的模型具有 getter 和 setter 方法。在这个例子中,它至少要求每个属性都有一个对应的setter方法。为避免错误并正确命名方法,应从数据库中过滤列名的名称。通常,数据库中的名称用下划线标注,模型的属性遵循驼峰式约定。 (例如:“fancy_string”=>“setFancyString”)
示例
class User
{
protected int $id;
protected DateTime $birthday;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
public function getBirtday(): DateTime
{
return $this->birthday;
}
public function setBirthday(DateTime $birthday): void
{
$this->birthday = $birthday;
}
}
$data = [
'id' => 1,
'birthday' => '1979-12-19',
];
$hydrator = new ClassMethodsHydrator();
$hydrator->addStrategy('birthday', new DateTimeStrategy());
$user = $hydrator->hydrate($data, new User());
这段代码的结果将是一个很好的水合用户模型。
object(Marcel\Model\User)#3 (2) {
["id":protected] => int(1)
["birthday":protected] => object(DateTime)#5 (3) {
["date"] => string(26) "1979-12-19 00:00:00.000000"
["timezone_type"] => int(3)
["timezone"] => string(13) "Europe/Berlin"
}