| Code Coverage
| Lines | Functions and Methods | Classes and Traits
Total | 72.60% covered (warning) | 72.60% | 53 / 73 | 50.00% covered (danger) | 50.00% | 3 / 6 | CRAP | 0.00% covered (danger) | 0.00% | 0 / 1
Borsh | 72.60% covered (warning) | 72.60% | 53 / 73 | 50.00% covered (danger) | 50.00% | 3 / 6 | 70.28 | 0.00% covered (danger) | 0.00% | 0 / 1
serialize | 100.00% covered (success) | 100.00% | 3 / 3 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
serializeObject | 41.67% covered (danger) | 41.67% | 5 / 12 | 0.00% covered (danger) | 0.00% | 0 / 1 | 9.96 |
serializeField | 72.00% covered (warning) | 72.00% | 18 / 25 | 0.00% covered (danger) | 0.00% | 0 / 1 | 18.30 |
deserialize | 100.00% covered (success) | 100.00% | 2 / 2 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
deserializeObject | 57.14% covered (warning) | 57.14% | 8 / 14 | 0.00% covered (danger) | 0.00% | 0 / 1 | 8.83 |
deserializeField | 100.00% covered (success) | 100.00% | 17 / 17 | 100.00% covered (success) | 100.00% | 1 / 1 | 12 |

