docs: consolidate documentation into organized structure

- 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
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -35,6 +35,8 @@ final class Blueprint
public bool $temporary = false;
public bool $ifNotExists = false;
public function __construct(string $table)
{
$this->table = $table;
@@ -71,6 +73,13 @@ final class Blueprint
return $this;
}
public function ifNotExists(): self
{
$this->ifNotExists = true;
return $this;
}
/**
* Column definitions
*/
@@ -91,12 +100,12 @@ final class Blueprint
public function increments(string $column): ColumnDefinition
{
return $this->addColumn('increments', $column);
return $this->addColumn('increments', $column)->autoIncrement()->primary();
}
public function bigIncrements(string $column): ColumnDefinition
{
return $this->addColumn('bigIncrements', $column);
return $this->addColumn('bigIncrements', $column)->autoIncrement()->primary();
}
public function integer(string $column): ColumnDefinition

View File

@@ -441,7 +441,7 @@ final class {$className} extends AbstractMigration
{
public function getVersion(): MigrationVersion
{
return MigrationVersion::fromString('{$version->toString()}');
return MigrationVersion::fromTimestamp('{$version->toString()}');
}
public function getDescription(): string

View File

@@ -43,6 +43,10 @@ final class MySQLSchemaCompiler implements SchemaCompiler
$sql .= " TEMPORARY";
}
if ($blueprint->ifNotExists) {
$sql .= " IF NOT EXISTS";
}
$sql .= " `{$command->table}` (";
// Columns
@@ -125,8 +129,8 @@ final class MySQLSchemaCompiler implements SchemaCompiler
private function getColumnType(ColumnDefinition $column): string
{
return match($column->type) {
'increments' => 'INT AUTO_INCREMENT',
'bigIncrements' => 'BIGINT AUTO_INCREMENT',
'increments' => 'INT',
'bigIncrements' => 'BIGINT',
'integer' => 'INT',
'bigInteger' => 'BIGINT',
'tinyInteger' => 'TINYINT',

View File

@@ -43,7 +43,13 @@ final class PostgreSQLSchemaCompiler implements SchemaCompiler
$sql .= " TEMPORARY";
}
$sql .= " TABLE \"{$command->table}\" (";
$sql .= " TABLE";
if ($blueprint->ifNotExists) {
$sql .= " IF NOT EXISTS";
}
$sql .= " \"{$command->table}\" (";
// Columns
$columns = [];

View File

@@ -43,7 +43,13 @@ final class SQLiteSchemaCompiler implements SchemaCompiler
$sql .= " TEMPORARY";
}
$sql .= " TABLE `{$command->table}` (";
$sql .= " TABLE";
if ($blueprint->ifNotExists) {
$sql .= " IF NOT EXISTS";
}
$sql .= " `{$command->table}` (";
// Columns
$columns = [];

View File

@@ -11,6 +11,7 @@ use App\Framework\Database\Schema\Commands\{
DropTableCommand,
RenameTableCommand
};
use App\Framework\Database\ValueObjects\SqlQuery;
/**
* Database schema builder for migrations
@@ -28,10 +29,28 @@ final class Schema
* Create a new table
* @param string $table
* @param callable(Blueprint): void $callback
* @param bool $ifNotExists - Default true for idempotent migrations
*/
public function create(string $table, callable $callback): void
public function create(string $table, callable $callback, bool $ifNotExists = true): void
{
$blueprint = new Blueprint($table);
if ($ifNotExists) {
$blueprint->ifNotExists();
}
$callback($blueprint);
$this->commands[] = new CreateTableCommand($table, $blueprint);
}
/**
* Create a new table with IF NOT EXISTS
* @param string $table
* @param callable(Blueprint): void $callback
*/
public function createIfNotExists(string $table, callable $callback): void
{
$blueprint = new Blueprint($table);
$blueprint->ifNotExists();
$callback($blueprint);
$this->commands[] = new CreateTableCommand($table, $blueprint);
@@ -132,17 +151,30 @@ final class Schema
*/
public function execute(): void
{
$compiler = $this->createCompiler();
if (empty($this->commands)) {
return;
}
foreach ($this->commands as $command) {
$sql = $compiler->compile($command);
$statements = $this->toSql();
if (is_array($sql)) {
foreach ($sql as $statement) {
$this->connection->execute($statement);
// If we're already in a transaction (e.g., from MigrationRunner),
// execute statements directly without managing our own transaction
if ($this->connection->inTransaction()) {
foreach ($statements as $statement) {
$this->connection->execute(SqlQuery::create($statement));
}
} else {
// We're not in a transaction, so manage our own
$this->connection->beginTransaction();
try {
foreach ($statements as $statement) {
$this->connection->execute(SqlQuery::create($statement));
}
} else {
$this->connection->execute($sql);
$this->connection->commit();
} catch (\Throwable $e) {
$this->connection->rollback();
throw $e;
}
}

View File

@@ -0,0 +1,239 @@
<?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);
}
}
}