为此,所谓的hydrate概念是非常普遍的,特别是对于类型为DateTime的数据字段,在其他模型中可能会重复出现,使用hydrate是有意义的。这将使逻辑与模型分离,并与可重用代码一起工作。
为什么需要Hydrators?
如果您考虑使用另一个数据库系统进行整个开发,或者如果您只想保持数据模型的最大灵活性,hydrate就是绝佳选择。如前所述,hydrate可以确保模型不带任何逻辑。此外,hydrate可用于表示灵活的场景。此外,仅基于PHP PDO类提供的可能性来hydration数据是很弱的。只需处理数组形式的数据库中的原始数据,然后让hydrate完成魔法即可。
Hydrators背后的逻辑
每个hydrate都可以对要hydrate的对象的属性应用不同的策略。这些hydrate策略可用于在实际hydration之前更改值或执行其他函数。
<?php
declare(strict_types=1);
namespace Marcel\Hydrator;
interface HydratorInterface
{
public function hydrate(array $data, object $model): object;
public function extract(object $model): array;
}
每个水合器类都应该实现上面显示的接口。每个水合器都应该有一个hydrate方法,将给定的数据数组推送到给定的模型中。此外还必须有一个反向操作,即extract方法,它将从模型中提取数据并转换为数组。
<?php
declare(strict_types=1);
namespace Marcel\Hydrator\Strategy;
interface StrategyInterface
{
public function hydrate($value);
}
这两个接口定义了水合器和水合器策略必须具备的方法。这些接口主要用于安全类型提示,以便识别对象。
水合器策略
<?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;
}
}
这个简单的数据注入策略只是将原始值取出并用它初始化一个新的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);
}
}
这个水合器要求你的模型有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"
}
Doctrine
这样的DBAL来为您完成此操作。 - Daniel W.