注意
本教程介绍如何使用MongoDB扩展中的 MongoDB\ BSON \Persistable 接口实施自定义数据类型。请考虑改用编解码器,将MongoDB持久性逻辑与业务逻辑分离开来。有关示例,请参阅 编解码器教程。
MongoDB PHP扩展和库在序列化和反序列化时支持自定义类。这可能有用的一个示例是,如果您想要存储日期/时间信息,并保留 PHP 的 DateTimeImmutable 类随时间点存储的时区域信息。
该扩展在与服务器通信时将PHP变量(包括对象)序列化为BSON ,并在从服务器接收数据时将BSON反序列化回PHP变量。
可以通过实现MongoDB\ BSON\Persistable 接口来影响行为。如果一个类实现了此接口,则在序列化时将调用 bsonSerialize 方法。此方法负责返回一个大量或 stdClass对象,以转换为BSON并存储在数据库中。稍后,该数据将用于在从数据库读取时重建对象。
我们以LocalDateTime
类示例。此类包装MongoDB\ BSON\UTCDateTime数据类型和时区域。
/* Custom document class that stores a UTCDateTime and time zone and also * implements the UTCDateTime interface for portability. */ class LocalDateTime implements \MongoDB\BSON\Persistable, \MongoDB\BSON\UTCDateTimeInterface { private $utc; private $tz; public function __construct($milliseconds = null, \DateTimeZone $timezone = null) { $this->utc = new \MongoDB\BSON\UTCDateTime($milliseconds); if ($timezone === null) { $timezone = new \DateTimeZone(date_default_timezone_get()); } $this->tz = $timezone; }
由于它实现了MongoDB\ BSON\Persistable 接口,因此该类需要实现bsonSerialize 和 bsonUnserialize 方法。在 bsonSerialize 方法中,我们返回一个大量,其中包含需要保留的两个值:自纪元以来的时间点(以毫秒为单位),由MongoDB\ BSON\UTCDateTime对象表示,以及一个包含 Olson 时区域标识符的字符串:
public function bsonSerialize() { return [ 'utc' => $this->utc, 'tz' => $this->tz->getName(), ]; }
该扩展还会向文档添加一个__pclass
字段,并将其存储在数据库中。 该字段包含PHP类名,以便在反序列化时扩展知道使用哪个类来重新创建存储的对象。
从数据库读取文档时,扩展会检测是否存在__pclass
字段,然后执行MongoDB\ BSON\Persistable::bsonUnserialize 方法,该方法负责恢复对象的原始状态。
在下面的代码中,我们确保utc
和tz
字段中的数据是正确的时间,然后将它们的值分配给这两个私有属性。
public function bsonUnserialize(array $data) { if ( ! isset($data['utc']) || ! $data['utc'] instanceof \MongoDB\BSON\UTCDateTime) { throw new Exception('Expected "utc" field to be a UTCDateTime'); } if ( ! isset($data['tz']) || ! is_string($data['tz'])) { throw new Exception('Expected "tz" field to be a string'); } $this->utc = $data['utc']; $this->tz = new \DateTimeZone($data['tz']); }
您可能已经注意到,该类还实现了MongoDB\ BSON\UTCDateTimeInterface 接口。此接口定义了MongoDB\ BSON\UTCDateTime 类的两个非构造函数方法。
建议现有BSON类的包装器实现各自的接口(即MongoDB\ BSON\UTCDateTimeInterface),以便包装器对象可以在与其原始未包装版本相同的上下文中使用。还建议您始终针对接口(即MongoDB\ BSON\UTCDateTimeInterface)进行类型提示,而永远不要针对具体类(即MongoDB\ BSON\UTCDateTime),因为这会阻止包装对象被方法接受。
在新的toDateTime
方法中,我们返回一个设立了本地时区域的 DateTime对象,而不是MongoDB\ BSON \UTCDateTime 通常在其返回值中使用的 UTC 时区域。
public function toDateTime() { return $this->utc->toDateTime()->setTimezone($this->tz); } public function __toString() { return (string) $this->utc; } }
定义类后,现在就可以在文档中使用它了。 下面的代码段演示了从LocalDateTime
对象到 BSON,再返回到LocalDateTime
的往返。
$bson = MongoDB\BSON\Document::fromPHP(['date' => new LocalDateTime]); $document = $bson->toPHP(); var_dump($document); var_dump($document->date->toDateTime());
其输出:
object(stdClass)#1 (1) { ["date"]=> object(LocalDateTime)#2 (2) { ["utc":"LocalDateTime":private]=> object(MongoDB\BSON\UTCDateTime)#3 (1) { ["milliseconds"]=> string(13) "1533042443716" } ["tz":"LocalDateTime":private]=> object(DateTimeZone)#4 (2) { ["timezone_type"]=> int(3) ["timezone"]=> string(13) "Europe/London" } } } object(DateTime)#5 (3) { ["date"]=> string(26) "2018-07-31 14:07:23.716000" ["timezone_type"]=> int(3) ["timezone"]=> string(13) "Europe/London" }
将 Olson 时区标识符存储在单独的字段中也可以很好地与 MongoDB 的聚合框架配合使用,该框架允许根据特定时区进行日期操作、格式化和查询。