/**
  * This evenement describe the structure of a table.
  * It's send before a change append on a table.
  * A end user of the lib should have no usage of this
  *
  * @return TableMapDTO
  * @throws BinaryDataReaderException
  * @throws ConfigException
  */
 public function makeTableMapDTO()
 {
     $data = [];
     $data['table_id'] = $this->binaryDataReader->readTableId();
     $this->binaryDataReader->advance(2);
     $data['schema_length'] = $this->binaryDataReader->readUInt8();
     $data['schema_name'] = $this->binaryDataReader->read($data['schema_length']);
     if ([] !== $this->config->getDatabasesOnly() && !in_array($data['schema_name'], $this->config->getDatabasesOnly(), true)) {
         return null;
     }
     $this->binaryDataReader->advance(1);
     $data['table_length'] = $this->binaryDataReader->readUInt8();
     $data['table_name'] = $this->binaryDataReader->read($data['table_length']);
     if ([] !== $this->config->getTablesOnly() && !in_array($data['table_name'], $this->config->getTablesOnly(), true)) {
         return null;
     }
     $this->binaryDataReader->advance(1);
     $data['columns_amount'] = $this->binaryDataReader->readCodedBinary();
     $data['column_types'] = $this->binaryDataReader->read($data['columns_amount']);
     // automatically clear table cache to save memory
     if (count(self::$tableMapCache) > $this->config->getTableCacheSize()) {
         self::$tableMapCache = array_slice(self::$tableMapCache, ceil($this->config->getTableCacheSize() / 2), null, true);
     }
     // already in cache don't parse
     if (isset(self::$tableMapCache[$data['table_id']])) {
         return new TableMapDTO($this->eventInfo, self::$tableMapCache[$data['table_id']]);
     }
     $this->binaryDataReader->readCodedBinary();
     $columns = $this->repository->getFields($data['schema_name'], $data['table_name']);
     $fields = [];
     // if you drop tables and parse of logs you will get empty scheme
     if (!empty($columns)) {
         $columnLength = strlen($data['column_types']);
         for ($i = 0; $i < $columnLength; $i++) {
             // this a dirty hack to prevent row events containing columns which have been dropped
             if (!isset($columns[$i])) {
                 $columns[$i] = ['COLUMN_NAME' => 'DROPPED_COLUMN_' . $i, 'COLLATION_NAME' => null, 'CHARACTER_SET_NAME' => null, 'COLUMN_COMMENT' => null, 'COLUMN_TYPE' => 'BLOB', 'COLUMN_KEY' => '', 'REFERENCED_TABLE_NAME' => '', 'REFERENCED_COLUMN_NAME' => ''];
                 $type = ConstFieldType::IGNORE;
             } else {
                 $type = ord($data['column_types'][$i]);
             }
             $fields[$i] = Columns::parse($type, $columns[$i], $this->binaryDataReader);
         }
     }
     // save to cache
     self::$tableMapCache[$data['table_id']] = new TableMap($data['schema_name'], $data['table_name'], $data['table_id'], $data['columns_amount'], $fields);
     return new TableMapDTO($this->eventInfo, self::$tableMapCache[$data['table_id']]);
 }
 /**
  * @throws BinaryDataReaderException
  * @throws BinLogException
  * @throws ConfigException
  * @throws EventException
  * @throws MySQLReplicationException
  * @throws JsonBinaryDecoderException
  */
 public function consume()
 {
     $binaryDataReader = $this->packageService->makePackageFromBinaryData($this->socketConnect->getPacket(false));
     // "ok" value on first byte continue
     $binaryDataReader->advance(1);
     // decode all events data
     $eventInfo = new EventInfo($binaryDataReader->readInt32(), $binaryDataReader->readUInt8(), $binaryDataReader->readInt32(), $binaryDataReader->readInt32(), $binaryDataReader->readInt32(), $binaryDataReader->readUInt16(), $this->socketConnect->getCheckSum());
     if (ConstEventType::TABLE_MAP_EVENT === $eventInfo->getType()) {
         $event = $this->rowEventService->makeRowEvent($binaryDataReader, $eventInfo)->makeTableMapDTO();
         if ($event !== null) {
             $this->eventDispatcher->dispatch(ConstEventsNames::TABLE_MAP, $event);
         }
     } else {
         if ([] !== $this->config->getEventsOnly() && !in_array($eventInfo->getType(), $this->config->getEventsOnly(), true)) {
             return;
         }
         if (in_array($eventInfo->getType(), $this->config->getEventsIgnore(), true)) {
             return;
         }
         if (in_array($eventInfo->getType(), [ConstEventType::UPDATE_ROWS_EVENT_V1, ConstEventType::UPDATE_ROWS_EVENT_V2], true)) {
             $event = $this->rowEventService->makeRowEvent($binaryDataReader, $eventInfo)->makeUpdateRowsDTO();
             if ($event !== null) {
                 $this->eventDispatcher->dispatch(ConstEventsNames::UPDATE, $event);
             }
         } elseif (in_array($eventInfo->getType(), [ConstEventType::WRITE_ROWS_EVENT_V1, ConstEventType::WRITE_ROWS_EVENT_V2], true)) {
             $event = $this->rowEventService->makeRowEvent($binaryDataReader, $eventInfo)->makeWriteRowsDTO();
             if ($event !== null) {
                 $this->eventDispatcher->dispatch(ConstEventsNames::WRITE, $event);
             }
         } elseif (in_array($eventInfo->getType(), [ConstEventType::DELETE_ROWS_EVENT_V1, ConstEventType::DELETE_ROWS_EVENT_V2], true)) {
             $event = $this->rowEventService->makeRowEvent($binaryDataReader, $eventInfo)->makeDeleteRowsDTO();
             if ($event !== null) {
                 $this->eventDispatcher->dispatch(ConstEventsNames::DELETE, $event);
             }
         } elseif (ConstEventType::XID_EVENT === $eventInfo->getType()) {
             $this->eventDispatcher->dispatch(ConstEventsNames::XID, (new XidEvent($eventInfo, $binaryDataReader))->makeXidDTO());
         } elseif (ConstEventType::ROTATE_EVENT === $eventInfo->getType()) {
             $this->eventDispatcher->dispatch(ConstEventsNames::ROTATE, (new RotateEvent($eventInfo, $binaryDataReader))->makeRotateEventDTO());
         } elseif (ConstEventType::GTID_LOG_EVENT === $eventInfo->getType()) {
             $this->eventDispatcher->dispatch(ConstEventsNames::GTID, (new GtidEvent($eventInfo, $binaryDataReader))->makeGTIDLogDTO());
         } elseif (ConstEventType::QUERY_EVENT === $eventInfo->getType()) {
             $this->eventDispatcher->dispatch(ConstEventsNames::QUERY, (new QueryEvent($eventInfo, $binaryDataReader))->makeQueryDTO());
         } elseif (ConstEventType::MARIA_GTID_EVENT === $eventInfo->getType()) {
             $this->eventDispatcher->dispatch(ConstEventsNames::MARIADB_GTID, (new MariaDbGtidEvent($eventInfo, $binaryDataReader))->makeMariaDbGTIDLogDTO());
         }
     }
 }
 /**
  * @param Config $config
  * @throws MySQLReplicationException
  * @throws DBALException
  * @throws ConfigException
  * @throws BinLogException
  */
 public function __construct(Config $config)
 {
     $config->validate();
     $this->connection = DriverManager::getConnection(['user' => $config->getUser(), 'password' => $config->getPassword(), 'host' => $config->getHost(), 'port' => $config->getPort(), 'driver' => 'pdo_mysql', 'charset' => $config->getCharset()]);
     $this->repository = new MySQLRepository($this->connection);
     $this->gtiService = new GtidService();
     $this->binLogAuth = new BinLogAuth();
     $this->socketConnect = new BinLogSocketConnect($config, $this->repository, $this->binLogAuth, $this->gtiService);
     $this->socketConnect->connectToStream();
     $this->jsonBinaryDecoderFactory = new JsonBinaryDecoderFactory();
     $this->rowEventService = new RowEventService($config, $this->repository, $this->jsonBinaryDecoderFactory);
     $this->binaryDataReaderService = new BinaryDataReaderService();
     $this->eventDispatcher = new EventDispatcher();
     $this->event = new Event($config, $this->socketConnect, $this->binaryDataReaderService, $this->rowEventService, $this->eventDispatcher);
 }
 /**
  * @see https://dev.mysql.com/doc/internals/en/com-binlog-dump.html
  * @throws BinLogException
  */
 private function setBinLogDump()
 {
     $binFilePos = $this->config->getBinLogPosition();
     $binFileName = $this->config->getBinLogFileName();
     if ('' !== $this->config->getMariaDbGtid()) {
         $this->execute('SET @mariadb_slave_capability = 4');
         $this->execute('SET @slave_connect_state = \'' . $this->config->getMariaDbGtid() . '\'');
         $this->execute('SET @slave_gtid_strict_mode = 0');
         $this->execute('SET @slave_gtid_ignore_duplicates = 0');
     }
     if (0 === $binFilePos || '' === $binFileName) {
         $master = $this->repository->getMasterStatus();
         $binFilePos = $master['Position'];
         $binFileName = $master['File'];
     }
     $prelude = pack('i', strlen($binFileName) + 11) . chr(ConstCommand::COM_BINLOG_DUMP);
     $prelude .= pack('I', $binFilePos);
     $prelude .= pack('v', 0);
     $prelude .= pack('I', $this->config->getSlaveId());
     $prelude .= $binFileName;
     $this->writeToSocket($prelude);
     $this->getPacket();
 }