feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -14,11 +14,14 @@ use App\Framework\Database\Driver\DriverType;
use App\Framework\Database\Driver\MysqlDriver;
use App\Framework\Database\Driver\PostgresDriver;
use App\Framework\Database\Driver\SqliteDriver;
use App\Framework\Database\Exception\ConnectionFailedException;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\Middleware\CacheMiddleware;
use App\Framework\Database\Middleware\HealthCheckMiddleware;
use App\Framework\Database\Middleware\MiddlewarePipeline;
use App\Framework\Database\Middleware\RetryMiddleware;
use App\Framework\Database\Services\SqlStateErrorMapper;
use App\Framework\Database\ValueObjects\SqlState;
use Pdo\Mysql;
use Pdo\Pgsql;
use Pdo\Sqlite;
@@ -95,7 +98,8 @@ final readonly class DatabaseFactory
$driver = self::createDriver($config);
$pdo = self::createPdo($driver);
return new PdoConnection($pdo);
// Create PdoConnection with SqlStateErrorMapper for enhanced error handling
return new PdoConnection($pdo, new SqlStateErrorMapper());
}
public static function createLazyConnection(
@@ -308,24 +312,117 @@ final readonly class DatabaseFactory
private static function createPdo(Driver $driver): \PDO
{
return match($driver->config->driverType) {
DriverType::MYSQL => new Mysql(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
try {
return match($driver->config->driverType) {
DriverType::MYSQL => new Mysql(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
),
DriverType::PGSQL => new Pgsql(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
),
DriverType::SQLITE => new Sqlite(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
),
};
} catch (\PDOException $e) {
// Extract SQLSTATE from PDOException
$sqlStateCode = self::extractSqlState($e);
try {
$sqlState = new SqlState($sqlStateCode);
} catch (\InvalidArgumentException) {
// Fallback to generic exception if SQLSTATE is invalid
throw DatabaseException::simple(
"Failed to create database connection: {$e->getMessage()}",
$e
);
}
// Map SQLSTATE to specific connection exception
throw self::handleConnectionError(
$driver->config,
$sqlState,
$e
);
}
}
/**
* Extract SQLSTATE from PDOException
*/
private static function extractSqlState(\PDOException $e): string
{
$sqlStateCode = $e->getCode();
// PDO sometimes returns error info array as code
if (is_array($sqlStateCode)) {
return $sqlStateCode[0] ?? 'HY000';
}
// If code is not a valid 5-character SQLSTATE, try errorInfo
if (!is_string($sqlStateCode) || strlen($sqlStateCode) !== 5) {
// For connection errors, PDO might not have errorInfo available
// Fallback to generic driver error
return 'HY000';
}
return $sqlStateCode;
}
/**
* Handle connection errors with SQLSTATE-aware exceptions
*/
private static function handleConnectionError(
DriverConfig $config,
SqlState $sqlState,
\PDOException $e
): ConnectionFailedException {
$host = $config->host ?? 'unknown';
$database = $config->database ?? 'unknown';
$username = $config->username ?? 'unknown';
return match ($sqlState->code) {
'08001' => ConnectionFailedException::cannotConnect(
$host,
$database,
$sqlState,
"Could not connect to database server: {$e->getMessage()}",
$e
),
DriverType::PGSQL => new Pgsql(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
'08004' => ConnectionFailedException::serverRejectedConnection(
$host,
$username,
$sqlState,
$e->getMessage()
),
DriverType::SQLITE => new Sqlite(
$driver->dsn,
$driver->config->username,
$driver->config->password,
$driver->options
'08006' => ConnectionFailedException::connectionFailedDuringTransaction(
$sqlState
),
'08007' => ConnectionFailedException::connectionLost(
$sqlState,
null
),
'28000' => ConnectionFailedException::serverRejectedConnection(
$host,
$username,
$sqlState,
"Invalid authorization: {$e->getMessage()}"
),
default => ConnectionFailedException::cannotConnect(
$host,
$database,
$sqlState,
$e->getMessage(),
$e
),
};
}