manager = $manager;
$this->updater = new DatabaseUpdater();
}
public function update($extension, $stopOnVersion = null)
{
$name = $this->manager->getName($extension);
if (! $this->hasVersionFile($name)) {
return false;
}
$currentVersion = $this->getLatestFileVersion($name);
$databaseVersion = $this->getDatabaseVersion($name);
if ($currentVersion === $databaseVersion) {
$this->note('- Nothing to update.');
return;
}
$this->manager->get($extension)->update($databaseVersion, $stopOnVersion ?: $currentVersion);
$newUpdates = $this->getNewFileVersions($name, $databaseVersion);
foreach ($newUpdates as $version => $details) {
$this->applyExtensionUpdate($name, $version, $details);
if ($stopOnVersion === $version) {
return true;
}
}
return true;
}
public function listNewVersions($extension)
{
$name = $this->manager->getName($extension);
if (! $this->hasVersionFile($name)) {
return [];
}
return $this->getNewFileVersions($name, $this->getDatabaseVersion($name));
}
protected function applyExtensionUpdate($name, $version, $details)
{
[$comments, $scripts] = $this->extractScriptsAndComments($details);
foreach ($scripts as $script) {
if ($this->hasDatabaseHistory($name, $version, $script)) {
continue;
}
$this->applyDatabaseScript($name, $version, $script);
}
if (! $this->hasDatabaseHistory($name, $version)) {
foreach ($comments as $comment) {
$this->applyDatabaseComment($name, $version, $comment);
$this->note(sprintf('- v%s: %s', $version, $comment));
}
}
$this->setDatabaseVersion($name, $version);
}
public function remove($extension, $stopOnVersion = null, $stopCurrentVersion = false)
{
$name = $this->manager->getName($extension);
if (! $this->hasVersionFile($name)) {
return false;
}
$extensionHistory = $this->getDatabaseHistory($name);
$extensionHistory = array_reverse($extensionHistory);
$stopOnNextVersion = false;
$newExtensionVersion = null;
try {
foreach ($extensionHistory as $history) {
if ($stopCurrentVersion && $stopOnVersion === $history->version) {
$newExtensionVersion = $history->version;
break;
}
if ($stopOnNextVersion && $history->version !== $stopOnVersion) {
$newExtensionVersion = $history->version;
break;
}
if ($history->type == static::HISTORY_TYPE_COMMENT) {
$this->removeDatabaseComment($name, $history->version);
} elseif ($history->type == static::HISTORY_TYPE_SCRIPT) {
$this->removeDatabaseScript($name, $history->version, $history->detail);
}
if ($stopOnVersion === $history->version) {
$stopOnNextVersion = true;
}
}
} catch (\Throwable $exception) {
$lastHistory = $this->getLastHistory($name);
if ($lastHistory) {
$this->setDatabaseVersion($name, $lastHistory->version);
}
throw $exception;
}
$this->setDatabaseVersion($name, $newExtensionVersion);
if (isset($this->fileVersions[$name])) {
unset($this->fileVersions[$name]);
}
if (isset($this->databaseVersions[$name])) {
unset($this->databaseVersions[$name]);
}
if (isset($this->databaseHistory[$name])) {
unset($this->databaseHistory[$name]);
}
return true;
}
public function purge($name)
{
$name = $this->manager->getName($name);
$versions = Extension::query()->where('name', $name);
if ($countVersions = $versions->count()) {
$versions->delete();
}
$history = ExtensionHistory::query()->where('name', $name);
if ($countHistory = $history->count()) {
$history->delete();
}
return $countHistory + $countVersions;
}
protected function getLatestFileVersion($name)
{
$versionInfo = $this->getFileVersions($name);
if (! $versionInfo) {
return static::NO_VERSION_VALUE;
}
return trim(key(array_slice($versionInfo, -1, 1)));
}
public function getNewFileVersions($name, $version = null)
{
$name = $this->manager->getName($name);
if ($version === null) {
$version = static::NO_VERSION_VALUE;
}
$versions = $this->getFileVersions($name);
$position = array_search($version, array_keys($versions));
return array_slice($versions, ++$position);
}
public function getFileVersions($name)
{
$name = $this->manager->getName($name);
if ($this->fileVersions !== null && array_key_exists($name, $this->fileVersions)) {
return $this->fileVersions[$name];
}
$versionInfo = (array) $this->parseVersionFile($this->getVersionFile($name));
if ($versionInfo) {
uksort($versionInfo, function ($a, $b) {
return version_compare($a, $b);
});
}
return $this->fileVersions[$name] = $versionInfo;
}
protected function parseVersionFile($file)
{
if ($file && is_file($file)) {
return include $file;
}
}
protected function getVersionFile($name)
{
return $this->manager->path($name, 'version.php');
}
protected function hasVersionFile($name)
{
$versionFile = $this->getVersionFile($name);
return $versionFile && is_file($versionFile);
}
protected function getDatabaseVersion($name)
{
if ($this->databaseVersions === null) {
$this->databaseVersions = Extension::query()->pluck('version', 'name');
}
if (! isset($this->databaseVersions[$name])) {
$this->databaseVersions[$name] =
Extension::query()
->where('name', $name)
->value('version');
}
return $this->databaseVersions[$name] ?? static::NO_VERSION_VALUE;
}
protected function setDatabaseVersion($name, $version = null)
{
$currentVersion = $this->getDatabaseVersion($name);
if ($version && ! $currentVersion) {
Extension::query()->create([
'name' => $name,
'version' => $version,
]);
} elseif ($version && $currentVersion) {
Extension::query()->where('name', $name)->update([
'version' => $version,
'updated_at' => new Carbon,
]);
} elseif ($currentVersion) {
Extension::query()->where('name', $name)->delete();
}
$this->databaseVersions[$name] = $version;
}
protected function applyDatabaseComment($name, $version, $comment)
{
ExtensionHistory::query()->create([
'name' => $name,
'type' => static::HISTORY_TYPE_COMMENT,
'version' => $version,
'detail' => $comment,
]);
}
protected function removeDatabaseComment($name, $version)
{
ExtensionHistory::query()
->where('name', $name)
->where('type', static::HISTORY_TYPE_COMMENT)
->where('version', $version)
->delete();
}
protected function applyDatabaseScript($name, $version, $script)
{
$updateFile = $this->manager->path($name, 'updates/'.$script);
if (! is_file($updateFile)) {
$this->note(sprintf('- v%s: Migration file "%s" not found', $version, $script));
return;
}
$this->updater->setUp($this->resolveUpdater($name, $updateFile), function () use ($name, $version, $script) {
ExtensionHistory::query()->create([
'name' => $name,
'type' => static::HISTORY_TYPE_SCRIPT,
'version' => $version,
'detail' => $script,
]);
});
$this->note(sprintf('- v%s: Migrated %s', $version, $script));
}
protected function resolveUpdater($name, $updateFile)
{
$updater = $this->updater->resolve($updateFile);
if (method_exists($updater, 'setExtension')) {
$updater->setExtension($this->manager->get($name));
}
return $updater;
}
protected function removeDatabaseScript($name, $version, $script)
{
$updateFile = $this->manager->path($name, 'updates/'.$script);
$this->updater->packDown($this->resolveUpdater($name, $updateFile), function () use ($name, $version, $script) {
ExtensionHistory::query()
->where('name', $name)
->where('type', static::HISTORY_TYPE_SCRIPT)
->where('version', $version)
->where('detail', $script)
->delete();
});
}
protected function getDatabaseHistory($name)
{
if ($this->databaseHistory !== null && array_key_exists($name, $this->databaseHistory)) {
return $this->databaseHistory[$name];
}
$historyInfo = ExtensionHistory::query()
->where('name', $name)
->orderBy('id')
->get()
->all();
return $this->databaseHistory[$name] = $historyInfo;
}
protected function getLastHistory($name)
{
return ExtensionHistory::query()
->where('name', $name)
->orderByDesc('id')
->first();
}
protected function hasDatabaseHistory($name, $version, $script = null)
{
$historyInfo = $this->getDatabaseHistory($name);
if (! $historyInfo) {
return false;
}
foreach ($historyInfo as $history) {
if ($history->version != $version) {
continue;
}
if ($history->type == static::HISTORY_TYPE_COMMENT && ! $script) {
return true;
}
if ($history->type == static::HISTORY_TYPE_SCRIPT && $history->detail == $script) {
return true;
}
}
return false;
}
protected function extractScriptsAndComments($details): array
{
$details = (array) $details;
$fileNamePattern = "/^[a-z0-9\_\-\.\/\\\]+\.php$/i";
$comments = array_values(array_filter($details, function ($detail) use ($fileNamePattern) {
return ! preg_match($fileNamePattern, $detail);
}));
$scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) {
return preg_match($fileNamePattern, $detail);
}));
return [$comments, $scripts];
}
public function getCurrentVersion($extension): string
{
return $this->getDatabaseVersion($this->manager->getName($extension));
}
public function hasDatabaseVersion($extension, string $version): bool
{
$name = $this->manager->getName($extension);
$histories = $this->getDatabaseHistory($name);
foreach ($histories as $history) {
if ($history->version === $version) {
return true;
}
}
return false;
}
public function getCurrentVersionNote($extension): string
{
$name = $this->manager->getName($extension);
$histories = $this->getDatabaseHistory($name);
$lastHistory = Arr::last(Arr::where($histories, function ($history) {
return $history->type === static::HISTORY_TYPE_COMMENT;
}));
return $lastHistory ? $lastHistory->detail : '';
}
}