Skip to content

Commit 880ab58

Browse files
Merge pull request #76 from neo4j-php/pack_structures
pack structures
2 parents c1173ef + 2aafece commit 880ab58

18 files changed

+567
-60
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"minimum-stability": "stable",
1010
"require": {
1111
"php": ">=7.1.0",
12-
"ext-mbstring": "*"
12+
"ext-mbstring": "*",
13+
"ext-bcmath": "*"
1314
},
1415
"require-dev": {
1516
"phpunit/phpunit": ">=7.5.0"

phpunit.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<directory suffix=".php">src/PackStream</directory>
1515
<directory suffix=".php">src/protocol</directory>
1616
<directory suffix=".php">src/helpers</directory>
17+
<directory suffix=".php">src/structures</directory>
1718
<file>src/Bolt.php</file>
1819
</whitelist>
1920
</filter>

src/PackStream/v1/Packer.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
use Bolt\PackStream\IPacker;
66
use Bolt\error\PackException;
77
use Generator;
8+
use Bolt\structures\{
9+
IStructure,
10+
Relationship,
11+
Date,
12+
Time,
13+
LocalTime,
14+
DateTime,
15+
DateTimeZoneId,
16+
LocalDateTime,
17+
Duration,
18+
Point2D,
19+
Point3D
20+
};
821

922
/**
1023
* Class Packer of PackStream version 1
@@ -20,6 +33,19 @@ class Packer implements IPacker
2033
private const LARGE = 65536;
2134
private const HUGE = 4294967295;
2235

36+
private $structuresLt = [
37+
Relationship::class => [0x52, 'packInteger', 'packInteger', 'packInteger', 'packString', 'packMap'],
38+
Date::class => [0x44, 'packInteger'],
39+
Time::class => [0x54, 'packInteger', 'packInteger'],
40+
LocalTime::class => [0x74, 'packInteger'],
41+
DateTime::class => [0x46, 'packInteger', 'packInteger', 'packInteger'],
42+
DateTimeZoneId::class => [0x66, 'packInteger', 'packInteger', 'packString'],
43+
LocalDateTime::class => [0x64, 'packInteger', 'packInteger'],
44+
Duration::class => [0x45, 'packInteger', 'packInteger', 'packInteger', 'packInteger'],
45+
Point2D::class => [0x58, 'packInteger', 'packFloat', 'packFloat'],
46+
Point3D::class => [0x59, 'packInteger', 'packFloat', 'packFloat', 'packFloat']
47+
];
48+
2349
/**
2450
* Pack message with parameters
2551
* @param $signature
@@ -77,6 +103,8 @@ private function p($param): string
77103
$output .= chr($param ? 0xC3 : 0xC2);
78104
} elseif (is_string($param)) {
79105
$output .= $this->packString($param);
106+
} elseif ($param instanceof IStructure) {
107+
$output .= $this->packStructure($param);
80108
} elseif (is_array($param)) {
81109
$keys = array_keys($param);
82110
if (count($param) == 0 || count(array_filter($keys, 'is_int')) == count($keys)) {
@@ -222,4 +250,34 @@ private function packList(array $arr): string
222250
return $output;
223251
}
224252

253+
/**
254+
* @param IStructure $structure
255+
* @return string
256+
* @throws PackException
257+
*/
258+
private function packStructure(IStructure $structure): string
259+
{
260+
$reflection = new \ReflectionClass($structure);
261+
$properties = $reflection->getProperties();
262+
$cnt = count($properties);
263+
264+
if (!array_key_exists(get_class($structure), $this->structuresLt)) {
265+
throw new PackException('Provided structure as parameter is not supported');
266+
}
267+
268+
$arr = $this->structuresLt[get_class($structure)];
269+
if (count($arr) != $cnt + 1) {
270+
throw new PackException('Invalid amount of structure properties');
271+
}
272+
273+
$output = pack('C', 0b10110000 | $cnt);
274+
$output .= chr(array_shift($arr));
275+
foreach ($arr as $i => $method) {
276+
$properties[$i]->setAccessible(true);
277+
$output .= $this->{$method}($properties[$i]->getValue($structure));
278+
}
279+
280+
return $output;
281+
}
282+
225283
}

src/structures/Date.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* @author Michal Stefanak
1212
* @link https://github.com/neo4j-php/Bolt
13+
* @link https://7687.org/packstream/packstream-specification-1.html#date---structure
1314
* @package Bolt\structures
1415
*/
1516
class Date implements IStructure
@@ -39,6 +40,6 @@ public function days(): int
3940

4041
public function __toString(): string
4142
{
42-
return date('Y-m-d', strtotime($this->days . ' days'));
43+
return gmdate('Y-m-d', strtotime('+' . $this->days . ' days +0000', 0));
4344
}
4445
}

src/structures/DateTime.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*
1515
* @author Michal Stefanak
1616
* @link https://github.com/neo4j-php/Bolt
17+
* @link https://7687.org/packstream/packstream-specification-1.html#datetime---structure
1718
* @package Bolt\structures
1819
*/
1920
class DateTime implements IStructure
@@ -65,7 +66,7 @@ public function nanoseconds(): int
6566
}
6667

6768
/**
68-
* specifies the offset in minutes from UTC
69+
* specifies the offset in seconds from UTC
6970
* @return int
7071
*/
7172
public function tz_offset_seconds(): int
@@ -75,11 +76,9 @@ public function tz_offset_seconds(): int
7576

