Skip to content

Commit

Permalink
Realize column factory (#864)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov authored Sep 1, 2024
1 parent 2f82ef6 commit b0f0dfd
Show file tree
Hide file tree
Showing 15 changed files with 467 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- Enh #862: Refactor PHP type of `ColumnSchemaInterface` instances (@Tigrov)
- Enh #865: Raise minimum PHP version to `^8.1` with minor refactoring (@Tigrov, @vjik)
- Enh #798: Allow `QueryInterface::one()` and `QueryInterface::all()` to return objects (@darkdef, @Tigrov)
- Enh #864: Realize column factory (@Tigrov)
- Enh #875: Ignore "Packets out of order..." warnings in `AbstractPdoCommand::internalExecute()` method (@Tigrov)

## 1.3.0 March 21, 2024
Expand Down
6 changes: 4 additions & 2 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ and the following changes were made:
- `getName()` method can return `string` or `null`;
- `getPhpType()` method must return `string` PHP type of the column which used for generating related model properties;
- `name(string|null $name)` method is added;
- `load(array $info)` method is added;
- constructor of `AbstractColumnSchema` class is changed to `__construct(string $type, string|null $phpType = null)`;
- added method chaining.

Expand All @@ -88,9 +89,10 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
- `BinaryColumnSchema` for columns with binary type;
- `JsonColumnSchema` for columns with json type.

### New methods in `QuoterInterface`
### New methods

- `QuoterInterface::getRawTableName()` - returns the raw table name without quotes.
- `QuoterInterface::getRawTableName()` - returns the raw table name without quotes;
- `SchemaInterface::getColumnFactory()` - returns the column factory.

### Remove methods

Expand Down
52 changes: 0 additions & 52 deletions src/Schema/AbstractSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@
use Yiisoft\Db\Constraint\IndexConstraint;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Schema\Column\BinaryColumnSchema;
use Yiisoft\Db\Schema\Column\BitColumnSchema;
use Yiisoft\Db\Schema\Column\BooleanColumnSchema;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\Column\DoubleColumnSchema;
use Yiisoft\Db\Schema\Column\IntegerColumnSchema;
use Yiisoft\Db\Schema\Column\JsonColumnSchema;
use Yiisoft\Db\Schema\Column\StringColumnSchema;
use Yiisoft\Db\Schema\Column\BigIntColumnSchema;

use function gettype;
use function is_array;
Expand Down Expand Up @@ -374,49 +365,6 @@ protected function findTableNames(string $schema): array
throw new NotSupportedException(static::class . ' does not support fetching all table names.');
}

/**
* Creates a column schema for the database.
*
* This method may be overridden by child classes to create a DBMS-specific column schema.
*
* @param string $type The abstract data type.
* @param mixed ...$info The column information.
* @psalm-param array{unsigned?: bool} $info The set of parameters may be different for a specific DBMS.
*
* @return ColumnSchemaInterface
*/
protected function createColumnSchema(string $type, mixed ...$info): ColumnSchemaInterface
{
$isUnsigned = !empty($info['unsigned']);

$column = $this->createColumnSchemaFromType($type, $isUnsigned);
$column->unsigned($isUnsigned);

return $column;
}

protected function createColumnSchemaFromType(string $type, bool $isUnsigned = false): ColumnSchemaInterface
{
return match ($type) {
SchemaInterface::TYPE_BOOLEAN => new BooleanColumnSchema($type),
SchemaInterface::TYPE_BIT => new BitColumnSchema($type),
SchemaInterface::TYPE_TINYINT => new IntegerColumnSchema($type),
SchemaInterface::TYPE_SMALLINT => new IntegerColumnSchema($type),
SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && $isUnsigned
? new BigIntColumnSchema($type)
: new IntegerColumnSchema($type),
SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || $isUnsigned
? new BigIntColumnSchema($type)
: new IntegerColumnSchema($type),
SchemaInterface::TYPE_DECIMAL => new DoubleColumnSchema($type),
SchemaInterface::TYPE_FLOAT => new DoubleColumnSchema($type),
SchemaInterface::TYPE_DOUBLE => new DoubleColumnSchema($type),
SchemaInterface::TYPE_BINARY => new BinaryColumnSchema($type),
SchemaInterface::TYPE_JSON => new JsonColumnSchema($type),
default => new StringColumnSchema($type),
};
}

