OpenXE/classes/Components/SchemaCreator/LineGenerator/Common/ColumnLineGenerator.php
2021-05-21 08:49:41 +02:00

421 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace Xentral\Components\SchemaCreator\LineGenerator\Common;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Components\SchemaCreator\Exception\LineGeneratorException;
use Xentral\Components\SchemaCreator\Interfaces\CharTypeInterface;
use Xentral\Components\SchemaCreator\Interfaces\DecimalTypeInterface;
use Xentral\Components\SchemaCreator\Interfaces\EnumAndSetInterface;
use Xentral\Components\SchemaCreator\Interfaces\IntegerTypeInterface;
use Xentral\Components\SchemaCreator\Interfaces\ColumnInterface;
use Xentral\Components\SchemaCreator\Interfaces\NumericTypeInterface;
use Xentral\Components\SchemaCreator\Interfaces\ColumnTextInterface;
use Xentral\Components\SchemaCreator\Type\Datetime;
use Xentral\Components\SchemaCreator\Type\Timestamp;
use Xentral\Components\SchemaCreator\Type\Year;
final class ColumnLineGenerator
{
/** @var Database $db */
private $db;
/**
* @param Database $database
*/
public function __construct(Database $database)
{
$this->db = $database;
}
/**
* @param ColumnInterface $column
*
* @return string
*/
private function generateType(ColumnInterface $column): string
{
if ($column instanceof IntegerTypeInterface) {
$spec = $column->isUnsigned() ? NumericTypeInterface::NON_NEGATIV : NumericTypeInterface::WITH_NEGATIV;
if ($column instanceof DecimalTypeInterface) {
return sprintf(
'%s(%d, %d) %s',
$column->getType(),
$column->getLength(),
$column->getDecimals(),
$spec
);
}
return sprintf('%s(%d) %s', $column->getType(), $column->getLength(), $spec);
}
if ($column instanceof CharTypeInterface || $column instanceof Year) {
return sprintf('%s(%d)', $column->getType(), $column->getLength());
}
if ($column instanceof EnumAndSetInterface) {
return sprintf('%s(%s)', $column->getType(), $column->getReferences());
}
return $column->getType();
}
/**
* @param ColumnInterface $column
*
* @throws EscapingException
*
* @return string
*/
public function generateLine(ColumnInterface $column): string
{
$line = [];
$line[] = $this->db->escapeIdentifier($column->getField());
$line[] = $this->generateType($column);
if ($defaultLine = $this->getLineDefault($column)) {
$line[] = $defaultLine;
}
if ($charset = $this->generateCharset($column)) {
$line[] = $charset;
}
if ($collate = $this->generateCollate($column)) {
$line[] = $collate;
}
if ($comment = $this->generateComment($column)) {
$line[] = $comment;
}
$outLine = trim(implode(' ', $line));
if ($this->isAutoIncrementField($column)) {
$outLine .= ' AUTO_INCREMENT';
}
return $outLine;
}
/**
* @param $column
*
* @throws EscapingException
*
* @return string|null
*/
private function generateComment($column): ?string
{
return $this->hasOption('comment', $column) ? sprintf(
"COMMENT %s",
$this->db->escapeString($this->getOption('comment', $column))
) : null;
}
/**
* @param $column
*
* @throws EscapingException
*
* @return string|null
*/
private function generateCharset($column): ?string
{
return $this->hasOption('charset', $column) ? sprintf(
"CHARACTER SET %s",
$this->db->escapeString(
$this->getOption('charset', $column)
)
) : null;
}
/**
* @param $column
*
* @throws EscapingException
*
* @return string|null
*/
private function generateCollate($column): ?string
{
return $this->hasOption('collate', $column) ? sprintf(
'COLLATE %s',
$this->db->escapeString(
$this->getOption('collate', $column)
)
) : null;
}
/**
* @param ColumnInterface $column
*
* @return array
*/
public function toArray(ColumnInterface $column): array
{
return [
'field' => $column->getField(),
'type' => $column->getType(),
'length' => method_exists($column, 'getLength') ? $column->getLength() : null,
'decimals' => method_exists($column, 'getDecimals') ? $column->getDecimals() : null,
'unsigned' => method_exists($column, 'isUnsigned') ? $column->isUnsigned() : false,
'nullable' => $this->isNullable($column),
'default' => $this->getOption('default', $column),
'extra' => $this->getOption('extra', $column),
'references' => method_exists($column, 'getReferences') ? $column->getReferences() : null,
'sql_default' => $this->getLineDefault($column),
];
}
/**
* @param string $key
* @param ColumnInterface|null $column
*
* @return mixed|null
*/
public function getOption(string $key, ColumnInterface $column)
{
$options = $this->getAllOptions($column);
return $options[$key] ?? null;
}
/**
* @param ColumnInterface $column
*
* @return array
*/
private function getAllOptions(ColumnInterface $column): array
{
$options = $column->getOptions();
$options = array_merge(ColumnInterface::DEFAULT_PARAMS, $options);
if (array_key_exists('extra', $options) && $options['extra'] !== null) {
$options['extra'] = $this->extraMapping($options['extra']);
}
return $options;
}
/**
* @param string $key
* @param ColumnInterface $column
*
* @return bool
*/
public function hasOption(string $key, ColumnInterface $column): bool
{
$options = $this->getAllOptions($column);
return array_key_exists($key, $options) && $options[$key] !== null;
}
/**
* @param ColumnInterface $column
*
* @return bool
*/
private function isNullable(ColumnInterface $column): bool
{
return $column->isNullable();
}
/**
* @param ColumnInterface $column
*
* @return string
*/
private function getLineDefault(ColumnInterface $column): string
{
if ($this->isNullable($column) === true) {
$defaultValue = $column instanceof Timestamp ? 'NULL DEFAULT NULL' : 'DEFAULT NULL';
if ($this->getOption('default', $column) === null) {
return $defaultValue;
}
$customDefault = $this->getOption('default', $column);
if (($column instanceof Timestamp || $column instanceof Datetime) &&
$this->containMySQLTimeConstant($customDefault) === true) {
return sprintf('DEFAULT %s', strtoupper($this->getOption('default', $column)));
}
return sprintf("DEFAULT '%s'", $this->getOption('default', $column));
}
if (($column instanceof ColumnTextInterface) || $this->isAutoIncrementField($column) === true) {
return 'NOT NULL';
}
$default = $this->getDefault($column);
if ($this->hasOption('extra', $column)) {
$default .= ' ' . $this->getOption('extra', $column);
}
if ($this->isNullable($column) === false) {
$default = 'NOT NULL ' . $default;
}
return $default;
}
/**
* @param ColumnInterface $column
*
* @return string
*/
private function getDefault(ColumnInterface $column): string
{
if ($column instanceof NumericTypeInterface) {
return $this->hasOption('default', $column) ? sprintf(
"DEFAULT '%d'",
$this->getOption('default', $column)
) : '';
}
$isNullable = $this->isNullable($column);
$defaultSet = $this->getOption('default', $column);
if ($isNullable === false && $defaultSet !== null) {
return sprintf("DEFAULT '%s'", $defaultSet);
}
return "DEFAULT ''";
}
/**
* @param ColumnInterface $column
*
* @return bool
*/
private function isAutoIncrementField(ColumnInterface $column): bool
{
return $this->hasOption('extra', $column) && in_array(
$this->getOption('extra', $column),
['ai', 'auto_increment', 'AUTO_INCREMENT'],
true
);
}
/**
* @param string $extra
*
* @return string
*/
private function extraMapping(string $extra): string
{
$short = ['ai' => 'AUTO_INCREMENT'];
if (array_key_exists($extra, $short)) {
return $short[$extra];
}
return strtoupper($extra);
}
/**
* @param string $tableName
*
* @throws LineGeneratorException
*
* @return array
*/
public function fetchColumnsFromDb(string $tableName): array
{
try {
$columns = $this->db->fetchAll('SHOW COLUMNS FROM ' . $tableName);
} catch (QueryFailureException $exception) {
throw new LineGeneratorException(
$exception->getMessage(),
$exception->getCode(),
$exception->getPrevious()
);
}
$result = [];
foreach ($columns as $column) {
$isNull = strtoupper($column['Null']) === 'YES' && $column['Default'] === null;
$isNullable = strtoupper($column['Null']) === 'YES';
$extra = !empty($column['Extra']) ? strtoupper($column['Extra']) : null;
$default = $isNull ? 'DEFAULT NULL' : "DEFAULT '" . $column['Default'] . "'";
if ($isNullable === false && $isNull === false) {
$default = "NOT NULL DEFAULT '" . $column['Default'] . "'";
}
$resultType = $column['Type'];
$references = null;
if (preg_match('/^(enum|set)\w*/i', $resultType, $found) && count($found) > 0) {
$resultType = strtoupper($found[0]);
$reference_values = str_replace(['(', ')', $found[0], '\''], '', $column['Type']);
$references = $reference_values;
} else {
$resultType = strtoupper($resultType);
}
$length = null;
$decimals = null;
preg_match('/^(decimal|double|float)\(\d*(,\d*)?\)/i', $resultType, $decimalFound);
if (count($decimalFound) > 0) {
$decimals = 0;
preg_match('/\(\d*(,\d*)?\)/', $resultType, $doubleLengthFound);
if (count($decimalFound) === 3) {
$decimals = (int)str_replace(',', '', $decimalFound[2]);
}
$length = (int)str_replace(['(', ')'], '', $doubleLengthFound[0]);
$resultType = str_replace($decimalFound[0], $decimalFound[1], $resultType);
}
if ($length === null) {
preg_match('/\(\d*\)/', $resultType, $lengthFound);
if (count($lengthFound) > 0) {
$length = (int)str_replace(['(', ')'], '', $lengthFound[0]);
$resultType = str_replace($lengthFound[0], '', $resultType);
}
}
$unsigned = false;
$resultType_exploded = explode(' ', $resultType);
if (count($resultType_exploded) > 1 && in_array(
$resultType_exploded[1],
[NumericTypeInterface::WITH_NEGATIV, NumericTypeInterface::NON_NEGATIV],
true
)) {
$unsigned = $resultType_exploded[1] === NumericTypeInterface::NON_NEGATIV;
$resultType = trim(str_replace($resultType_exploded[1], '', $resultType));
}
$result[] = [
'field' => $column['Field'],
'type' => $resultType,
'length' => $length,
'decimals' => $decimals,
'unsigned' => $unsigned,
'nullable' => $isNullable,
'default' => $column['Default'],
'extra' => $extra,
'references' => $references,
'sql_default' => $extra === 'AUTO_INCREMENT' ? 'NOT NULL' : $default,
];
}
return $result;
}
/**
* @param string $value
*
* @return bool
*/
private function containMySQLTimeConstant(string $value): bool
{
return (boolean)preg_match('/(\bCURRENT_TIMESTAMP\b|\bNOW\b|\bLOCALTIME\b|\bLOCALTIMESTAMP\b)/i', $value);
}
}