Home | History | Annotate | Download | only in php
      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 
     22 namespace Google\FlatBuffers;
     23 
     24 final class 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 
    282     /**
    283      * @param $off
    284      */
    285     public function putOffset($off)
    286     {
    287         $new_off = $this->offset() - $off + Constants::SIZEOF_INT;
    288         $this->putInt($new_off);
    289     }
    290     /// @endcond
    291 
    292     /**
    293      * Add a `bool` to the buffer, properly aligned, and grows the buffer (if necessary).
    294      * @param $x The `bool` to add to the buffer.
    295      */
    296     public function addBool($x)
    297     {
    298         $this->prep(1, 0);
    299         $this->putBool($x);
    300     }
    301 
    302     /**
    303      * Add a `byte` to the buffer, properly aligned, and grows the buffer (if necessary).
    304      * @param $x The `byte` to add to the buffer.
    305      */
    306     public function addByte($x)
    307     {
    308         $this->prep(1, 0);
    309         $this->putByte($x);
    310     }
    311 
    312     /**
    313      * Add a `signed byte` to the buffer, properly aligned, and grows the buffer (if necessary).
    314      * @param $x The `signed byte` to add to the buffer.
    315      */
    316     public function addSbyte($x)
    317     {
    318         $this->prep(1, 0);
    319         $this->putSbyte($x);
    320     }
    321 
    322     /**
    323      * Add a `short` to the buffer, properly aligned, and grows the buffer (if necessary).
    324      * @param $x The `short` to add to the buffer.
    325      */
    326     public function addShort($x)
    327     {
    328         $this->prep(2, 0);
    329         $this->putShort($x);
    330     }
    331 
    332     /**
    333      * Add an `unsigned short` to the buffer, properly aligned, and grows the buffer (if necessary).
    334      * @param $x The `unsigned short` to add to the buffer.
    335      */
    336     public function addUshort($x)
    337     {
    338         $this->prep(2, 0);
    339         $this->putUshort($x);
    340     }
    341 
    342     /**
    343      * Add an `int` to the buffer, properly aligned, and grows the buffer (if necessary).
    344      * @param $x The `int` to add to the buffer.
    345      */
    346     public function addInt($x)
    347     {
    348         $this->prep(4, 0);
    349         $this->putInt($x);
    350     }
    351 
    352     /**
    353      * Add an `unsigned int` to the buffer, properly aligned, and grows the buffer (if necessary).
    354      * @param $x The `unsigned int` to add to the buffer.
    355      */
    356     public function addUint($x)
    357     {
    358         $this->prep(4, 0);
    359         $this->putUint($x);
    360     }
    361 
    362     /**
    363      * Add a `long` to the buffer, properly aligned, and grows the buffer (if necessary).
    364      * @param $x The `long` to add to the buffer.
    365      */
    366     public function addLong($x)
    367     {
    368         $this->prep(8, 0);
    369         $this->putLong($x);
    370     }
    371 
    372     /**
    373      * Add an `unsigned long` to the buffer, properly aligned, and grows the buffer (if necessary).
    374      * @param $x The `unsigned long` to add to the buffer.
    375      */
    376     public function addUlong($x)
    377     {
    378         $this->prep(8, 0);
    379         $this->putUlong($x);
    380     }
    381 
    382     /**
    383      * Add a `float` to the buffer, properly aligned, and grows the buffer (if necessary).
    384      * @param $x The `float` to add to the buffer.
    385      */
    386     public function addFloat($x)
    387     {
    388         $this->prep(4, 0);
    389         $this->putFloat($x);
    390     }
    391 
    392     /**
    393      * Add a `double` to the buffer, properly aligned, and grows the buffer (if necessary).
    394      * @param $x The `double` to add to the buffer.
    395      */
    396     public function addDouble($x)
    397     {
    398         $this->prep(8, 0);
    399         $this->putDouble($x);
    400     }
    401 
    402     /// @cond FLATBUFFERS_INTERNAL
    403     /**
    404      * @param $o
    405      * @param $x
    406      * @param $d
    407      */
    408     public function addBoolX($o, $x, $d)
    409     {
    410         if ($this->force_defaults || $x != $d) {
    411             $this->addBool($x);
    412             $this->slot($o);
    413         }
    414     }
    415 
    416     /**
    417      * @param $o
    418      * @param $x
    419      * @param $d
    420      */
    421     public function addByteX($o, $x, $d)
    422     {
    423         if ($this->force_defaults || $x != $d) {
    424             $this->addByte($x);
    425             $this->slot($o);
    426         }
    427     }
    428 
    429     /**
    430      * @param $o
    431      * @param $x
    432      * @param $d
    433      */
    434     public function addSbyteX($o, $x, $d)
    435     {
    436         if ($this->force_defaults || $x != $d) {
    437             $this->addSbyte($x);
    438             $this->slot($o);
    439         }
    440     }
    441 
    442     /**
    443      * @param $o
    444      * @param $x
    445      * @param $d
    446      */
    447     public function addShortX($o, $x, $d)
    448     {
    449         if ($this->force_defaults || $x != $d) {
    450             $this->addShort($x);
    451             $this->slot($o);
    452         }
    453     }
    454 
    455     /**
    456      * @param $o
    457      * @param $x
    458      * @param $d
    459      */
    460     public function addUshortX($o, $x, $d)
    461     {
    462         if ($this->force_defaults || $x != $d) {
    463             $this->addUshort($x);
    464             $this->slot($o);
    465         }
    466     }
    467 
    468     /**
    469      * @param $o
    470      * @param $x
    471      * @param $d
    472      */
    473     public function addIntX($o, $x, $d)
    474     {
    475         if ($this->force_defaults || $x != $d) {
    476             $this->addInt($x);
    477             $this->slot($o);
    478         }
    479     }
    480 
    481     /**
    482      * @param $o
    483      * @param $x
    484      * @param $d
    485      */
    486     public function addUintX($o, $x, $d)
    487     {
    488         if ($this->force_defaults || $x != $d) {
    489             $this->addUint($x);
    490             $this->slot($o);
    491         }
    492     }
    493 
    494     /**
    495      * @param $o
    496      * @param $x
    497      * @param $d
    498      */
    499     public function addLongX($o, $x, $d)
    500     {
    501         if ($this->force_defaults || $x != $d) {
    502             $this->addLong($x);
    503             $this->slot($o);
    504         }
    505     }
    506 
    507     /**
    508      * @param $o
    509      * @param $x
    510      * @param $d
    511      */
    512     public function addUlongX($o, $x, $d)
    513     {
    514         if ($this->force_defaults || $x != $d) {
    515             $this->addUlong($x);
    516             $this->slot($o);
    517         }
    518     }
    519 
    520 
    521     /**
    522      * @param $o
    523      * @param $x
    524      * @param $d
    525      */
    526     public function addFloatX($o, $x, $d)
    527     {
    528         if ($this->force_defaults || $x != $d) {
    529             $this->addFloat($x);
    530             $this->slot($o);
    531         }
    532     }
    533 
    534     /**
    535      * @param $o
    536      * @param $x
    537      * @param $d
    538      */
    539     public function addDoubleX($o, $x, $d)
    540     {
    541         if ($this->force_defaults || $x != $d) {
    542             $this->addDouble($x);
    543             $this->slot($o);
    544         }
    545     }
    546 
    547     /**
    548      * @param $o
    549      * @param $x
    550      * @param $d
    551      * @throws \Exception
    552      */
    553     public function addOffsetX($o, $x, $d)
    554     {
    555         if ($this->force_defaults || $x != $d) {
    556             $this->addOffset($x);
    557             $this->slot($o);
    558         }
    559     }
    560     /// @endcond
    561 
    562     /**
    563      * Adds on offset, relative to where it will be written.
    564      * @param $off The offset to add to the buffer.
    565      * @throws \Exception Throws an exception if `$off` is greater than the underlying ByteBuffer's
    566      * offest.
    567      */
    568     public function addOffset($off)
    569     {
    570         $this->prep(Constants::SIZEOF_INT, 0); // Ensure alignment is already done
    571         if ($off > $this->offset()) {
    572             throw new \Exception("");
    573         }
    574         $this->putOffset($off);
    575     }
    576 
    577     /// @cond FLATBUFFERS_INTERNAL
    578     /**
    579      * @param $elem_size
    580      * @param $num_elems
    581      * @param $alignment
    582      * @throws \Exception
    583      */
    584     public function startVector($elem_size, $num_elems, $alignment)
    585     {
    586         $this->notNested();
    587         $this->vector_num_elems = $num_elems;
    588         $this->prep(Constants::SIZEOF_INT, $elem_size * $num_elems);
    589         $this->prep($alignment, $elem_size * $num_elems); // Just in case alignemnt > int;
    590     }
    591 
    592     /**
    593      * @return int
    594      */
    595     public function endVector()
    596     {
    597         $this->putUint($this->vector_num_elems);
    598         return $this->offset();
    599     }
    600 
    601     protected function is_utf8($bytes)
    602     {
    603         if (function_exists('mb_detect_encoding')) {
    604             return (bool) mb_detect_encoding($bytes, 'UTF-8', true);
    605         }
    606 
    607         $len = strlen($bytes);
    608         if ($len < 1) {
    609             /* NOTE: always return 1 when passed string is null */
    610             return true;
    611         }
    612 
    613         for ($j = 0, $i = 0; $i < $len; $i++) {
    614             // check ACII
    615             if ($bytes[$j] == "\x09" ||
    616                 $bytes[$j] == "\x0A" ||
    617                 $bytes[$j] == "\x0D" ||
    618                 ($bytes[$j] >= "\x20" && $bytes[$j] <= "\x7E")) {
    619                 $j++;
    620                 continue;
    621             }
    622 
    623             /* non-overlong 2-byte */
    624             if ((($i+1) <= $len) &&
    625                 ($bytes[$j] >= "\xC2" && $bytes[$j] <= "\xDF" &&
    626                     ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF"))) {
    627                 $j += 2;
    628                 $i++;
    629                 continue;
    630             }
    631 
    632             /* excluding overlongs */
    633             if ((($i + 2) <= $len) &&
    634                 $bytes[$j] == "\xE0" &&
    635                 ($bytes[$j+1] >= "\xA0" && $bytes[$j+1] <= "\xBF" &&
    636                     ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF"))) {
    637                 $bytes += 3;
    638                 $i +=2;
    639                 continue;
    640             }
    641 
    642             /* straight 3-byte */
    643             if ((($i+2) <= $len) &&
    644                 (($bytes[$j] >= "\xE1" && $bytes[$j] <= "\xEC") ||
    645                     $bytes[$j] == "\xEE" ||
    646                     $bytes[$j] = "\xEF") &&
    647                 ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF") &&
    648                 ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF")) {
    649                 $j += 3;
    650                 $i += 2;
    651                 continue;
    652             }
    653 
    654             /* excluding surrogates */
    655             if ((($i+2) <= $len) &&
    656                 $bytes[$j] == "\xED" &&
    657                 ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\x9f" &&
    658                     ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF"))) {
    659                 $j += 3;
    660                 $i += 2;
    661                 continue;
    662             }
    663 
    664             /* planes 1-3 */
    665             if ((($i + 3) <= $len) &&
    666                 $bytes[$j] == "\xF0" &&
    667                 ($bytes[$j+1] >= "\x90" && $bytes[$j+1] <= "\xBF") &&
    668                 ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF") &&
    669                 ($bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF")) {
    670                 $j += 4;
    671                 $i += 3;
    672                 continue;
    673             }
    674 
    675 
    676             /* planes 4-15 */
    677             if ((($i+3) <= $len) &&
    678                 $bytes[$j] >= "\xF1" && $bytes[$j] <= "\xF3" &&
    679                 $bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\xBF" &&
    680                 $bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF" &&
    681                 $bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF"
    682             ) {
    683                 $j += 4;
    684                 $i += 3;
    685                 continue;
    686             }
    687 
    688             /* plane 16 */
    689             if ((($i+3) <= $len) &&
    690                 $bytes[$j] == "\xF4" &&
    691                 ($bytes[$j+1] >= "\x80" && $bytes[$j+1] <= "\x8F") &&
    692                 ($bytes[$j+2] >= "\x80" && $bytes[$j+2] <= "\xBF") &&
    693                 ($bytes[$j+3] >= "\x80" && $bytes[$j+3] <= "\xBF")
    694             ) {
    695                 $bytes += 4;
    696                 $i += 3;
    697                 continue;
    698             }
    699 
    700 
    701             return false;
    702         }
    703 
    704         return true;
    705     }
    706     /// @endcond
    707 
    708     /**
    709      * Encode the string `$s` in the buffer using UTF-8.
    710      * @param string $s The string to encode.
    711      * @return int The offset in the buffer where the encoded string starts.
    712      * @throws InvalidArgumentException Thrown if the input string `$s` is not
    713      *     UTF-8.
    714      */
    715     public function createString($s)
    716     {
    717         if (!$this->is_utf8($s)) {
    718             throw new \InvalidArgumentException("string must be utf-8 encoded value.");
    719         }
    720 
    721         $this->notNested();
    722         $this->addByte(0); // null terminated
    723         $this->startVector(1, strlen($s), 1);
    724         $this->space -= strlen($s);
    725         for ($i =  $this->space, $j = 0 ; $j < strlen($s) ; $i++, $j++) {
    726             $this->bb->_buffer[$i] = $s[$j];
    727         }
    728         return $this->endVector();
    729     }
    730 
    731     /// @cond FLATBUFFERS_INTERNAL
    732     /**
    733      * @throws \Exception
    734      */
    735     public function notNested()
    736     {
    737         if ($this->nested) {
    738             throw new \Exception("FlatBuffers; object serialization must not be nested");
    739         }
    740     }
    741 
    742     /**
    743      * @param $obj
    744      * @throws \Exception
    745      */
    746     public function nested($obj)
    747     {
    748         if ($obj != $this->offset()) {
    749             throw new \Exception("FlatBuffers: struct must be serialized inline");
    750         }
    751     }
    752 
    753     /**
    754      * @param $numfields
    755      * @throws \Exception
    756      */
    757     public function startObject($numfields)
    758     {
    759         $this->notNested();
    760         if ($this->vtable == null || count($this->vtable) < $numfields) {
    761             $this->vtable = array();
    762         }
    763 
    764         $this->vtable_in_use = $numfields;
    765         for ($i = 0; $i < $numfields; $i++) {
    766             $this->vtable[$i] = 0;
    767         }
    768 
    769         $this->nested = true;
    770         $this->object_start = $this->offset();
    771     }
    772 
    773     /**
    774      * @param $voffset
    775      * @param $x
    776      * @param $d
    777      * @throws \Exception
    778      */
    779     public function addStructX($voffset, $x, $d)
    780     {
    781         if ($x != $d) {
    782             $this->nested($x);
    783             $this->slot($voffset);
    784         }
    785     }
    786 
    787     /**
    788      * @param $voffset
    789      * @param $x
    790      * @param $d
    791      * @throws \Exception
    792      */
    793     public function addStruct($voffset, $x, $d)
    794     {
    795         if ($x != $d) {
    796             $this->nested($x);
    797             $this->slot($voffset);
    798         }
    799     }
    800 
    801     /**
    802      * @param $voffset
    803      */
    804     public function slot($voffset)
    805     {
    806         $this->vtable[$voffset] = $this->offset();
    807     }
    808 
    809     /**
    810      * @return int
    811      * @throws \Exception
    812      */
    813     public function endObject()
    814     {
    815         if ($this->vtable == null || !$this->nested) {
    816             throw new \Exception("FlatBuffers: endObject called without startObject");
    817         }
    818 
    819         $this->addInt(0);
    820         $vtableloc = $this->offset();
    821 
    822         $i = $this->vtable_in_use -1;
    823         // Trim trailing zeroes.
    824         for (; $i >= 0 && $this->vtable[$i] == 0; $i--) {}
    825         $trimmed_size = $i + 1;
    826         for (; $i >= 0; $i--) {
    827             $off = ($this->vtable[$i] != 0) ? $vtableloc - $this->vtable[$i] : 0;
    828             $this->addShort($off);
    829         }
    830 
    831         $standard_fields = 2; // the fields below
    832         $this->addShort($vtableloc - $this->object_start);
    833         $this->addShort(($trimmed_size + $standard_fields) * Constants::SIZEOF_SHORT);
    834 
    835         // search for an existing vtable that matches the current one.
    836         $existing_vtable = 0;
    837 
    838         for ($i = 0; $i < $this->num_vtables; $i++) {
    839             $vt1 = $this->bb->capacity() - $this->vtables[$i];
    840             $vt2 = $this->space;
    841 
    842             $len = $this->bb->getShort($vt1);
    843 
    844             if ($len == $this->bb->getShort($vt2)) {
    845                 for ($j = Constants::SIZEOF_SHORT; $j < $len; $j += Constants::SIZEOF_SHORT) {
    846                     if ($this->bb->getShort($vt1 + $j) != $this->bb->getShort($vt2 + $j)) {
    847                         continue 2;
    848                     }
    849                 }
    850                 $existing_vtable = $this->vtables[$i];
    851                 break;
    852             }
    853         }
    854 
    855         if ($existing_vtable != 0) {
    856             // Found a match:
    857             // Remove the current vtable
    858             $this->space = $this->bb->capacity() - $vtableloc;
    859             $this->bb->putInt($this->space, $existing_vtable - $vtableloc);
    860         } else {
    861             // No Match:
    862             // Add the location of the current vtable to the list of vtables
    863             if ($this->num_vtables == count($this->vtables)) {
    864                 $vtables = $this->vtables;
    865                 $this->vtables = array();
    866                 // copy of
    867                 for ($i = 0; $i < count($vtables) * 2; $i++) {
    868                     $this->vtables[$i] = ($i < count($vtables)) ? $vtables[$i] : 0;
    869                 }
    870             }
    871             $this->vtables[$this->num_vtables++] = $this->offset();
    872             $this->bb->putInt($this->bb->capacity() - $vtableloc, $this->offset() - $vtableloc);
    873         }
    874 
    875         $this->nested = false;
    876         $this->vtable = null;
    877         return $vtableloc;
    878     }
    879 
    880     /**
    881      * @param $table
    882      * @param $field
    883      * @throws \Exception
    884      */
    885     public function required($table, $field)
    886     {
    887         $table_start = $this->bb->capacity() - $table;
    888         $vtable_start = $table_start - $this->bb->getInt($table_start);
    889         $ok = $this->bb->getShort($vtable_start + $field) != 0;
    890 
    891         if (!$ok) {
    892             throw new \Exception("FlatBuffers: field "  . $field  .  " must be set");
    893         }
    894     }
    895     /// @endcond
    896 
    897     /**
    898      * Finalize a buffer, pointing to the given `$root_table`.
    899      * @param $root_table An offest to be added to the buffer.
    900      * @param $file_identifier A FlatBuffer file identifier to be added to the
    901      *     buffer before `$root_table`. This defaults to `null`.
    902      * @throws InvalidArgumentException Thrown if an invalid `$identifier` is
    903      *     given, where its length is not equal to
    904      *    `Constants::FILE_IDENTIFIER_LENGTH`.
    905      */
    906     public function finish($root_table, $identifier = null)
    907     {
    908         if ($identifier == null) {
    909             $this->prep($this->minalign, Constants::SIZEOF_INT);
    910             $this->addOffset($root_table);
    911             $this->bb->setPosition($this->space);
    912         } else {
    913             $this->prep($this->minalign, Constants::SIZEOF_INT + Constants::FILE_IDENTIFIER_LENGTH);
    914             if (strlen($identifier) != Constants::FILE_IDENTIFIER_LENGTH) {
    915                 throw new \InvalidArgumentException(
    916                     sprintf("FlatBuffers: file identifier must be length %d",
    917                         Constants::FILE_IDENTIFIER_LENGTH));
    918             }
    919 
    920             for ($i = Constants::FILE_IDENTIFIER_LENGTH - 1; $i >= 0;
    921                   $i--) {
    922                 $this->addByte(ord($identifier[$i]));
    923             }
    924             $this->finish($root_table);
    925         }
    926     }
    927 
    928     /**
    929      * In order to save space, fields that are set to their default value don't
    930      * get serialized into the buffer.
    931      * @param bool $forceDefaults When set to `true`, always serializes default
    932      *     values.
    933      */
    934     public function forceDefaults($forceDefaults)
    935     {
    936         $this->force_defaults = $forceDefaults;
    937     }
    938 
    939     /**
    940      * Get the ByteBuffer representing the FlatBuffer.
    941      * @return ByteBuffer The ByteBuffer containing the FlatBuffer data.
    942      */
    943     public function dataBuffer()
    944     {
    945         return $this->bb;
    946     }
    947 
    948     /// @cond FLATBUFFERS_INTERNAL
    949     /**
    950      * @return int
    951      */
    952     public function dataStart()
    953     {
    954         return $this->space;
    955     }
    956     /// @endcond
    957 
    958     /**
    959      * Utility function to copy and return the FlatBuffer data from the
    960      * underlying ByteBuffer.
    961      * @return string A string (representing a byte[]) that contains a copy
    962      * of the FlatBuffer data.
    963      */
    964     public function sizedByteArray()
    965     {
    966         $start = $this->space;
    967         $length = $this->bb->capacity() - $this->space;
    968 
    969         $result = str_repeat("\0", $length);
    970         $this->bb->setPosition($start);
    971         $this->bb->getX($result);
    972 
    973         return $result;
    974     }
    975 }
    976 
    977 /// @}
    978