1<?php
2/*
3 * Copyright 2015 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18/// @file
19/// @addtogroup flatbuffers_php_api
20/// @{
21
22namespace Google\FlatBuffers;
23
24class FlatbufferBuilder
25{
26    /**
27     * Internal ByteBuffer for the FlatBuffer data.
28     * @var ByteBuffer $bb
29     */
30    public $bb;
31
32    /// @cond FLATBUFFERS_INTERNAL
33    /**
34     * @var int $space
35     */
36    protected $space;
37
38    /**
39     * @var int $minalign
40     */
41    protected $minalign = 1;
42
43    /**
44     * @var array $vtable
45     */
46    protected $vtable;
47
48    /**
49     * @var int $vtable_in_use
50     */
51    protected $vtable_in_use = 0;
52
53    /**
54     * @var bool $nested
55     */
56    protected $nested = false;
57
58    /**
59     * @var int $object_start
60     */
61    protected $object_start;
62
63    /**
64     * @var array $vtables
65     */
66    protected $vtables = array();
67
68    /**
69     * @var int $num_vtables
70     */
71    protected $num_vtables = 0;
72
73    /**
74     * @var int $vector_num_elems
75     */
76    protected $vector_num_elems = 0;
77
78    /**
79     * @var bool $force_defaults
80     */
81    protected $force_defaults = false;
82    /// @endcond
83
84    /**
85     * Create a FlatBufferBuilder with a given initial size.
86     *
87     * @param $initial_size initial byte buffer size.
88     */
89    public function __construct($initial_size)
90    {
91        if ($initial_size <= 0) {
92            $initial_size = 1;
93        }
94        $this->space = $initial_size;
95        $this->bb = $this->newByteBuffer($initial_size);
96    }
97
98    /// @cond FLATBUFFERS_INTERNAL
99    /**
100     * create new bytebuffer
101     *
102     * @param $size
103     * @return ByteBuffer
104     */
105    private function newByteBuffer($size)
106    {
107        return new ByteBuffer($size);
108    }
109
110    /**
111     * Returns the current ByteBuffer offset.
112     *
113     * @return int
114     */
115    public function offset()
116    {
117        return $this->bb->capacity() - $this->space;
118    }
119
120    /**
121     * padding buffer
122     *
123     * @param $byte_size
124     */
125    public function pad($byte_size)
126    {
127        for ($i = 0; $i < $byte_size; $i++) {
128            $this->bb->putByte(--$this->space, "\0");
129        }
130    }
131
132    /**
133     * prepare bytebuffer
134     *
135     * @param $size
136     * @param $additional_bytes
137     * @throws \Exception
138     */
139    public function prep($size, $additional_bytes)
140    {
141        if ($size > $this->minalign) {
142            $this->minalign = $size;
143        }
144
145        $align_size = ((~($this->bb->capacity() - $this->space + $additional_bytes)) + 1) & ($size - 1);
146        while ($this->space < $align_size + $size  + $additional_bytes) {
147            $old_buf_size = $this->bb->capacity();
148            $this->bb = $this->growByteBuffer($this->bb);
149            $this->space += $this->bb->capacity() - $old_buf_size;
150        }
151
152        $this->pad($align_size);
153    }
154
155    /**
156     * @param ByteBuffer $bb
157     * @return ByteBuffer
158     * @throws \Exception
159     */
160    private static function growByteBuffer(ByteBuffer $bb)
161    {
162        $old_buf_size = $bb->capacity();
163        if (($old_buf_size & 0xC0000000) != 0) {
164            throw new \Exception("FlatBuffers: cannot grow buffer beyond 2 gigabytes");
165        }
166        $new_buf_size = $old_buf_size << 1;
167
168        $bb->setPosition(0);
169        $nbb = new ByteBuffer($new_buf_size);
170
171        $nbb->setPosition($new_buf_size - $old_buf_size);
172
173        // TODO(chobie): is this little bit faster?
174        //$nbb->_buffer = substr_replace($nbb->_buffer, $bb->_buffer, $new_buf_size - $old_buf_size, strlen($bb->_buffer));
175        for ($i = $new_buf_size - $old_buf_size, $j = 0; $j < strlen($bb->_buffer); $i++, $j++) {
176            $nbb->_buffer[$i] = $bb->_buffer[$j];
177        }
178
179        return $nbb;
180    }
181
182    /**
183     * @param $x
184     */
185    public function putBool($x)
186    {
187        $this->bb->put($this->space -= 1, chr((int)(bool)($x)));
188    }
189
190    /**
191     * @param $x
192     */
193    public function putByte($x)
194    {
195        $this->bb->put($this->space -= 1, chr($x));
196    }
197
198    /**
199     * @param $x
200     */
201    public function putSbyte($x)
202    {
203        $this->bb->put($this->space -= 1, chr($x));
204    }
205
206    /**
207     * @param $x
208     */
209    public function putShort($x)
210    {
211        $this->bb->putShort($this->space -= 2, $x);
212    }
213
214    /**
215     * @param $x
216     */
217    public function putUshort($x)
218    {
219        $this->bb->putUshort($this->space -= 2, $x);
220    }
221
222    /**
223     * @param $x
224     */
225    public function putInt($x)
226    {
227        $this->bb->putInt($this->space -= 4, $x);
228    }
229
230    /**
231     * @param $x
232     */
233    public function putUint($x)
234    {
235        if ($x > PHP_INT_MAX) {
236            throw new \InvalidArgumentException("your platform can't handle uint correctly. use 64bit machine.");
237        }
238
239        $this->bb->putUint($this->space -= 4, $x);
240    }
241
242    /**
243     * @param $x
244     */
245    public function putLong($x)
246    {
247        if ($x > PHP_INT_MAX) {
248            throw new \InvalidArgumentException("Your platform can't handle long correctly. Use a 64bit machine.");
249        }
250
251        $this->bb->putLong($this->space -= 8, $x);
252    }
253
254    /**
255     * @param $x
256     */
257    public function putUlong($x)
258    {
259        if ($x > PHP_INT_MAX) {
260            throw new \InvalidArgumentException("Your platform can't handle ulong correctly. This is a php limitation. Please wait for the extension release.");
261        }
262
263        $this->bb->putUlong($this->space -= 8, $x);
264    }
265
266    /**
267     * @param $x
268     */
269    public function putFloat($x)
270    {
271        $this->bb->putFloat($this->space -= 4, $x);
272    }
273
274    /**
275     * @param $x
276     */
277    public function putDouble($x)
278    {
279        $this->bb->putDouble($this->space -= 8, $x);
280    }
281    /// @endcond
282
283    /**
284     * Add a `bool` to the buffer, properly aligned, and grows the buffer (if necessary).
285     * @param $x The `bool` to add to the buffer.
286     */
287    public function addBool($x)
288    {
289        $this->prep(1, 0);
290        $this->putBool($x);
291    }
292
293    /**
294     * Add a `byte` to the buffer, properly aligned, and grows the buffer (if necessary).
295     * @param $x The `byte` to add to the buffer.
296     */
297    public function addByte($x)
298    {
299        $this->prep(1, 0);
300        $this->putByte($x);
301    }
302
303    /**
304     * Add a `signed byte` to the buffer, properly aligned, and grows the buffer (if necessary).
305     * @param $x The `signed byte` to add to the buffer.
306     */
307    public function addSbyte($x)
308    {
309        $this->prep(1, 0);
310        $this->putSbyte($x);
311    }
312
313    /**
314     * Add a `short` to the buffer, properly aligned, and grows the buffer (if necessary).
315     * @param $x The `short` to add to the buffer.
316     */
317    public function addShort($x)
318    {
319        $this->prep(2, 0);
320        $this->putShort($x);
321    }
322
323    /**
324     * Add an `unsigned short` to the buffer, properly aligned, and grows the buffer (if necessary).
325     * @param $x The `unsigned short` to add to the buffer.
326     */
327    public function addUshort($x)
328    {
329        $this->prep(2, 0);
330        $this->putUshort($x);
331    }
332
333    /**
334     * Add an `int` to the buffer, properly aligned, and grows the buffer (if necessary).
335     * @param $x The `int` to add to the buffer.
336     */
337    public function addInt($x)
338    {
339        $this->prep(4, 0);
340        $this->putInt($x);
341    }
342
343    /**
344     * Add an `unsigned int` to the buffer, properly aligned, and grows the buffer (if necessary).
345     * @param $x The `unsigned int` to add to the buffer.
346     */
347    public function addUint($x)
348    {
349        $this->prep(4, 0);
350        $this->putUint($x);
351    }
352
353    /**
354     * Add a `long` to the buffer, properly aligned, and grows the buffer (if necessary).
355     * @param $x The `long` to add to the buffer.
356     */
357    public function addLong($x)
358    {
359        $this->prep(8, 0);
360        $this->putLong($x);
361    }
362
363    /**
364     * Add an `unsigned long` to the buffer, properly aligned, and grows the buffer (if necessary).
365     * @param $x The `unsigned long` to add to the buffer.
366     */
367    public function addUlong($x)
368    {
369        $this->prep(8, 0);
370        $this->putUlong($x);
371    }
372
373    /**
374     * Add a `float` to the buffer, properly aligned, and grows the buffer (if necessary).
375     * @param $x The `float` to add to the buffer.
376     */
377    public function addFloat($x)
378    {
379        $this->prep(4, 0);
380        $this->putFloat($x);
381    }
382
383    /**
384     * Add a `double` to the buffer, properly aligned, and grows the buffer (if necessary).
385     * @param $x The `double` to add to the buffer.
386     */
387    public function addDouble($x)
388    {
389        $this->prep(8, 0);
390        $this->putDouble($x);
391    }
392
393    /// @cond FLATBUFFERS_INTERNAL
394    /**
395     * @param $o
396     * @param $x
397     * @param $d
398     */
399    public function addBoolX($o, $x, $d)
400    {
401        if ($this->force_defaults || $x != $d) {
402            $this->addBool($x);
403            $this->slot($o);
404        }
405    }
406
407    /**
408     * @param $o
409     * @param $x
410     * @param $d
411     */
412    public function addByteX($o, $x, $d)
413    {
414        if ($this->force_defaults || $x != $d) {
415            $this->addByte($x);
416            $this->slot($o);
417        }
418    }
419
420    /**
421     * @param $o
422     * @param $x
423     * @param $d
424     */
425    public function addSbyteX($o, $x, $d)
426    {
427        if ($this->force_defaults || $x != $d) {
428            $this->addSbyte($x);
429            $this->slot($o);
430        }
431    }
432
433    /**
434     * @param $o
435     * @param $x
436     * @param $d
437     */
438    public function addShortX($o, $x, $d)
439    {
440        if ($this->force_defaults || $x != $d) {
441            $this->addShort($x);
442            $this->slot($o);
443        }
444    }
445
446    /**
447     * @param $o
448     * @param $x
449     * @param $d
450     */
451    public function addUshortX($o, $x, $d)
452    {
453        if ($this->force_defaults || $x != $d) {
454            $this->addUshort($x);
455            $this->slot($o);
456        }
457    }
458
459    /**
460     * @param $o
461     * @param $x
462     * @param $d
463     */
464    public function addIntX($o, $x, $d)
465    {
466        if ($this->force_defaults || $x != $d) {
467            $this->addInt($x);
468            $this->slot($o);
469        }
470    }
471
472    /**
473     * @param $o
474     * @param $x
475     * @param $d
476     */
477    public function addUintX($o, $x, $d)
478    {
479        if ($this->force_defaults || $x != $d) {
480            $this->addUint($x);
481            $this->slot($o);
482        }
483    }
484
485    /**
486     * @param $o
487     * @param $x
488     * @param $d
489     */
490    public function addLongX($o, $x, $d)
491    {
492        if ($this->force_defaults || $x != $d) {
493            $this->addLong($x);
494            $this->slot($o);
495        }
496    }
497
498    /**
499     * @param $o
500     * @param $x
501     * @param $d
502     */
503    public function addUlongX($o, $x, $d)
504    {
505        if ($this->force_defaults || $x != $d) {
506            $this->addUlong($x);
507            $this->slot($o);
508        }
509    }
510
511
512    /**
513     * @param $o
514     * @param $x
515     * @param $d
516     */
517    public function addFloatX($o, $x, $d)
518    {
519        if ($this->force_defaults || $x != $d) {
520            $this->addFloat($x);
521            $this->slot($o);
522        }
523    }
524
525    /**
526     * @param $o
527     * @param $x
528     * @param $d
529     */
530    public function addDoubleX($o, $x, $d)
531    {
532        if ($this->force_defaults || $x != $d) {
533            $this->addDouble($x);
534            $this->slot($o);
535        }
536    }
537
538    /**
539     * @param $o
540     * @param $x
541     * @param $d
542     * @throws \Exception
543     */
544    public function addOffsetX($o, $x, $d)
545    {
546        if ($this->force_defaults || $x != $d) {
547            $this->addOffset($x);
548            $this->slot($o);
549        }
550    }
551    /// @endcond
552
553    /**
554     * Adds on offset, relative to where it will be written.
555     * @param $off The offset to add to the buffer.
556     * @throws \Exception Throws an exception if `$off` is greater than the underlying ByteBuffer's
557     * offest.
558     */
559    public function addOffset($off)
560    {
561        $this->prep(Constants::SIZEOF_INT, 0); // Ensure alignment is already done
562        if ($off > $this->offset()) {
563            throw new \Exception("");
564        }
565
566        $off = $this->offset() - $off + Constants::SIZEOF_INT;
567        $this->putInt($off);
568    }
569
570    /// @cond FLATBUFFERS_INTERNAL
571    /**
572     * @param $elem_size
573     * @param $num_elems
574     * @param $alignment
575     * @throws \Exception
576     */
577    public function startVector($elem_size, $num_elems, $alignment)
578    {
579        $this->notNested();
580        $this->vector_num_elems = $num_elems;
581        $this->prep(Constants::SIZEOF_INT, $elem_size * $num_elems);
582        $this->prep($alignment, $elem_size * $num_elems); // Just in case alignemnt > int;
583    }
584
585    /**
586     * @return int
587     */
588    public function endVector()
589    {
590        $this->putUint($this->vector_num_elems);
591        return $this->offset();
592    }
593
594    protected function is_utf8($bytes)
595    {
596        if (function_exists('mb_detect_encoding')) {
597            return (bool) mb_detect_encoding($bytes, 'UTF-8', true);
598        }
599
600        $len = strlen($bytes);
601        if ($len < 1) {
602            /* NOTE: always return 1 when passed string is null */
603            return true;
604        }
605
606        for ($j = 0, $i = 0; $i < $len; $i++) {
607            // check ACII
608            if ($bytes[$j] == "\x09" ||
609                $bytes[$j] == "\x0A" ||
610                $bytes[$j] == "\x0D" ||
611                ($bytes[$j] >= "\x20" && $bytes[$j] <= "\x7E")) {
612                $j++;
613                continue;
614            }
615
616            /* non-overlong 2-byte */
617            if ((($i+1) <= $len) &&
618                ($bytes[$j] >= "\xC2" && $bytes[$j] <= "\xDF" &&
619                    ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF"))) {
620                $j += 2;
621                $i++;
622                continue;
623            }
624
625            /* excluding overlongs */
626            if ((($i + 2) <= $len) &&
627                $bytes[$j] == "\xE0" &&
628                ($bytes[$j+1] >= "\xA0" && $bytes[$j+1] <= "\xBF" &&
629                    ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF"))) {
630                $bytes += 3;
631                $i +=2;
632                continue;
633            }
634
635            /* straight 3-byte */
636            if ((($i+2) <= $len) &&
637                (($bytes[$j] >= "\xE1" && $bytes[$j] <= "\xEC") ||
638                    $bytes[$j] == "\xEE" ||
639                    $bytes[$j] = "\xEF") &&
640                ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF") &&
641                ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF")) {
642                $j += 3;
643                $i += 2;
644                continue;
645            }
646
647            /* excluding surrogates */
648            if ((($i+2) <= $len) &&
649                $bytes[$j] == "\xED" &&
650                ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\x9f" &&
651                    ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF"))) {
652                $j += 3;
653                $i += 2;
654                continue;
655            }
656
657            /* planes 1-3 */
658            if ((($i + 3) <= $len) &&
659                $bytes[$j] == "\xF0" &&
660                ($bytes[$j+1] >= "\x90" && $bytes[$j+1] <= "\xBF") &&
661                ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF") &&
662                ($bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF")) {
663                $j += 4;
664                $i += 3;
665                continue;
666            }
667
668
669            /* planes 4-15 */
670            if ((($i+3) <= $len) &&
671                $bytes[$j] >= "\xF1" && $bytes[$j] <= "\xF3" &&
672                $bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF" &&
673                $bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF" &&
674                $bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF"
675            ) {
676                $j += 4;
677                $i += 3;
678                continue;
679            }
680
681            /* plane 16 */
682            if ((($i+3) <= $len) &&
683                $bytes[$j] == "\xF4" &&
684                ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\x8F") &&
685                ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF") &&
686                ($bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF")
687            ) {
688                $bytes += 4;
689                $i += 3;
690                continue;
691            }
692
693
694            return false;
695        }
696
697        return true;
698    }
699    /// @endcond
700
701    /**
702     * Encode the string `$s` in the buffer using UTF-8.
703     * @param string $s The string to encode.
704     * @return int The offset in the buffer where the encoded string starts.
705     * @throws InvalidArgumentException Thrown if the input string `$s` is not
706     *     UTF-8.
707     */
708    public function createString($s)
709    {
710        if (!$this->is_utf8($s)) {
711            throw new \InvalidArgumentException("string must be utf-8 encoded value.");
712        }
713
714        $this->notNested();
715        $this->addByte(0); // null terminated
716        $this->startVector(1, strlen($s), 1);
717        $this->space -= strlen($s);
718        for ($i =  $this->space, $j = 0 ; $j < strlen($s) ; $i++, $j++) {
719            $this->bb->_buffer[$i] = $s[$j];
720        }
721        return $this->endVector();
722    }
723
724    /// @cond FLATBUFFERS_INTERNAL
725    /**
726     * @throws \Exception
727     */
728    public function notNested()
729    {
730        if ($this->nested) {
731            throw new \Exception("FlatBuffers; object serialization must not be nested");
732        }
733    }
734
735    /**
736     * @param $obj
737     * @throws \Exception
738     */
739    public function nested($obj)
740    {
741        if ($obj != $this->offset()) {
742            throw new \Exception("FlatBuffers: struct must be serialized inline");
743        }
744    }
745
746    /**
747     * @param $numfields
748     * @throws \Exception
749     */
750    public function startObject($numfields)
751    {
752        $this->notNested();
753        if ($this->vtable == null || count($this->vtable) < $numfields) {
754            $this->vtable = array();
755        }
756
757        $this->vtable_in_use = $numfields;
758        for ($i = 0; $i < $numfields; $i++) {
759            $this->vtable[$i] = 0;
760        }
761
762        $this->nested = true;
763        $this->object_start = $this->offset();
764    }
765
766    /**
767     * @param $voffset
768     * @param $x
769     * @param $d
770     * @throws \Exception
771     */
772    public function addStructX($voffset, $x, $d)
773    {
774        if ($x != $d) {
775            $this->nested($x);
776            $this->slot($voffset);
777        }
778    }
779
780    /**
781     * @param $voffset
782     * @param $x
783     * @param $d
784     * @throws \Exception
785     */
786    public function addStruct($voffset, $x, $d)
787    {
788        if ($x != $d) {
789            $this->nested($x);
790            $this->slot($voffset);
791        }
792    }
793
794    /**
795     * @param $voffset
796     */
797    public function slot($voffset)
798    {
799        $this->vtable[$voffset] = $this->offset();
800    }
801
802    /**
803     * @return int
804     * @throws \Exception
805     */
806    public function endObject()
807    {
808        if ($this->vtable == null || !$this->nested) {
809            throw new \Exception("FlatBuffers: endObject called without startObject");
810        }
811
812        $this->addInt(0);
813        $vtableloc = $this->offset();
814
815        $i = $this->vtable_in_use -1;
816        // Trim trailing zeroes.
817        for (; $i >= 0 && $this->vtable[$i] == 0; $i--) {}
818        $trimmed_size = $i + 1;
819        for (; $i >= 0; $i--) {
820            $off = ($this->vtable[$i] != 0) ? $vtableloc - $this->vtable[$i] : 0;
821            $this->addShort($off);
822        }
823
824        $standard_fields = 2; // the fields below
825        $this->addShort($vtableloc - $this->object_start);
826        $this->addShort(($trimmed_size + $standard_fields) * Constants::SIZEOF_SHORT);
827
828        // search for an existing vtable that matches the current one.
829        $existing_vtable = 0;
830
831        for ($i = 0; $i < $this->num_vtables; $i++) {
832            $vt1 = $this->bb->capacity() - $this->vtables[$i];
833            $vt2 = $this->space;
834
835            $len = $this->bb->getShort($vt1);
836
837            if ($len == $this->bb->getShort($vt2)) {
838                for ($j = Constants::SIZEOF_SHORT; $j < $len; $j += Constants::SIZEOF_SHORT) {
839                    if ($this->bb->getShort($vt1 + $j) != $this->bb->getShort($vt2 + $j)) {
840                        continue 2;
841                    }
842                }
843                $existing_vtable = $this->vtables[$i];
844                break;
845            }
846        }
847
848        if ($existing_vtable != 0) {
849            // Found a match:
850            // Remove the current vtable
851            $this->space = $this->bb->capacity() - $vtableloc;
852            $this->bb->putInt($this->space, $existing_vtable - $vtableloc);
853        } else {
854            // No Match:
855            // Add the location of the current vtable to the list of vtables
856            if ($this->num_vtables == count($this->vtables)) {
857                $vtables = $this->vtables;
858                $this->vtables = array();
859                // copy of
860                for ($i = 0; $i < count($vtables) * 2; $i++) {
861                    $this->vtables[$i] = ($i < count($vtables)) ? $vtables[$i] : 0;
862                }
863            }
864            $this->vtables[$this->num_vtables++] = $this->offset();
865            $this->bb->putInt($this->bb->capacity() - $vtableloc, $this->offset() - $vtableloc);
866        }
867
868        $this->nested = false;
869        $this->vtable = null;
870        return $vtableloc;
871    }
872
873    /**
874     * @param $table
875     * @param $field
876     * @throws \Exception
877     */
878    public function required($table, $field)
879    {
880        $table_start = $this->bb->capacity() - $table;
881        $vtable_start = $table_start - $this->bb->getInt($table_start);
882        $ok = $this->bb->getShort($vtable_start + $field) != 0;
883
884        if (!$ok) {
885            throw new \Exception("FlatBuffers: field "  . $field  .  " must be set");
886        }
887    }
888    /// @endcond
889
890    /**
891     * Finalize a buffer, pointing to the given `$root_table`.
892     * @param $root_table An offest to be added to the buffer.
893     * @param $file_identifier A FlatBuffer file identifier to be added to the
894     *     buffer before `$root_table`. This defaults to `null`.
895     * @throws InvalidArgumentException Thrown if an invalid `$identifier` is
896     *     given, where its length is not equal to
897     *    `Constants::FILE_IDENTIFIER_LENGTH`.
898     */
899    public function finish($root_table, $identifier = null)
900    {
901        if ($identifier == null) {
902            $this->prep($this->minalign, Constants::SIZEOF_INT);
903            $this->addOffset($root_table);
904            $this->bb->setPosition($this->space);
905        } else {
906            $this->prep($this->minalign, Constants::SIZEOF_INT + Constants::FILE_IDENTIFIER_LENGTH);
907            if (strlen($identifier) != Constants::FILE_IDENTIFIER_LENGTH) {
908                throw new \InvalidArgumentException(
909                    sprintf("FlatBuffers: file identifier must be length %d",
910                        Constants::FILE_IDENTIFIER_LENGTH));
911            }
912
913            for ($i = Constants::FILE_IDENTIFIER_LENGTH - 1; $i >= 0;
914                  $i--) {
915                $this->addByte(ord($identifier[$i]));
916            }
917            $this->finish($root_table);
918        }
919    }
920
921    /**
922     * In order to save space, fields that are set to their default value don't
923     * get serialized into the buffer.
924     * @param bool $forceDefaults When set to `true`, always serializes default
925     *     values.
926     */
927    public function forceDefaults($forceDefaults)
928    {
929        $this->force_defaults = $forceDefaults;
930    }
931
932    /**
933     * Get the ByteBuffer representing the FlatBuffer.
934     * @return ByteBuffer The ByteBuffer containing the FlatBuffer data.
935     */
936    public function dataBuffer()
937    {
938        return $this->bb;
939    }
940
941    /// @cond FLATBUFFERS_INTERNAL
942    /**
943     * @return int
944     */
945    public function dataStart()
946    {
947        return $this->space;
948    }
949    /// @endcond
950
951    /**
952     * Utility function to copy and return the FlatBuffer data from the
953     * underlying ByteBuffer.
954     * @return string A string (representing a byte[]) that contains a copy
955     * of the FlatBuffer data.
956     */
957    public function sizedByteArray()
958    {
959        $start = $this->space;
960        $length = $this->bb->capacity() - $this->space;
961
962        $result = str_repeat("\0", $length);
963        $this->bb->setPosition($start);
964        $this->bb->getX($result);
965
966        return $result;
967    }
968}
969
970/// @}
971