Skip to content

Commit 1e332fc

Browse files
Merge pull request #93 from neo4j-php/pack_yield
Support iterator as query parameter
2 parents 209d0e7 + 62d4c0e commit 1e332fc

18 files changed

+607
-158
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ cert/
55
/vendor/
66
*.lock
77
*.cache
8+
phpunit.dev.xml
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Bolt\PackStream;
4+
5+
/**
6+
* Class PackDictionaryGenerator
7+
* @author Michal Stefanak
8+
* @link https://github.com/neo4j-php/Bolt
9+
* @package Bolt\PackStream
10+
*/
11+
interface IPackDictionaryGenerator extends \Countable, \Iterator
12+
{
13+
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Bolt\PackStream;
4+
5+
/**
6+
* Class PackListGenerator
7+
* @author Michal Stefanak
8+
* @link https://github.com/neo4j-php/Bolt
9+
* @package Bolt\PackStream
10+
*/
11+
interface IPackListGenerator extends \Iterator, \Countable
12+
{
13+
14+
}

src/PackStream/IPacker.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace Bolt\PackStream;
44

5-
use Generator;
6-
75
/**
86
* Interface IPacker
97
*
@@ -16,7 +14,7 @@ interface IPacker
1614
/**
1715
* @param $signature
1816
* @param mixed ...$params
19-
* @return Generator
17+
* @return iterable
2018
*/
21-
public function pack($signature, ...$params): Generator;
19+
public function pack($signature, ...$params): iterable;
2220
}

src/PackStream/v1/Packer.php

