Files
michaelschiemer/src/Framework/Database/ReadWriteConnection.php
Michael Schiemer 5050c7d73a 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
2025-10-05 11:05:04 +02:00

220 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Database;
use App\Framework\Database\Config\ReadWriteConfig;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\ReadWrite\MasterSlaveRouter;
use App\Framework\Database\ReadWrite\ReplicationLagDetector;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\Clock;
/**
* Class ReadWriteConnection provides a mechanism to handle separate writing and reading database connections.
* It ensures that write-heavy operations are directed to a designated write connection,
* while read operations are distributed among a pool of read connections.
*
* Enhanced with load balancing, health checks, and lag detection.
*/
final class ReadWriteConnection implements ConnectionInterface
{
private ConnectionInterface $writeConnection;
private array $readConnections;
private bool $forceWrite = false;
private ?MasterSlaveRouter $router = null;
public function __construct(
ConnectionInterface $writeConnection,
array $readConnections,
private readonly ?ReadWriteConfig $config = null,
private readonly ?Clock $clock = null,
private readonly ?ReplicationLagDetector $lagDetector = null
) {
$this->writeConnection = $writeConnection;
$this->readConnections = $readConnections;
if (empty($this->readConnections)) {
throw new DatabaseException('At least one read connection is required');
}
// Initialize advanced router if dependencies are provided
if ($this->config && $this->clock && $this->lagDetector) {
$this->router = new MasterSlaveRouter(
$writeConnection,
$readConnections,
$this->config,
$this->clock,
$this->lagDetector
);
}
}
public function execute(SqlQuery $query): int
{
$this->forceWrite = true;
return $this->writeConnection->execute($query);
}
public function query(SqlQuery $query): ResultInterface
{
$connection = $this->getConnection($query->sql);
return $connection->query($query);
}
public function queryOne(SqlQuery $query): ?array
{
$connection = $this->getConnection($query->sql);
return $connection->queryOne($query);
}
public function queryColumn(SqlQuery $query): array
{
$connection = $this->getConnection($query->sql);
return $connection->queryColumn($query);
}
public function queryScalar(SqlQuery $query): mixed
{
$connection = $this->getConnection($query->sql);
return $connection->queryScalar($query);
}
public function beginTransaction(): void
{
$this->forceWrite = true;
$this->writeConnection->beginTransaction();
}
public function commit(): void
{
$this->writeConnection->commit();
$this->forceWrite = false;
}
public function rollback(): void
{
$this->writeConnection->rollback();
$this->forceWrite = false;
}
public function inTransaction(): bool
{
return $this->writeConnection->inTransaction();
}
public function lastInsertId(): string
{
return $this->writeConnection->lastInsertId();
}
public function getPdo(): \PDO
{
return $this->writeConnection->getPdo();
}
public function forceWriteConnection(): void
{
$this->forceWrite = true;
}
public function resetConnectionMode(): void
{
$this->forceWrite = false;
$this->router?->resetStickyConnection();
}
private function shouldUseWriteConnection(string $sql): bool
{
if ($this->forceWrite || $this->inTransaction()) {
return true;
}
$sql = trim(strtoupper($sql));
$writeOperations = ['INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'TRUNCATE'];
return array_any($writeOperations, fn ($operation) => str_starts_with($sql, $operation));
}
/**
* Get appropriate connection for SQL query
*/
private function getConnection(string $sql): ConnectionInterface
{
if ($this->router) {
return $this->router->route($sql, $this->forceWrite);
}
// Fallback to simple routing
if ($this->shouldUseWriteConnection($sql)) {
return $this->writeConnection;
}
return $this->getReadConnection();
}
/**
* Simple round-robin read connection selection (fallback)
*/
private function getReadConnection(): ConnectionInterface
{
static $currentReadIndex = 0;
$connection = $this->readConnections[$currentReadIndex];
$currentReadIndex = ($currentReadIndex + 1) % count($this->readConnections);
return $connection;
}
/**
* Get read/write statistics
*/
public function getStatistics(): array
{
if ($this->router) {
return $this->router->getStatistics();
}
return [
'router' => 'simple',
'master_connection' => 'active',
'replica_count' => count($this->readConnections),
'load_balancing_strategy' => 'round_robin',
];
}
/**
* Get write connection (for direct access if needed)
*/
public function getWriteConnection(): ConnectionInterface
{
return $this->writeConnection;
}
/**
* Get read connections
*/
public function getReadConnections(): array
{
return $this->readConnections;
}
/**
* Check if advanced routing is enabled
*/
public function hasAdvancedRouting(): bool
{
return $this->router !== null;
}
}