/**
* Returns the metadata of the given type for all tables in the given schema.
*
Expand Down
108 changes: 108 additions & 0 deletions src/Schema/Column/AbstractColumnFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Schema\Column;

use Yiisoft\Db\Schema\SchemaInterface;

use function explode;
use function preg_match;
use function str_ireplace;
use function stripos;
use function strlen;
use function strtolower;
use function substr;
use function trim;

use const PHP_INT_SIZE;

/**
* The default implementation of the {@see ColumnFactoryInterface}.
*
* @psalm-import-type ColumnInfo from ColumnSchemaInterface
* @psalm-suppress MixedArgumentTypeCoercion
*/
abstract class AbstractColumnFactory implements ColumnFactoryInterface
{
/**
* Get the abstract database type for a database column type.
*
* @param string $dbType The database column type.
* @param array $info The column information.
*
* @return string The abstract database type.
*
* @psalm-param ColumnInfo $info
*/
abstract protected function getType(string $dbType, array $info = []): string;

public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterface
{
$info['db_type'] = $dbType;
$type = $info['type'] ?? $this->getType($dbType, $info);

return $this->fromType($type, $info);
}

public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface
{
preg_match('/^(\w*)(?:\(([^)]+)\))?\s*/', $definition, $matches);

$dbType = strtolower($matches[1]);

if (isset($matches[2])) {
$values = explode(',', $matches[2]);
$info['size'] = (int) $values[0];
$info['precision'] = (int) $values[0];

if (isset($values[1])) {
$info['scale'] = (int) $values[1];
}
}

$extra = substr($definition, strlen($matches[0]));

if (!empty($extra)) {
if (stripos($extra, 'unsigned') !== false) {
$info['unsigned'] = true;
$extra = trim(str_ireplace('unsigned', '', $extra));
}

if (!empty($extra)) {
if (empty($info['extra'])) {
$info['extra'] = $extra;
} else {
/** @psalm-suppress MixedOperand */
$info['extra'] = $extra . ' ' . $info['extra'];
}
}
}

return $this->fromDbType($dbType, $info);
}

public function fromType(string $type, array $info = []): ColumnSchemaInterface
{
$column = match ($type) {
SchemaInterface::TYPE_BOOLEAN => new BooleanColumnSchema($type),
SchemaInterface::TYPE_BIT => new BitColumnSchema($type),
SchemaInterface::TYPE_TINYINT => new IntegerColumnSchema($type),
SchemaInterface::TYPE_SMALLINT => new IntegerColumnSchema($type),
SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && !empty($info['unsigned'])
? new BigIntColumnSchema($type)
: new IntegerColumnSchema($type),
SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || !empty($info['unsigned'])
? new BigIntColumnSchema($type)
: new IntegerColumnSchema($type),
SchemaInterface::TYPE_DECIMAL => new DoubleColumnSchema($type),
SchemaInterface::TYPE_FLOAT => new DoubleColumnSchema($type),
SchemaInterface::TYPE_DOUBLE => new DoubleColumnSchema($type),
SchemaInterface::TYPE_BINARY => new BinaryColumnSchema($type),
SchemaInterface::TYPE_JSON => new JsonColumnSchema($type),
default => new StringColumnSchema($type),
};

return $column->load($info);
}
}
34 changes: 32 additions & 2 deletions src/Schema/Column/AbstractColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Yiisoft\Db\Constant\PhpType;

use function is_array;