7677
public function __toString(): string
7778
{
78-
$dt = \DateTime::createFromFormat('U', $this->seconds, new \DateTimeZone('UTC'));
79-
$fraction = new \DateInterval('PT0S');
80-
$fraction->f = $this->nanoseconds / 1000000000;
81-
$dt->add($fraction);
82-
$dt->setTimezone(new \DateTimeZone(sprintf('+%04d', $this->tz_offset_seconds / 3600 * 100)));
83-
return $dt->format('Y-m-d\TH:i:s.uP');
79+
$ts = bcadd($this->seconds - $this->tz_offset_seconds, bcdiv($this->nanoseconds, 10e8, 6), 6);
80+
return \DateTime::createFromFormat('U.u', $ts, new \DateTimeZone('UTC'))
81+
->setTimezone(new \DateTimeZone(sprintf("%+'05d", $this->tz_offset_seconds / 3600 * 100)))
82+
->format('Y-m-d\TH:i:s.uP');
8483
}
8584
}

src/structures/DateTimeZoneId.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*
1515
* @author Michal Stefanak
1616
* @link https://github.com/neo4j-php/Bolt
17+
* @link https://7687.org/packstream/packstream-specification-1.html#datetimezoneid---structure
1718
* @package Bolt\structures
1819
*/
1920
class DateTimeZoneId implements IStructure
@@ -75,11 +76,8 @@ public function tz_id(): string
7576

7677
public function __toString(): string
7778
{
78-
$dt = \DateTime::createFromFormat('U', $this->seconds, new \DateTimeZone('UTC'));
79-
$fraction = new \DateInterval('PT0S');
80-
$fraction->f = $this->nanoseconds / 1000000000;
81-
$dt->add($fraction);
82-
$dt->setTimezone(new \DateTimeZone($this->tz_id));
83-
return $dt->format('Y-m-d\TH:i:s.uP');
79+
$timestamp = bcadd($this->seconds(), bcdiv($this->nanoseconds, 10e8, 6), 6);
80+
return \DateTime::createFromFormat('U.u', $timestamp, new \DateTimeZone($this->tz_id))
81+
->format('Y-m-d\TH:i:s.u') . '[' . $this->tz_id . ']';
8482
}
8583
}

src/structures/Duration.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* @author Michal Stefanak
1212
* @link https://github.com/neo4j-php/Bolt
13+
* @link https://7687.org/packstream/packstream-specification-1.html#duration---structure
1314
* @package Bolt\structures
1415
*/
1516
class Duration implements IStructure
@@ -84,6 +85,30 @@ public function nanoseconds(): int
8485

8586
public function __toString(): string
8687
{
87-
return 'P' . $this->months . 'M' . $this->days . 'DT' . ($this->seconds + $this->nanoseconds / 1000000000) . 'S';
88+
$output = 'P';
89+
$years = floor($this->months / 12);
90+
if (!empty($years))
91+
$output .= $years . 'Y';
92+
if (!empty($this->months % 12))
93+
$output .= ($this->months % 12) . 'M';
94+
if (!empty($this->days))
95+
$output .= $this->days . 'D';
96+
97+
$time = '';
98+
$hours = floor($this->seconds / 3600);
99+
if (!empty($hours))
100+
$time .= $hours . 'H';
101+
$minutes = floor($this->seconds % 3600 / 60);
102+
if (!empty($minutes))
103+
$time .= $minutes . 'M';
104+
105+
$seconds = rtrim(bcadd($this->seconds % 3600 % 60, bcdiv($this->nanoseconds, 10e8, 6), 6), '0.');
106+
if (!empty($seconds))
107+
$time .= $seconds . 'S';
108+
109+
if (!empty($time))
110+
$output .= 'T' . $time;
111+
112+
return $output;
88113
}
89114
}

src/structures/LocalDateTime.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* @author Michal Stefanak
1212
* @link https://github.com/neo4j-php/Bolt
13+
* @link https://7687.org/packstream/packstream-specification-1.html#localdatetime---structure
1314
* @package Bolt\structures
1415
*/
1516
class LocalDateTime implements IStructure
@@ -55,11 +56,8 @@ public function nanoseconds(): int
5556

5657
public function __toString(): string
5758
{
58-
$dt = \DateTime::createFromFormat('U', $this->seconds, new \DateTimeZone('UTC'));
59-
$fraction = new \DateInterval('PT0S');
60-
$fraction->f = $this->nanoseconds / 1000000000;
61-
$dt->add($fraction);
62-
return $dt->format('Y-m-d\TH:i:s.u');
59+
return \DateTime::createFromFormat('U.u', bcadd($this->seconds, bcdiv($this->nanoseconds, 10e8, 6), 6))
60+
->format('Y-m-d\TH:i:s.u');
6361
}
6462

6563
}

src/structures/LocalTime.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*
1111
* @author Michal Stefanak
1212
* @link https://github.com/neo4j-php/Bolt
13+
* @link https://7687.org/packstream/packstream-specification-1.html#localtime---structure
1314
* @package Bolt\structures
1415
*/
1516
class LocalTime implements IStructure
@@ -39,6 +40,7 @@ public function nanoseconds(): int
3940

4041
public function __toString(): string
4142
{
42-
return date('H:i:s', floor($this->nanoseconds / 1000000000)) . '.' . $this->nanoseconds % 1000000000;
43+
return \DateTime::createFromFormat('U.u', bcdiv($this->nanoseconds, 10e8, 6))
44+
->format('H:i:s.u');
4345
}
4446
}

src/structures/Node.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*
99
* @author Michal Stefanak
1010
* @link https://github.com/neo4j-php/Bolt
11+
* @link https://7687.org/packstream/packstream-specification-1.html#node---structure
1112
* @package Bolt\structures
1213
*/
1314
class Node implements IStructure

0 commit comments

Comments
 (0)