Lines changed: 86 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Bolt\PackStream\IPacker;
66
use Bolt\error\PackException;
7-
use Generator;
7+
use Bolt\PackStream\{IPackListGenerator, IPackDictionaryGenerator};
88
use Bolt\structures\{
99
IStructure,
1010
Date,
@@ -51,78 +51,86 @@ class Packer implements IPacker
5151
* Pack message with parameters
5252
* @param $signature
5353
* @param mixed ...$params
54-
* @return Generator
54+
* @return iterable
5555
* @throws PackException
5656
*/
57-
public function pack($signature, ...$params): Generator
57+
public function pack($signature, ...$params): iterable
5858
{
59-
$output = '';
60-
6159
$this->littleEndian = unpack('S', "\x01\x00")[1] === 1;
6260

6361
//structure
6462
$length = count($params);
6563
if ($length < self::SMALL) { //TINY_STRUCT
66-
$output .= pack('C', 0b10110000 | $length);
64+
yield pack('n', 1) . pack('C', 0b10110000 | $length);
6765
} elseif ($length < self::MEDIUM) { //STRUCT_8
68-
$output .= chr(0xDC) . pack('C', $length);
66+
yield pack('n', 2) . chr(0xDC) . pack('C', $length);
6967
} elseif ($length < self::LARGE) { //STRUCT_16
70-
$output .= chr(0xDD) . pack('n', $length);
68+
yield pack('n', 4) . chr(0xDD) . pack('n', $length);
7169
} else {
7270
throw new PackException('Too many parameters');
7371
}
7472

75-
$output .= chr($signature);
73+
yield pack('n', 1) . chr($signature);
7674

7775
foreach ($params as $param) {
78-
$output .= $this->p($param);
79-
}
80-
81-
//structure buffer
82-
$totalLength = mb_strlen($output, '8bit');
83-
$offset = 0;
84-
while ($offset < $totalLength) {
85-
$chunk = mb_strcut($output, $offset, 65535, '8bit');
86-
$chunkLength = mb_strlen($chunk, '8bit');
87-
$offset += $chunkLength;
88-
yield pack('n', $chunkLength) . $chunk;
76+
foreach ($this->p($param) as $packed) {
77+
$totalLength = mb_strlen($packed, '8bit');
78+
$offset = 0;
79+
while ($offset < $totalLength) {
80+
$chunk = mb_strcut($packed, $offset, 65535, '8bit');
81+
$chunkLength = mb_strlen($chunk, '8bit');
82+
$offset += $chunkLength;
83+
yield pack('n', $chunkLength) . $chunk;
84+
}
85+
}
8986
}
9087

9188
yield chr(0x00) . chr(0x00);
9289
}
9390

9491
/**
9592
* @param mixed $param
96-
* @return string
93+
* @return iterable
9794
* @throws PackException
9895
*/
99-
private function p($param): string
96+
private function p($param): iterable
10097
{
10198
switch (gettype($param)) {
10299
case 'integer':
103-
return $this->packInteger($param);
100+
yield from $this->packInteger($param);
101+
break;
104102
case 'double':
105-
return $this->packFloat($param);
103+
yield from $this->packFloat($param);
104+
break;
106105
case 'boolean':
107-
return chr($param ? 0xC3 : 0xC2);
106+
yield chr($param ? 0xC3 : 0xC2);
107+
break;
108108
case 'NULL':
109-
return chr(0xC0);
109+
yield chr(0xC0);
110+
break;
110111
case 'string':
111-
return $this->packString($param);
112+
yield from $this->packString($param);
113+
break;
112114
case 'array':
113115
if ($param === array_values($param)) {
114-
return $this->packList($param);
116+
yield from $this->packList($param);
115117
} else {
116-
return $this->packMap($param);
118+
yield from $this->packDictionary($param);
117119
}
120+
break;
118121
case 'object':
119122
if ($param instanceof IStructure) {
120-
return $this->packStructure($param);
123+
yield from $this->packStructure($param);
121124
} elseif ($param instanceof Bytes) {
122-
return $this->packByteArray($param);
125+
yield from $this->packByteArray($param);
126+
} elseif ($param instanceof IPackListGenerator) {
127+
yield from $this->packList($param);
128+
} elseif ($param instanceof IPackDictionaryGenerator) {
129+
yield from $this->packDictionary($param);
123130
} else {
124-
return $this->packMap((array)$param);
131+
yield from $this->packDictionary((array)$param);
125132
}
133+
break;
126134

127135
default:
128136
throw new PackException('Not recognized type of parameter');
@@ -131,155 +139,147 @@ private function p($param): string
131139

132140
/**
133141
* @param string $str
134-
* @return string
142+
* @return iterable
135143
* @throws PackException
136144
*/
137-
private function packString(string $str): string
145+
private function packString(string $str): iterable
138146
{
139147
$length = mb_strlen($str, '8bit');
140148

141149
if ($length < self::SMALL) { //TINY_STRING
142-
return pack('C', 0b10000000 | $length) . $str;
150+
yield pack('C', 0b10000000 | $length) . $str;
143151
} elseif ($length < self::MEDIUM) { //STRING_8
144-
return chr(0xD0) . pack('C', $length) . $str;
152+
yield chr(0xD0) . pack('C', $length) . $str;
145153
} elseif ($length < self::LARGE) { //STRING_16
146-
return chr(0xD1) . pack('n', $length) . $str;
154+
yield chr(0xD1) . pack('n', $length) . $str;
147155
} elseif ($length < self::HUGE) { //STRING_32
148-
return chr(0xD2) . pack('N', $length) . $str;
156+
yield chr(0xD2) . pack('N', $length) . $str;
149157
} else {
150158
throw new PackException('String too long');
151159
}
152160
}
153161

154162
/**
155163
* @param float $value
156-
* @return string
164+
* @return iterable
157165
*/
158-
private function packFloat(float $value): string
166+
private function packFloat(float $value): iterable
159167
{
160168
$packed = pack('d', $value);
161-
return chr(0xC1) . ($this->littleEndian ? strrev($packed) : $packed);
169+
yield chr(0xC1) . ($this->littleEndian ? strrev($packed) : $packed);
162170
}
163171

164172
/**
165173
* @param int $value
166-
* @return string
174+
* @return iterable
167175
* @throws PackException
168176
*/
169-
private function packInteger(int $value): string
177+
private function packInteger(int $value): iterable
170178
{
171179
if ($value >= -16 && $value <= 127) { //TINY_INT
172-
return pack('c', $value);
180+
yield pack('c', $value);
173181
} elseif ($value >= -128 && $value <= -17) { //INT_8
174-
return chr(0xC8) . pack('c', $value);
182+
yield chr(0xC8) . pack('c', $value);
175183
} elseif (($value >= 128 && $value <= 32767) || ($value >= -32768 && $value <= -129)) { //INT_16
176184
$packed = pack('s', $value);
177-
return chr(0xC9) . ($this->littleEndian ? strrev($packed) : $packed);
185+
yield chr(0xC9) . ($this->littleEndian ? strrev($packed) : $packed);
178186
} elseif (($value >= 32768 && $value <= 2147483647) || ($value >= -2147483648 && $value <= -32769)) { //INT_32
179187
$packed = pack('l', $value);
180-
return chr(0xCA) . ($this->littleEndian ? strrev($packed) : $packed);
188+
yield chr(0xCA) . ($this->littleEndian ? strrev($packed) : $packed);
181189
} elseif (($value >= 2147483648 && $value <= 9223372036854775807) || ($value >= -9223372036854775808 && $value <= -2147483649)) { //INT_64
182190
$packed = pack('q', $value);
183-
return chr(0xCB) . ($this->littleEndian ? strrev($packed) : $packed);
191+
yield chr(0xCB) . ($this->littleEndian ? strrev($packed) : $packed);
184192
} else {
185193
throw new PackException('Integer out of range');
186194
}
187195
}
188196

189197
/**
190-
* @param array $arr
191-
* @return string
198+
* @param array|IPackDictionaryGenerator $param
199+
* @return iterable
192200
* @throws PackException
193201
*/
194-
private function packMap(array $arr): string
202+
private function packDictionary($param): iterable
195203
{
196-
$output = '';
197-
$size = count($arr);
204+
$size = is_array($param) ? count($param) : $param->count();
198205

199206
if ($size < self::SMALL) { //TINY_MAP
200-
$output .= pack('C', 0b10100000 | $size);
207+
yield pack('C', 0b10100000 | $size);
201208
} elseif ($size < self::MEDIUM) { //MAP_8
202-
$output .= chr(0xD8) . pack('C', $size);
209+
yield chr(0xD8) . pack('C', $size);
203210
} elseif ($size < self::LARGE) { //MAP_16
204-
$output .= chr(0xD9) . pack('n', $size);
211+
yield chr(0xD9) . pack('n', $size);
205212
} elseif ($size < self::HUGE) { //MAP_32
206-
$output .= chr(0xDA) . pack('N', $size);
213+
yield chr(0xDA) . pack('N', $size);
207214
} else {
208215
throw new PackException('Too many map elements');
209216
}
210217

211-
foreach ($arr as $k => $v) {
212-
$output .= $this->p((string)$k); // The key names in a map must be of type String.
213-
$output .= $this->p($v);
218+
foreach ($param as $k => $v) {
219+
yield from $this->p((string)$k); // The key names in a map must be of type String.
220+
yield from $this->p($v);
214221
}
215-
216-
return $output;
217222
}
218223

219224
/**
220-
* @param array $arr
221-
* @return string
225+
* @param array|IPackListGenerator $param
226+
* @return iterable
222227
* @throws PackException
223228
*/
224-
private function packList(array $arr): string
229+
private function packList($param): iterable
225230
{
226-
$output = '';
227-
$size = count($arr);
231+
$size = is_array($param) ? count($param) : $param->count();
228232

229233
if ($size < self::SMALL) { //TINY_LIST
230-
$output .= pack('C', 0b10010000 | $size);
234+
yield pack('C', 0b10010000 | $size);
231235
} elseif ($size < self::MEDIUM) { //LIST_8
232-
$output .= chr(0xD4) . pack('C', $size);
236+
yield chr(0xD4) . pack('C', $size);
233237
} elseif ($size < self::LARGE) { //LIST_16
234-
$output .= chr(0xD5) . pack('n', $size);
238+
yield chr(0xD5) . pack('n', $size);
235239
} elseif ($size < self::HUGE) { //LIST_32
236-
$output .= chr(0xD6) . pack('N', $size);
240+
yield chr(0xD6) . pack('N', $size);
237241
} else {
238242
throw new PackException('Too many list elements');
239243
}
240244

241-
foreach ($arr as $v) {
242-
$output .= $this->p($v);
245+
foreach ($param as $v) {
246+
yield from $this->p($v);
243247
}
244-
245-
return $output;
246248
}
247249

248250
/**
249251
* @param IStructure $structure
250-
* @return string
252+
* @return iterable
251253
* @throws PackException
252254
*/
253-
private function packStructure(IStructure $structure): string
255+
private function packStructure(IStructure $structure): iterable
254256
{
255257
$arr = $this->structuresLt[get_class($structure)] ?? null;
256258
if ($arr === null) {
257259
throw new PackException('Provided structure as parameter is not supported');
258260
}
259261

260262
$signature = chr(array_shift($arr));
261-
$output = pack('C', 0b10110000 | count($arr)) . $signature;
263+
yield pack('C', 0b10110000 | count($arr)) . $signature;
262264
foreach ($arr as $structureMethod => $packerMethod) {
263-
$output .= $this->{$packerMethod}($structure->{$structureMethod}());
265+
yield from $this->{$packerMethod}($structure->{$structureMethod}());
264266
}
265-
266-
return $output;
267267
}
268268

269269
/**
270270
* @param Bytes $bytes
271-
* @return string
271+
* @return iterable
272272
* @throws PackException
273273
*/
274-
private function packByteArray(Bytes $bytes): string
274+
private function packByteArray(Bytes $bytes): iterable
275275
{
276276
$size = count($bytes);
277277
if ($size < self::MEDIUM) {
278-
return chr(0xCC) . pack('C', $size) . $bytes;
278+
yield chr(0xCC) . pack('C', $size) . $bytes;
279279
} elseif ($size < self::LARGE) {
280-
return chr(0xCD) . pack('n', $size) . $bytes;
280+
yield chr(0xCD) . pack('n', $size) . $bytes;
281281
} elseif ($size <= 2147483647) {
282-
return chr(0xCE) . pack('N', $size) . $bytes;
282+
yield chr(0xCE) . pack('N', $size) . $bytes;
283283
} else {
284284
throw new PackException('ByteArray too big');
285285
}

0 commit comments

Comments
 (0)