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

@@ -6,6 +6,7 @@ namespace App\Framework\Database\Profiling;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
/**
* Connection wrapper that adds profiling capabilities to any database connection
@@ -24,16 +25,15 @@ final class ProfilingConnection implements ConnectionInterface
/**
* Execute SQL with profiling
*/
public function execute(string $sql, array $parameters = []): int
public function execute(SqlQuery $query): int
{
if (! $this->profilingEnabled) {
return $this->connection->execute($sql, $parameters);
return $this->connection->execute($query);
}
$profile = $this->profiler->profile(
$sql,
$parameters,
fn () => $this->connection->execute($sql, $parameters)
$query,
fn () => $this->connection->execute($query)
);
$this->logger?->logQuery($profile);
@@ -44,18 +44,17 @@ final class ProfilingConnection implements ConnectionInterface
/**
* Query with profiling
*/
public function query(string $sql, array $parameters = []): ResultInterface
public function query(SqlQuery $query): ResultInterface
{
if (! $this->profilingEnabled) {
return $this->connection->query($sql, $parameters);
return $this->connection->query($query);
}
$result = null;
$profile = $this->profiler->profile(
$sql,
$parameters,
function () use ($sql, $parameters, &$result) {
$result = $this->connection->query($sql, $parameters);
$query,
function () use ($query, &$result) {
$result = $this->connection->query($query);
return $result;
}
@@ -69,18 +68,17 @@ final class ProfilingConnection implements ConnectionInterface
/**
* Query single row with profiling
*/
public function queryOne(string $sql, array $parameters = []): ?array
public function queryOne(SqlQuery $query): ?array
{
if (! $this->profilingEnabled) {
return $this->connection->queryOne($sql, $parameters);
return $this->connection->queryOne($query);
}
$result = null;
$profile = $this->profiler->profile(
$sql,
$parameters,
function () use ($sql, $parameters, &$result) {
$result = $this->connection->queryOne($sql, $parameters);
$query,
function () use ($query, &$result) {
$result = $this->connection->queryOne($query);
return $result;
}
@@ -94,18 +92,17 @@ final class ProfilingConnection implements ConnectionInterface
/**
* Query column with profiling
*/
public function queryColumn(string $sql, array $parameters = []): array
public function queryColumn(SqlQuery $query): array
{
if (! $this->profilingEnabled) {
return $this->connection->queryColumn($sql, $parameters);
return $this->connection->queryColumn($query);
}
$result = null;
$profile = $this->profiler->profile(
$sql,
$parameters,
function () use ($sql, $parameters, &$result) {
$result = $this->connection->queryColumn($sql, $parameters);
$query,
function () use ($query, &$result) {
$result = $this->connection->queryColumn($query);
return $result;
}
@@ -119,18 +116,17 @@ final class ProfilingConnection implements ConnectionInterface
/**
* Query scalar with profiling
*/
public function queryScalar(string $sql, array $parameters = []): mixed
public function queryScalar(SqlQuery $query): mixed
{
if (! $this->profilingEnabled) {
return $this->connection->queryScalar($sql, $parameters);
return $this->connection->queryScalar($query);
}
$result = null;
$profile = $this->profiler->profile(
$sql,
$parameters,
function () use ($sql, $parameters, &$result) {
$result = $this->connection->queryScalar($sql, $parameters);
$query,
function () use ($query, &$result) {
$result = $this->connection->queryScalar($query);
return $result;
}
@@ -147,9 +143,9 @@ final class ProfilingConnection implements ConnectionInterface
public function beginTransaction(): void
{
if ($this->profilingEnabled && $this->logger) {
$query = SqlQuery::create('BEGIN TRANSACTION');
$profile = $this->profiler->profile(
'BEGIN TRANSACTION',
[],
$query,
fn () => $this->connection->beginTransaction()
);
@@ -165,9 +161,9 @@ final class ProfilingConnection implements ConnectionInterface
public function commit(): void
{
if ($this->profilingEnabled && $this->logger) {
$query = SqlQuery::create('COMMIT');
$profile = $this->profiler->profile(
'COMMIT',
[],
$query,
fn () => $this->connection->commit()
);
@@ -183,9 +179,9 @@ final class ProfilingConnection implements ConnectionInterface
public function rollback(): void
{
if ($this->profilingEnabled && $this->logger) {
$query = SqlQuery::create('ROLLBACK');
$profile = $this->profiler->profile(
'ROLLBACK',
[],
$query,
fn () => $this->connection->rollback()
);

View File

@@ -21,7 +21,7 @@ final class QueryAnalyzer
*/
public function analyzeQuery(QueryProfile $profile): QueryAnalysis
{
$sql = $profile->sql;
$sql = $profile->query->sql;
$suggestions = [];
$issues = [];
$indexRecommendations = [];

View File

@@ -133,7 +133,7 @@ final class QueryLogger
{
$context = [
'profile_id' => $profile->id,
'sql' => $profile->sql,
'sql' => $profile->query->sql,
'normalized_sql' => $profile->getNormalizedSql(),
'execution_time_ms' => $profile->executionTime->toMilliseconds(),
'memory_usage_bytes' => $profile->memoryUsage,
@@ -143,8 +143,8 @@ final class QueryLogger
'complexity_score' => $profile->getComplexityScore(),
];
if ($this->logParameters && ! empty($profile->parameters)) {
$context['parameters'] = $this->sanitizeParameters($profile->parameters);
if ($this->logParameters && ! $profile->query->parameters->isEmpty()) {
$context['parameters'] = $this->sanitizeParameters($profile->query->parameters->toArray());
}
if ($profile->affectedRows !== null) {
@@ -295,7 +295,7 @@ final class QueryLogger
$csv .= sprintf(
"%s,\"%s\",%s,%s,%s,%s,%s,\"%s\"\n",
$profile->id,
str_replace('"', '""', $profile->sql),
str_replace('"', '""', $profile->query->sql),
$profile->getQueryType(),
$profile->executionTime->toMilliseconds(),
$profile->memoryUsage,
@@ -343,7 +343,7 @@ final class QueryLogger
$profile->startTimestamp->format('H:i:s.u'),
strtoupper($profile->getQueryType()),
$profile->executionTime->toMilliseconds(),
htmlspecialchars(substr($profile->sql, 0, 100) . '...'),
htmlspecialchars(substr($profile->query->sql, 0, 100) . '...'),
$profile->getMemoryUsageBytes()->toHumanReadable(),
$profile->affectedRows ?? '-'
);
@@ -376,7 +376,7 @@ final class QueryLogger
$sql .= "-- ERROR: {$profile->error}\n";
}
$sql .= $profile->sql . ";\n\n";
$sql .= $profile->query->sql . ";\n\n";
}
return $sql;

View File

@@ -7,6 +7,7 @@ namespace App\Framework\Database\Profiling;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\Events\Timestamp;
use App\Framework\Database\ValueObjects\SqlQuery;
/**
* Immutable query profile with execution metrics
@@ -15,8 +16,7 @@ final readonly class QueryProfile
{
public function __construct(
public string $id,
public string $sql,
public array $parameters,
public SqlQuery $query,
public Duration $executionTime,
public Timestamp $startTimestamp,
public Timestamp $endTimestamp,
@@ -33,7 +33,7 @@ final readonly class QueryProfile
*/
public function getQueryType(): string
{
$sql = trim(strtoupper($this->sql));
$sql = trim(strtoupper($this->query->sql));
$writeOperations = ['INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'TRUNCATE', 'REPLACE'];
foreach ($writeOperations as $operation) {
@@ -107,7 +107,7 @@ final readonly class QueryProfile
public function getNormalizedSql(): string
{
// Remove extra whitespace and normalize
$normalized = preg_replace('/\s+/', ' ', trim($this->sql));
$normalized = preg_replace('/\s+/', ' ', trim($this->query->sql));
// Replace parameter placeholders and values with ? for grouping
$normalized = preg_replace('/\$\d+/', '?', $normalized);
@@ -121,7 +121,7 @@ final readonly class QueryProfile
*/
public function getComplexityScore(): int
{
$sql = strtoupper($this->sql);
$sql = strtoupper($this->query->sql);
$score = 1;
// Count JOINs
@@ -172,9 +172,9 @@ final readonly class QueryProfile
{
return [
'id' => $this->id,
'sql' => $this->sql,
'sql' => $this->query->sql,
'normalized_sql' => $this->getNormalizedSql(),
'parameters' => $this->parameters,
'parameters' => $this->query->parameters->toArray(),
'query_type' => $this->getQueryType(),
'execution_time_ms' => $this->executionTime->toMilliseconds(),
'execution_time_seconds' => $this->executionTime->toSeconds(),

View File

@@ -6,6 +6,7 @@ namespace App\Framework\Database\Profiling;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\Events\Timestamp;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\Clock;
use App\Framework\Performance\MemoryMonitor;
@@ -30,13 +31,12 @@ final class QueryProfiler
/**
* Start profiling a query
*/
public function startProfile(string $sql, array $parameters = []): string
public function startProfile(SqlQuery $query): string
{
$profileId = 'profile_' . ++$this->profileCounter . '_' . uniqid();
$this->activeProfiles[$profileId] = [
'sql' => $sql,
'parameters' => $parameters,
'query' => $query,
'start_time' => $this->clock->time(),
'start_timestamp' => Timestamp::fromClock($this->clock),
'memory_start' => $this->memoryMonitor->getCurrentMemory()->toBytes(),
@@ -65,8 +65,7 @@ final class QueryProfiler
$profile = new QueryProfile(
id: $profileId,
sql: $activeProfile['sql'],
parameters: $activeProfile['parameters'],
query: $activeProfile['query'],
executionTime: Duration::fromSeconds($executionTime),
startTimestamp: $activeProfile['start_timestamp'],
endTimestamp: $endTimestamp,
@@ -86,9 +85,9 @@ final class QueryProfiler
/**
* Profile a callable with automatic timing
*/
public function profile(string $sql, array $parameters, callable $execution): QueryProfile
public function profile(SqlQuery $query, callable $execution): QueryProfile
{
$profileId = $this->startProfile($sql, $parameters);
$profileId = $this->startProfile($query);
try {
$result = $execution();

View File

@@ -196,7 +196,7 @@ final class SlowQueryDetector
*/
private function detectNPlusOnePattern(QueryProfile $profile): bool
{
$sql = strtoupper($profile->sql);
$sql = strtoupper($profile->query->sql);
// Simple heuristic: SELECT with single WHERE condition executed frequently
if (str_starts_with($sql, 'SELECT') &&
@@ -214,7 +214,7 @@ final class SlowQueryDetector
*/
private function detectMissingIndexPattern(QueryProfile $profile): bool
{
$sql = strtoupper($profile->sql);
$sql = strtoupper($profile->query->sql);
// Heuristic: SELECT with WHERE but no JOINs, taking long time
return str_starts_with($sql, 'SELECT') &&
@@ -228,7 +228,7 @@ final class SlowQueryDetector
*/
private function detectTableScanPattern(QueryProfile $profile): bool
{
$sql = strtoupper($profile->sql);
$sql = strtoupper($profile->query->sql);
// Heuristic: SELECT without WHERE clause or with very generic conditions
return str_starts_with($sql, 'SELECT') &&