1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Data\Persistence\Sql;
6:
7: use Doctrine\DBAL\Connection as DbalConnection;
8: use Doctrine\DBAL\Driver\API\ExceptionConverter;
9: use Doctrine\DBAL\Driver\API\SQLSrv\ExceptionConverter as SQLServerExceptionConverter;
10: use Doctrine\DBAL\Driver\Exception as DbalDriverException;
11: use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
12: use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
13: use Doctrine\DBAL\Exception\DriverException as DbalDriverConvertedException;
14: use Doctrine\DBAL\Exception\TableNotFoundException;
15: use Doctrine\DBAL\Platforms\AbstractPlatform;
16: use Doctrine\DBAL\Platforms\OraclePlatform;
17: use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
18: use Doctrine\DBAL\Platforms\SQLitePlatform;
19: use Doctrine\DBAL\Platforms\SQLServerPlatform;
20: use Doctrine\DBAL\Query as DbalQuery;
21: use Doctrine\DBAL\Schema\AbstractSchemaManager;
22: use Doctrine\DBAL\Schema\OracleSchemaManager;
23: use Doctrine\DBAL\Schema\SqliteSchemaManager;
24:
25: class DbalDriverMiddleware extends AbstractDriverMiddleware
26: {
27: protected function replaceDatabasePlatform(AbstractPlatform $platform): AbstractPlatform
28: {
29: if ($platform instanceof SQLitePlatform) {
30: $platform = new class() extends SQLitePlatform {
31: use Sqlite\PlatformTrait;
32: };
33: } elseif ($platform instanceof PostgreSQLPlatform) {
34: $platform = new class() extends \Doctrine\DBAL\Platforms\PostgreSQL94Platform { // @phpstan-ignore-line
35: use Postgresql\PlatformTrait;
36: };
37: } elseif ($platform instanceof SQLServerPlatform) {
38: $platform = new class() extends \Doctrine\DBAL\Platforms\SQLServer2012Platform { // @phpstan-ignore-line
39: use Mssql\PlatformTrait;
40: };
41: } elseif ($platform instanceof OraclePlatform) {
42: $platform = new class() extends OraclePlatform {
43: use Oracle\PlatformTrait;
44: };
45: }
46:
47: return $platform;
48: }
49:
50: #[\Override]
51: public function getDatabasePlatform(): AbstractPlatform
52: {
53: return $this->replaceDatabasePlatform(parent::getDatabasePlatform());
54: }
55:
56: #[\Override]
57: public function createDatabasePlatformForVersion($version): AbstractPlatform
58: {
59: return $this->replaceDatabasePlatform(parent::createDatabasePlatformForVersion($version));
60: }
61:
62: /**
63: * @return AbstractSchemaManager<AbstractPlatform>
64: */
65: #[\Override]
66: public function getSchemaManager(DbalConnection $connection, AbstractPlatform $platform): AbstractSchemaManager
67: {
68: if ($platform instanceof SQLitePlatform) {
69: return new class($connection, $platform) extends SqliteSchemaManager { // @phpstan-ignore-line
70: use Sqlite\SchemaManagerTrait;
71: };
72: } elseif ($platform instanceof OraclePlatform) {
73: return new class($connection, $platform) extends OracleSchemaManager { // @phpstan-ignore-line
74: use Oracle\SchemaManagerTrait;
75: };
76: }
77:
78: return parent::getSchemaManager($connection, $platform);
79: }
80:
81: /**
82: * @param \Closure(DbalDriverConvertedException, ?DbalQuery): DbalDriverConvertedException $convertFx
83: */
84: protected function createExceptionConvertorMiddleware(ExceptionConverter $wrappedExceptionConverter, \Closure $convertFx): ExceptionConverter
85: {
86: return new class($wrappedExceptionConverter, $convertFx) implements ExceptionConverter {
87: private ExceptionConverter $wrappedExceptionConverter;
88:
89: /**
90: * @param \Closure(DbalDriverConvertedException, ?DbalQuery): DbalDriverConvertedException $convertFx
91: */
92: private \Closure $convertFx;
93:
94: /**
95: * @param \Closure(DbalDriverConvertedException, ?DbalQuery): DbalDriverConvertedException $convertFx
96: */
97: public function __construct(ExceptionConverter $wrappedExceptionConverter, \Closure $convertFx)
98: {
99: $this->wrappedExceptionConverter = $wrappedExceptionConverter;
100: $this->convertFx = $convertFx;
101: }
102:
103: #[\Override]
104: public function convert(DbalDriverException $exception, ?DbalQuery $query): DbalDriverConvertedException
105: {
106: $convertedException = $this->wrappedExceptionConverter->convert($exception, $query);
107:
108: return ($this->convertFx)($convertedException, $query);
109: }
110: };
111: }
112:
113: final protected static function getUnconvertedException(DbalDriverConvertedException $convertedException): DbalDriverException
114: {
115: return $convertedException->getPrevious(); // @phpstan-ignore-line
116: }
117:
118: #[\Override]
119: public function getExceptionConverter(): ExceptionConverter
120: {
121: $exceptionConverter = parent::getExceptionConverter();
122: if ($exceptionConverter instanceof SQLServerExceptionConverter) {
123: $exceptionConverter = $this->createExceptionConvertorMiddleware(
124: $exceptionConverter,
125: static function (DbalDriverConvertedException $convertedException, ?DbalQuery $query): DbalDriverConvertedException {
126: // fix table not found exception conversion
127: // https://github.com/doctrine/dbal/pull/5492
128: if ($convertedException instanceof DatabaseObjectNotFoundException) {
129: $exception = self::getUnconvertedException($convertedException);
130: $exceptionMessageLc = strtolower($exception->getMessage());
131: if (str_contains($exceptionMessageLc, 'cannot drop the table') && !$convertedException instanceof TableNotFoundException) {
132: return new TableNotFoundException($exception, $query);
133: }
134: }
135:
136: return $convertedException;
137: }
138: );
139: }
140:
141: return $exceptionConverter;
142: }
143: }
144: