tables = (array) $tables; $this->numTables = count($this->tables); if ($this->setStoreProgressFile($storeProgressFile) == false) { throw new Exception('Can\t set progress file'); } $this->setPosition(); if (is_callable($startTableCallback)) { $this->startTableCallback = $startTableCallback; } if (is_callable($endTableCallback)) { $this->endTableCallback = $endTableCallback; } } /** * set current position if progress file exists or rewrind the iterator * * @return void */ protected function setPosition() { if (!$this->isStoreProgress) { $this->rewind(); return; } DUP_PRO_Log::trace("LOAD DATA DATABASE ITERATOR"); if (($content = file_get_contents($this->storeProgressFile)) === false) { throw new Exception('Can\'t read database store progress file'); } if (strlen($content) === 0) { throw new Exception('Store progress file is empty'); } if (($data = json_decode($content)) === null) { throw new Exception('Can\'t decode json progress data content: ' . SnapLog::v2str($content)); } $this->tableIndex = $data[0]; $this->tableOffset = $data[1]; $this->lastIndexOffset = is_scalar($data[2]) ? $data[2] : (array) $data[2]; $this->totalRowsOffset = $data[3]; $this->lastIsCompleteInsert = $data[4]; $this->tableRows = $data[5]; $this->fileSize = $data[6]; $this->isValid = $data[7]; DUP_PRO_Log::trace("SET POSITION TABLE INDEX " . $this->tableIndex . " OFFSET INDEX " . SnapLog::v2str($this->lastIndexOffset)); } /** * save current position in progress file if initialized * * @return bool */ protected function saveCounterFile() { $this->storePointerRowCount = 0; if (!$this->isStoreProgress) { return true; } $data = array( $this->tableIndex, $this->tableOffset, $this->lastIndexOffset, $this->totalRowsOffset, $this->lastIsCompleteInsert, $this->tableRows, $this->fileSize, $this->isValid, ); if (($dataEncoded = json_encode($data)) === false) { throw new Exception('Can\'t encode database iterator pointer DATA: ' . SnapLog::v2str($data)); } // file_put_content is less optimized than fopen, // fwrite but in some serve keep the hadler file open and do fseek in massive way rarely generate corrupted files. // So writing and closing the file is the safest method. if (file_put_contents($this->storeProgressFile, $dataEncoded) !== strlen($dataEncoded)) { throw new Exception('Can\'t write database store progress file'); } return true; } /** * rewind current iterator (reset all offset and table counts) * * @return void */ #[\ReturnTypeWillChange] public function rewind() { DUP_PRO_Log::infoTrace("REWIND DATABASE ITERATOR"); $this->tableIndex = -1; $this->tableOffset = 0; $this->lastIndexOffset = 0; $this->totalRowsOffset = 0; $this->lastIsCompleteInsert = true; $this->tableRows = 0; $this->fileSize = 0; $this->storePointerRowCount = 0; $this->next(); } /** * remove store progress file * * @return void */ public function removeCounterFile() { if (file_exists($this->storeProgressFile)) { unlink($this->storeProgressFile); } $this->isStoreProgress = false; $this->storeProgressFile = null; } /** * open store pregress file, if don't exists create and initialize it. * * @param string $storeProgressFile path to store progress file * * @return boolean */ public function setStoreProgressFile($storeProgressFile = null) { $this->storeProgressFile = null; $this->isStoreProgress = false; if (empty($storeProgressFile)) { return true; } if (($fileExists = file_exists($storeProgressFile))) { if (!is_writable($storeProgressFile)) { return false; } } elseif (!is_writable(dirname($storeProgressFile))) { return false; } $this->storeProgressFile = $storeProgressFile; $this->isStoreProgress = true; if (!$fileExists) { $this->rewind(); } return true; } /** * next element (table) of iterator, put all table offsets at 0 and count tableRows * * If set call endTableCallback and startTableCallback * * @return boolean */ #[\ReturnTypeWillChange] public function next() { if ($this->tableIndex >= 0 && is_callable($this->endTableCallback)) { call_user_func($this->endTableCallback, $this); } $this->tableOffset = 0; $this->lastIndexOffset = 0; $this->tableRows = 0; $this->tableIndex++; if (($this->isValid = ($this->tableIndex < $this->numTables))) { $res = DUP_PRO_DB::getTablesRows($this->current()); $this->tableRows = $res[$this->current()]; if (is_callable($this->startTableCallback)) { call_user_func($this->startTableCallback, $this); } DUP_PRO_Log::infoTrace("INSERT ROWS TABLE[INDEX:" . $this->tableIndex . "] " . $this->tables[$this->tableIndex] . " NUM ROWS: " . $this->tableRows); } $this->saveCounterFile(); return $this->isValid; } /** * increment current table offsets and update store process file if exists * * @param mixed $lastIndexOffset last index offset selected, can be a primary key or mixed unique key also composed * @param int $addFileSize add file size to current file size * * @return int return total rows parsed count */ public function nextRow($lastIndexOffset = 0, $addFileSize = 0) { $this->totalRowsOffset++; $this->tableOffset++; $this->lastIndexOffset = $lastIndexOffset; $this->lastIsCompleteInsert = false; $this->fileSize += $addFileSize; $this->storePointerRowCount++; if ($this->storePointerRowCount >= self::UPDATE_POINTER_FILE_EACH_ROWS) { $this->saveCounterFile(); } return $this->totalRowsOffset; } /** * set last is complete inster at true and save it in store preocess file. * * @param int $addFileSize size to add * * @return void */ public function setLastIsCompleteInsert($addFileSize = 0) { $this->fileSize += $addFileSize; $this->lastIsCompleteInsert = true; $this->saveCounterFile(); } /** * @param int $fileSize Size to add * * @return void */ public function addFileSize($fileSize = 0) { $this->fileSize += $fileSize; $this->saveCounterFile(); } /** * @return bool */ public function isCurrentTableOffsetValid() { return $this->tableOffset < $this->tableRows; } /** * * @return bool */ #[\ReturnTypeWillChange] public function valid() { return $this->isValid; } /** * * @return string|bool // current table name or false if isn\'t valid */ #[\ReturnTypeWillChange] public function current() { return $this->isValid ? $this->tables[$this->tableIndex] : false; } /** * * @return int // table rows of current table */ public function getCurrentRows() { return $this->tableRows; } /** * * @return int // current offset of current table */ public function getCurrentOffset() { return $this->tableOffset; } /** * * @return mixed // last index offset selecte, can be a primary key or mixed unique key also composed */ public function getLastIndexOffset() { return $this->lastIndexOffset; } /** * * @return int // total rows dumped */ public function getTotalsRowsOffset() { return $this->totalRowsOffset; } /** * * @return int // stored file size */ public function getFileSize() { return $this->fileSize; } /** * * @return bool // return true if the last inserted sub loop is completed */ public function lastIsCompleteInsert() { return $this->lastIsCompleteInsert; } /** * * @return int // current table index */ #[\ReturnTypeWillChange] public function key() { return $this->tableIndex; } /** * * @return int // num table to process */ public function count() { return $this->numTables; } }