1 | <?php
2 |
3 | namespace Attestto\SolanaPhpSdk\Borsh;
4 |
5 | use Attestto\SolanaPhpSdk\Exceptions\TodoException;
6 | use Attestto\SolanaPhpSdk\Util\Buffer;
7 |
8 | class Borsh
9 | {
10 | /**
11 | * @param array $schema
12 | * @param $object
13 | * @return array
14 | */
15 | public static function serialize(
16 | array $schema,
17 | $object
18 | ) : array
19 | {
20 | $writer = new BinaryWriter();
21 | static::serializeObject($schema, $object, $writer);
22 | return $writer->toArray();
23 | }
24 |
25 | /**
26 | * @param array $schema
27 | * @param $object
28 | * @param BinaryWriter $writer
29 | */
30 | protected static function serializeObject(
31 | array $schema,
32 | $object,
33 | BinaryWriter $writer
34 | ) {
35 | $objectSchema = $schema[get_class($object)] ?? null;
36 | if (! $objectSchema) {
37 | $class = get_class($object);
38 | throw new BorshException("Class {$class} is missing in schema");
39 | }
40 |
41 | if ($objectSchema['kind'] === 'struct') {
42 | foreach ($objectSchema['fields'] as list($fieldName, $fieldType)) {
43 | static::serializeField($schema, $fieldName, $object->{$fieldName}, $fieldType, $writer);
44 | }
45 | } elseif ($objectSchema['kind'] === 'enum') {
46 | throw new TodoException("TODO: Enums don't exist in PHP yet???");
47 | } else {
48 | $kind = $objectSchema['kind'];
49 | $class = get_class($object);
50 | throw new BorshException("Unexpected schema kind: {$kind} for {$class}");
51 | }
52 | }
53 |
54 | /**
55 | * @param array $schema
56 | * @param $fieldName
57 | * @param $value
58 | * @param $fieldType
59 | * @param BinaryWriter $writer
60 | */
61 | protected static function serializeField(
62 | array $schema,
63 | $fieldName,
64 | $value,
65 | $fieldType,
66 | BinaryWriter $writer
67 | ) {
68 | if (is_string($fieldType)) {
69 | $writer->{'write' . ucfirst($fieldType)}($value);
70 | } elseif (is_array($fieldType) && isset($fieldType[0])) { // sequential array
71 | if (is_int($fieldType[0])) {
72 | if (sizeof($value) !== $fieldType[0]) {
73 | $sizeOf = sizeof($value);
74 | throw new BorshException("Expecting byte array of length {{$fieldType[0]}}, but got {{$sizeOf}} bytes");
75 |
76 | }
77 | $writer->writeFixedArray($value);
78 | } elseif (sizeof($fieldType) === 2 && is_int($fieldType[1])) {
79 | if (sizeof($value) !== $fieldType[1]) {
80 | $sizeOf = sizeof($value);
81 | throw new BorshException("Expecting byte array of length {{$fieldType[1]}}, but got {{$sizeOf}} bytes");
82 |
83 | }
84 |
85 | for ($i = 0; $i < $fieldType[1]; $i++) {
86 | static::serializeField($schema, null, $value[$i], $fieldType[0], $writer);
87 | }
88 | } else {
89 | $writer->writeArray($value, fn ($item) => static::serializeField($schema, $fieldName, $item, $fieldType[0], $writer));
90 | }
91 | } elseif (isset($fieldType['kind'])) { // associative array
92 | switch ($fieldType['kind']) {
93 | case 'option':
94 | if ($value) {
95 | $writer->writeU8(1);
96 | static::serializeField($schema, $fieldName, $value, $fieldType['type'], $writer);
97 | } else {
98 | $writer->writeU8(0);
99 | }
100 | break;
101 | default:
102 | throw new BorshException("FieldType {{$fieldType['kind']}} unrecognized");
103 | }
104 | } else {
105 | static::serializeObject($schema, $value, $writer);
106 | }
107 | }
108 |
109 | /**
110 | * @param array $schema
111 | * @param string $class
112 | * @param array $buffer
113 | */
114 | public static function deserialize(
115 | array $schema,
116 | string $class,
117 | array $buffer
118 | )
119 | {
120 | $reader = new BinaryReader(Buffer::from($buffer));
121 | return static::deserializeObject($schema, $class, $reader);
122 | }
123 |
124 | /**
125 | * @param array $schema
126 | * @param string $class
127 | * @param BinaryReader $reader
128 | */
129 | protected static function deserializeObject(
130 | array $schema,
131 | string $class,
132 | BinaryReader $reader
133 | ) {
134 | $objectSchema = $schema[$class] ?? null;
135 | if (! $objectSchema) {
136 | throw new BorshException("Class {$class} is missing in schema");
137 | }
138 |
139 | if ($objectSchema['kind'] === 'struct') {
140 | if (! method_exists($class, 'borshConstructor')) {
141 | throw new BorshException("Class {{$class}} does not implement borshConstructor. Please use the BorshDeserialize trait.");
142 | }
143 |
144 | $result = $class::borshConstructor();
145 | foreach ($objectSchema['fields'] as list($fieldName, $fieldType)) {
146 | $result->{$fieldName} = static::deserializeField($schema, $fieldName, $fieldType, $reader);
147 | //$result->fields[$fieldName] = static::deserializeField($schema, $fieldName, $fieldType, $reader);
148 | }
149 | return $result;
150 | }
151 |
152 | if ($objectSchema['kind'] === 'enum') {
153 | throw new TodoException("TODO: Enums don't exist in PHP yet???");
154 | }
155 |
156 | $kind = $objectSchema['kind'];
157 | throw new BorshException("Unexpected schema kind: {$kind} for {$class}");
158 | }
159 |
160 | /**
161 | * @param array $schema
162 | * @param $fieldName
163 | * @param $fieldType
164 | * @param BinaryReader $reader
165 | */
166 | protected static function deserializeField(
167 | array $schema,
168 | $fieldName,
169 | $fieldType,
170 | BinaryReader $reader
171 | ) {
172 | if (is_string($fieldType) && ! class_exists($fieldType)) {
173 | return $reader->{'read' . ucfirst($fieldType)}();
174 | }
175 |
176 | if (is_array($fieldType) && isset($fieldType[0])) { // sequential array
177 | if (is_int($fieldType[0])) {
178 | return $reader->readFixedArray($fieldType[0]);
179 | } elseif (sizeof($fieldType) === 2 && is_int($fieldType[1])) {
180 | $array = [];
181 | for ($i = 0; $i < $fieldType[1]; $i++) {
182 | array_push($array, static::deserializeField($schema, null, $fieldType[0], $reader));
183 | }
184 | return $array;
185 | } else {
186 | return $reader->readArray(fn () => static::deserializeField($schema, $fieldName, $fieldType[0], $reader));
187 | }
188 | }
189 |
190 | if (isset($fieldType['kind']) && $fieldType['kind'] === 'option') { // associative array
191 | $option = $reader->readU8();
192 | if ($option) {
193 | return static::deserializeField($schema, $fieldName, $fieldType['type'], $reader);
194 | }
195 |
196 | return null;
197 | }
198 |
199 | return static::deserializeObject($schema, $fieldType, $reader);
200 | }
201 | }