RebelCode/migrations

View on GitHub
src/AbstractDatabase.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace RebelCode\Migrations;

use ByJG\DbMigration\Database\AbstractDatabase as ByjgAbstractDatabase;
use ByJG\DbMigration\Exception\DatabaseNotVersionedException;
use Dhii\Util\String\StringableInterface as Stringable;
use InvalidArgumentException;
use PDOException;

/**
 * Abstract functionality for databases.
 *
 * Extends Byjg's class to add customizable table and column names.
 *
 * @since [*next-version*]
 */
abstract class AbstractDatabase extends ByjgAbstractDatabase
{
    /**
     * The placeholder to use for the database name.
     *
     * @since [*next-version*]
     */
    const PLACEHOLDER_DATABASE = '{db}';

    /**
     * The placeholder to use for the version log table name.
     *
     * @since [*next-version*]
     */
    const PLACEHOLDER_LOG_TABLE = '{lt}';

    /**
     * The placeholder to use for the version column name in the version log table.
     *
     * @since [*next-version*]
     */
    const PLACEHOLDER_LOG_VERSION_COLUMN = '{lt_version}';

    /**
     * The placeholder to use for the status column name in the version log table.
     *
     * @since [*next-version*]
     */
    const PLACEHOLDER_LOG_STATUS_COLUMN = '{lt_status}';

    /**
     * The version status used to indicate completed migrations.
     *
     * @since [*next-version*]
     */
    const STATUS_COMPLETE = 'complete';

    /**
     * The version status used to indicated partial migrations.
     *
     * @since [*next-version*]
     */
    const STATUS_PARTIAL = 'partial';

    /**
     * The version status used to indicated an unknown migration state, typically used for the base version.
     *
     * @since [*next-version*]
     */
    const STATUS_UNKNOWN = 'unknown';

    /**
     * Formats a query string.
     *
     * @since [*next-version*]
     *
     * @param string $sql  The SQL query string to format.
     * @param array  $args Optional arguments to interpolate into the SQL string.
     *
     * @return string The formatted SQL query.
     */
    protected function _formatSql($sql, array $args = [])
    {
        $sql = $this->_normalizeString($sql);
        $sql = vsprintf($sql, $args);
        $sql = str_replace(static::PLACEHOLDER_DATABASE, $this->_getDatabaseName(), $sql);
        $sql = str_replace(static::PLACEHOLDER_LOG_TABLE, $this->_getLogTableName(), $sql);
        $sql = str_replace(static::PLACEHOLDER_LOG_VERSION_COLUMN, $this->_getLogTableVersionColumn(), $sql);
        $sql = str_replace(static::PLACEHOLDER_LOG_STATUS_COLUMN, $this->_getLogTableStatusColumn(), $sql);

        return $sql;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function getVersion()
    {
        $result     = [];
        $versionCol = $this->_getLogTableVersionColumn();
        $statusCol  = $this->_getLogTableStatusColumn();

        try {
            $result[$versionCol] = $this->getDbDriver()->getScalar(
                $this->_formatSql(
                    'SELECT %1$s FROM %2$s',
                    [
                        static::PLACEHOLDER_LOG_VERSION_COLUMN,
                        static::PLACEHOLDER_LOG_TABLE,
                    ]
                )
            );
        } catch (PDOException $ex) {
            throw new DatabaseNotVersionedException(
                'This database does not have a migration version'
            );
        }

        try {
            $result[$statusCol] = $this->getDbDriver()->getScalar(
                $this->_formatSql(
                    'SELECT %1$s FROM %2$s',
                    [
                        static::PLACEHOLDER_LOG_STATUS_COLUMN,
                        static::PLACEHOLDER_LOG_TABLE,
                    ]
                )
            );
        } catch (PDOException $ex) {
            throw new DatabaseNotVersionedException(
                'This database does not have a migration version'
            );
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function setVersion($version, $status)
    {
        $this->getDbDriver()->execute(
            $this->_formatSql(
                'UPDATE %1$s SET %2$s = :version, %3$s = :status',
                [
                    static::PLACEHOLDER_LOG_TABLE,
                    static::PLACEHOLDER_LOG_VERSION_COLUMN,
                    static::PLACEHOLDER_LOG_STATUS_COLUMN,
                ]
            ),
            [
                'version' => $version,
                'status'  => $status,
            ]
        );
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function checkExistsVersion()
    {
        // Get the version to check if exists
        $versionInfo = $this->getVersion();
        $versionCol  = $this->_getLogTableVersionColumn();

        if (empty($versionInfo[$versionCol])) {
            $this->getDbDriver()->execute(
                $this->_formatSql(
                    'INSERT INTO %1$s VALUES(0, \'%2$s\')',
                    [
                        static::PLACEHOLDER_LOG_TABLE,
                        static::STATUS_UNKNOWN,
                    ]
                )
            );
        }
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function createVersion()
    {
        $this->executeSql(
            $this->_formatSql(
                'DROP TABLE IF EXISTS %s',
                [
                    static::PLACEHOLDER_LOG_TABLE,
                ]
            )
        );

        $this->executeSql(
            $this->_formatSql(
                'CREATE TABLE IF NOT EXISTS %1$s (%2$s int, %3$s varchar(20))',
                [
                    static::PLACEHOLDER_LOG_TABLE,
                    static::PLACEHOLDER_LOG_VERSION_COLUMN,
                    static::PLACEHOLDER_LOG_STATUS_COLUMN,
                ]
            )
        );

        $this->checkExistsVersion();
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function updateVersionTable()
    {
        $currentVersion = $this->getDbDriver()->getScalar(
            $this->_formatSql(
                'SELECT %1$s FROM %2$s',
                [
                    static::PLACEHOLDER_LOG_VERSION_COLUMN,
                    static::PLACEHOLDER_LOG_TABLE,
                ]
            )
        );
        $this->createVersion();
        $this->setVersion($currentVersion, static::STATUS_UNKNOWN);
    }

    /**
     * Retrieves the name of the database.
     *
     * @since [*next-version*]
     *
     * @return string The name of the database.
     */
    abstract protected function _getDatabaseName();

    /**
     * Retrieves the name of the table where migration are logged.
     *
     * @since [*next-version*]
     *
     * @return string The name of the version table.
     */
    abstract protected function _getLogTableName();

    /**
     * Retrieves the name of the version column in the version log table.
     *
     * @since [*next-version*]
     *
     * @return string The name of the version column.
     */
    abstract protected function _getLogTableVersionColumn();

    /**
     * Retrieves the name of the status column in the version log table.
     *
     * @since [*next-version*]
     *
     * @return string The name of the status column.
     */
    abstract protected function _getLogTableStatusColumn();

    /**
     * Normalizes a value to its string representation.
     *
     * The values that can be normalized are any scalar values, as well as
     * {@see StringableInterface).
     *
     * @since [*next-version*]
     *
     * @param Stringable|string|int|float|bool $subject The value to normalize to string.
     *
     * @throws InvalidArgumentException If the value cannot be normalized.
     *
     * @return string The string that resulted from normalization.
     */
    abstract protected function _normalizeString($subject);
}