/**
* Represents the metadata of a column in a database table.
*
Expand All @@ -21,11 +23,10 @@
* ```php
* use Yiisoft\Db\Schema\ColumnSchema;
*
* $column = (new ColumnSchema())
* $column = (new IntegerColumnSchema())
* ->name('id')
* ->allowNull(false)
* ->dbType('int(11)')
* ->phpType('integer')
* ->type('integer')
* ->defaultValue(0)
* ->autoIncrement()
Expand Down Expand Up @@ -182,6 +183,35 @@ public function isUnsigned(): bool
return $this->unsigned;
}

public function load(array $info): static
{
foreach ($info as $key => $value) {
/**
* @psalm-suppress PossiblyInvalidCast
* @psalm-suppress RiskyCast
*/
match ($key) {
'allow_null' => $this->allowNull((bool) $value),
'auto_increment' => $this->autoIncrement((bool) $value),
'comment' => $this->comment($value !== null ? (string) $value : null),
'computed' => $this->computed((bool) $value),
'db_type' => $this->dbType($value !== null ? (string) $value : null),
'default_value' => $this->defaultValue($value),
'enum_values' => $this->enumValues(is_array($value) ? $value : null),
'extra' => $this->extra($value !== null ? (string) $value : null),
'name' => $this->name($value !== null ? (string) $value : null),
'primary_key' => $this->primaryKey((bool) $value),
'precision' => $this->precision($value !== null ? (int) $value : null),
'scale' => $this->scale($value !== null ? (int) $value : null),
'size' => $this->size($value !== null ? (int) $value : null),
'unsigned' => $this->unsigned((bool) $value),
default => null,
};
}

return $this;
}

public function name(string|null $name): static
{
$this->name = $name;
Expand Down
44 changes: 44 additions & 0 deletions src/Schema/Column/ColumnFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Schema\Column;

/**
* The interface must be implemented by a column factory class. It should create a column schema for a database column
* type and initialize column information.
*
* @psalm-import-type ColumnInfo from ColumnSchemaInterface
*/
interface ColumnFactoryInterface
{
/**
* Creates a column schema for a database column type and initializes column information.
*
* @param string $dbType The database column type.
* @param array $info The column information.
*
* @psalm-param ColumnInfo $info The set of parameters may be different for a specific DBMS.
*/
public function fromDbType(string $dbType, array $info = []): ColumnSchemaInterface;

/**
* Creates a column schema for a database column definition and initializes column information.
*
* @param string $definition The database column definition.
* @param array $info The column information.
*
* @psalm-param ColumnInfo $info The set of parameters may be different for a specific DBMS.
*/
public function fromDefinition(string $definition, array $info = []): ColumnSchemaInterface;

/**
* Creates a column schema for an abstract database type and initializes column information.
*
* @param string $type The abstract database type.
* @param array $info The column information.
*
* @psalm-param ColumnInfo $info The set of parameters may be different for a specific DBMS.
*/
public function fromType(string $type, array $info = []): ColumnSchemaInterface;
}
30 changes: 29 additions & 1 deletion src/Schema/Column/ColumnSchemaInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
/**
* This interface defines a set of methods that must be implemented by a class that represents the column schema of a
* database table column.
*
* @psalm-type ColumnInfo = array{
* allow_null?: bool|string|null,
* auto_increment?: bool|string,
* comment?: string|null,
* computed?: bool|string,
* db_type?: string|null,
* default_value?: mixed,
* enum_values?: array|null,
* extra?: string|null,
* primary_key?: bool|string,
* name?: string|null,
* precision?: int|string|null,
* scale?: int|string|null,
* schema?: string|null,
* size?: int|string|null,
* table?: string|null,
* type?: string,
* unsigned?: bool|string,
* ...<string, mixed>
* }
*/
interface ColumnSchemaInterface
{
Expand Down Expand Up @@ -250,7 +271,14 @@ public function isPrimaryKey(): bool;
public function isUnsigned(): bool;

/**
* Sets the name of the column.
* Loads the column's schema information from an array.
*
* @psalm-param ColumnInfo $info
*/
public function load(array $info): static;

/**
* Sets a name of the column.
*
* ```php
* $columns = [
Expand Down
6 changes: 6 additions & 0 deletions src/Schema/SchemaInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\Column\ColumnFactoryInterface;

/**
* Represents the schema for a database table.
Expand Down Expand Up @@ -228,6 +229,11 @@ interface SchemaInterface extends ConstraintSchemaInterface
*/
public function createColumn(string $type, array|int|string $length = null): ColumnInterface;

/**
* Returns the column factory for creating column instances.
*/
public function getColumnFactory(): ColumnFactoryInterface;

/**
* @return string|null The default schema name.
*/
Expand Down
Loading

0 comments on commit b0f0dfd

Please sign in to comment.