Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Realize column factory #864

Merged
merged 13 commits into from
Sep 1, 2024
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)

Check warning on line 93 in src/Schema/Column/AbstractColumnFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Column/AbstractColumnFactory.php#L93

Added line #L93 was not covered by tests
: 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
Tigrov marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* 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
Loading