/** * Applies a series of SQL patches to the database and registers them in the db_patches table. * * @param bool $register_only Only register the patches as done, don't run their code * @throws \LemonWeb\Deployer\Exceptions\DatabaseException */ public function update($register_only = false) { $this->logger->log('Database update: ' . implode(', ', array_keys($this->sql_patch_objects)), LOG_DEBUG, true); if (!count($this->sql_patch_objects)) { return; } foreach ($this->sql_patch_objects as $filename => $sql_patch_object) { $patch_name = DatabaseHelper::getClassnameFromFilepath($filename); $patch_timestamp = DatabaseHelper::convertFilenameToDateTime($filename); // Register the patch in the db_patches table (except for the db_patches table patch itself, that wouldn't be possible yet). // Add the revert (down) code to the record so the update can be reverted when the file doesn't exist, which can happen when code is rolled back. // Also add the patch' dependencies list so depending patches won't be reverted before it is reverted first. if ('19700101000000' != $patch_timestamp) { $this->driver->query("\n INSERT INTO db_patches (\n patch_name,\n patch_timestamp,\n down_sql,\n dependencies\n )\n VALUES (\n '" . $this->driver->escape($patch_name) . "',\n '" . $this->driver->escape($patch_timestamp) . "',\n " . (trim($sql_patch_object->down()) != '' ? "'" . $this->driver->escape(trim($sql_patch_object->down())) . "'" : 'null') . ",\n " . (count($sql_patch_object->getDependencies()) > 0 ? "'" . $this->driver->escape(implode("\n", $sql_patch_object->getDependencies())) . "'" : 'null') . "\n );\n "); } // apply the patch if (!$register_only) { $this->driver->startTransaction(); $result = $this->driver->multiQuery($sql_patch_object->up()); if (false === $result) { throw new DatabaseException('Error applying patch ' . $patch_name . ': ' . $this->driver->getLastError(), 1); } $this->driver->doCommit(); } // if there were no errors, mark the patch as applied if ('19700101000000' != $patch_timestamp) { $this->driver->query("\n UPDATE db_patches\n SET applied_at = '" . $this->driver->escape($this->timestamp) . "'\n WHERE patch_name = '" . $this->driver->escape($patch_name) . "';\n "); if ($register_only) { $this->logger->log("Patch '{$filename}' registered."); } else { $this->logger->log("Patch '{$filename}' succeeded."); } } else { // the db_patches patch has no record set, insert it now $this->driver->query("\n INSERT INTO db_patches (\n patch_name,\n patch_timestamp,\n applied_at\n )\n VALUES (\n '" . $this->driver->escape($patch_name) . "',\n '" . $this->driver->escape($patch_timestamp) . "',\n '" . $this->driver->escape($this->timestamp) . "'\n );\n "); $this->logger->log("Patch '{$filename}' succeeded."); } } }
public function key() { /** @var \DirectoryIterator $this */ return Helper::getClassnameFromFilepath($this->getFilename()); }
/** * Returns all patches that have already been applied and their dependencies. * * @throws \LemonWeb\Deployer\Exceptions\DeployException When crashed patches (during patching or reverting) are found * @return array [array with performed patches, array with their dependencies] */ protected function findPerformedSQLPatches() { $this->logger->log('findPerformedSQLPatches', LOG_DEBUG); $applied_patches = array(); $crashed_patches = array(); $reverted_patches = array(); $dependencies = array(); $output = array(); $this->query(' SELECT patch_name, patch_timestamp, dependencies, applied_at, reverted_at FROM db_patches ORDER BY patch_timestamp, id ', $output); foreach ($output as $patch_record) { list($patch_name, $patch_timestamp, $patch_dependencies, $applied_at, $reverted_at) = explode("\t", $patch_record); if ('NULL' == $applied_at) { // this patch crashed while being applied $crashed_patches[$patch_name] = $patch_name; } elseif ('NULL' != $reverted_at) { // this patch crashed while being reverted $reverted_patches[$patch_name] = $patch_name; } else { // this patch was succesfully applied $applied_patches[$patch_name] = $applied_at; if ('NULL' != $patch_dependencies) { $dependency_names = array(); $patch_dependency_names = (array) explode('\\n', $patch_dependencies); foreach ($patch_dependency_names as $dependency_name) { $dependency_names[Helper::convertFilenameToDateTime($dependency_name)] = $dependency_name; } $dependencies[$patch_name] = $dependency_names; } } } if (!empty($crashed_patches)) { if (count($crashed_patches) > 1) { throw new DeployException('Patches ' . implode(', ', $crashed_patches) . ' have crashed at previous update !'); } else { throw new DeployException('Patch ' . implode(', ', $crashed_patches) . ' has crashed at previous update !'); } } if (!empty($reverted_patches)) { if (count($reverted_patches) > 1) { throw new DeployException('Patches ' . implode(', ', $reverted_patches) . ' have crashed at previous rollback !'); } else { throw new DeployException('Patch ' . implode(', ', $reverted_patches) . ' has crashed at previous rollback !'); } } return array($applied_patches, $dependencies); }