- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
240 lines
6.2 KiB
PHP
240 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Database\Schema;
|
|
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\Database\Platform\DatabasePlatform;
|
|
use App\Framework\Database\Platform\ValueObjects\ColumnDefinition;
|
|
use App\Framework\Database\Platform\ValueObjects\IndexDefinition;
|
|
use App\Framework\Database\Platform\ValueObjects\TableOptions;
|
|
|
|
/**
|
|
* Schema builder for database-independent table creation
|
|
*/
|
|
final readonly class SchemaBuilder
|
|
{
|
|
public function __construct(
|
|
private DatabasePlatform $platform,
|
|
private ConnectionInterface $connection
|
|
) {
|
|
}
|
|
|
|
public function createTable(string $tableName, array $columns, ?TableOptions $options = null): void
|
|
{
|
|
$tableOptions = $options ?? TableOptions::default();
|
|
|
|
$sql = $this->platform->getCreateTableSQL($tableName, $columns, $tableOptions);
|
|
$this->connection->execute($sql);
|
|
}
|
|
|
|
public function dropTable(string $tableName, bool $ifExists = true): void
|
|
{
|
|
$sql = $this->platform->getDropTableSQL($tableName, $ifExists);
|
|
$this->connection->execute($sql);
|
|
}
|
|
|
|
public function tableExists(string $tableName): bool
|
|
{
|
|
$sql = $this->platform->getTableExistsSQL($tableName);
|
|
$result = $this->connection->queryColumn($sql, [$tableName]);
|
|
|
|
return ! empty($result) && (int) $result[0] > 0;
|
|
}
|
|
|
|
public function createIndex(string $tableName, IndexDefinition $index): void
|
|
{
|
|
$sql = $this->platform->getCreateIndexSQL(
|
|
$tableName,
|
|
$index->name,
|
|
$index->columns,
|
|
['type' => $index->type]
|
|
);
|
|
|
|
$this->connection->execute($sql);
|
|
}
|
|
|
|
public function listTables(): array
|
|
{
|
|
$sql = $this->platform->getListTablesSQL();
|
|
|
|
return $this->connection->queryColumn($sql);
|
|
}
|
|
|
|
/**
|
|
* Builder pattern for table creation
|
|
*/
|
|
public function table(string $tableName): TableBuilder
|
|
{
|
|
return new TableBuilder($this, $tableName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fluent table builder for easy table creation
|
|
*/
|
|
final class TableBuilder
|
|
{
|
|
private array $columns = [];
|
|
|
|
private array $indexes = [];
|
|
|
|
private ?TableOptions $options = null;
|
|
|
|
public function __construct(
|
|
private readonly SchemaBuilder $schemaBuilder,
|
|
private readonly string $tableName
|
|
) {
|
|
}
|
|
|
|
public function id(string $name = 'id'): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::id($name);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function string(string $name, int $length = 255, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::string($name, $length, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function text(string $name, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::text($name, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function integer(string $name, bool $nullable = true, bool $unsigned = false): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::integer($name, $nullable, $unsigned);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function bigInteger(string $name, bool $nullable = true, bool $unsigned = false): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::bigInteger($name, $nullable, $unsigned);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function decimal(string $name, int $precision = 8, int $scale = 2, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::decimal($name, $precision, $scale, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function boolean(string $name, bool $nullable = true, ?bool $default = null): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::boolean($name, $nullable, $default);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function timestamp(string $name, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::timestamp($name, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function timestamps(): self
|
|
{
|
|
$timestamps = ColumnDefinition::timestamps();
|
|
foreach ($timestamps as $timestamp) {
|
|
$this->columns[] = $timestamp;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function json(string $name, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::json($name, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function uuid(string $name, bool $nullable = true): self
|
|
{
|
|
$this->columns[] = ColumnDefinition::uuid($name, $nullable);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function column(ColumnDefinition $column): self
|
|
{
|
|
$this->columns[] = $column;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function index(IndexDefinition $index): self
|
|
{
|
|
$this->indexes[] = $index;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function unique(string $name, array $columns): self
|
|
{
|
|
$this->indexes[] = IndexDefinition::unique($name, $columns);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function foreignKey(string $column, string $referencedTable, string $referencedColumn = 'id'): self
|
|
{
|
|
// Foreign keys will be handled in a separate method/builder
|
|
return $this;
|
|
}
|
|
|
|
public function engine(string $engine): self
|
|
{
|
|
$this->options = ($this->options ?? TableOptions::default())->withCustomOption('engine', $engine);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function charset(string $charset, ?string $collation = null): self
|
|
{
|
|
$this->options = ($this->options ?? TableOptions::default())->withCharset($charset, $collation);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function comment(string $comment): self
|
|
{
|
|
$this->options = ($this->options ?? TableOptions::default())->withComment($comment);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function temporary(): self
|
|
{
|
|
$this->options = TableOptions::temporary();
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function create(): void
|
|
{
|
|
if (empty($this->columns)) {
|
|
throw new \RuntimeException("Cannot create table '{$this->tableName}' without columns");
|
|
}
|
|
|
|
$this->schemaBuilder->createTable($this->tableName, $this->columns, $this->options);
|
|
|
|
// Create additional indexes
|
|
foreach ($this->indexes as $index) {
|
|
$this->schemaBuilder->createIndex($this->tableName, $index);
|
|
}
|
|
}
|
|
}
|