diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 2ac9c8639cd..0b0d3f086ed 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -38,6 +38,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -82,6 +86,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -125,6 +133,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -180,6 +192,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -242,6 +258,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -330,6 +350,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -398,6 +422,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -455,6 +483,10 @@ jobs:
with:
fetch-depth: 2
+ - name: "Temporarily remove support for PSR Log 3 on PHP 8.1"
+ run: 'sed -i "s/\"psr\/log\": \"^1|^2|^3\"/\"psr\/log\": \"^1|^2\"/" composer.json'
+ if: "${{ matrix.php-version=='8.1' }}"
+
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
diff --git a/UPGRADE.md b/UPGRADE.md
index a0e21d83d8d..e51562bd0ab 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -8,6 +8,11 @@ awareness about deprecated code.
# Upgrade to 3.2
+## Deprecated `SQLLogger` and its implementations.
+
+The `SQLLogger` and its implementations `DebugStack` and `LoggerChain` have been deprecated.
+For logging purposes, use `Doctrine\DBAL\Logging\Middleware` instead. No replacement for `DebugStack` is provided.
+
## Deprecated `SqliteSchemaManager::createDatabase()` and `dropDatabase()` methods.
The `SqliteSchemaManager::createDatabase()` and `dropDatabase()` methods have been deprecated. The SQLite engine
diff --git a/composer.json b/composer.json
index 9dec3533283..6d5f58437ce 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,8 @@
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.0",
- "psr/cache": "^1|^2|^3"
+ "psr/cache": "^1|^2|^3",
+ "psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "9.0.0",
diff --git a/psalm.xml.dist b/psalm.xml.dist
index eb1afaf6ba6..4b194dda9f3 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -67,6 +67,13 @@
TODO: remove in 4.0.0
-->
+
+
+
+
@@ -80,6 +87,11 @@
TODO: remove in 4.0.0
-->
+
+
diff --git a/src/Logging/Connection.php b/src/Logging/Connection.php
new file mode 100644
index 00000000000..9bb11ac0593
--- /dev/null
+++ b/src/Logging/Connection.php
@@ -0,0 +1,126 @@
+connection = $connection;
+ $this->logger = $logger;
+ }
+
+ public function __destruct()
+ {
+ $this->logger->info('Disconnecting');
+ }
+
+ public function prepare(string $sql): DriverStatement
+ {
+ return new Statement(
+ $this->connection->prepare($sql),
+ $this->logger,
+ $sql
+ );
+ }
+
+ public function query(string $sql): Result
+ {
+ $this->logger->debug('Executing query: {sql}', ['sql' => $sql]);
+
+ return $this->connection->query($sql);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function quote($value, $type = ParameterType::STRING)
+ {
+ return $this->connection->quote($value, $type);
+ }
+
+ public function exec(string $sql): int
+ {
+ $this->logger->debug('Executing statement: {sql}', ['sql' => $sql]);
+
+ return $this->connection->exec($sql);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function lastInsertId($name = null)
+ {
+ if ($name !== null) {
+ Deprecation::triggerIfCalledFromOutside(
+ 'doctrine/dbal',
+ 'https://github.com/doctrine/dbal/issues/4687',
+ 'The usage of Connection::lastInsertId() with a sequence name is deprecated.'
+ );
+ }
+
+ return $this->connection->lastInsertId($name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function beginTransaction()
+ {
+ $this->logger->debug('Beginning transaction');
+
+ return $this->connection->beginTransaction();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function commit()
+ {
+ $this->logger->debug('Committing transaction');
+
+ return $this->connection->commit();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function rollBack()
+ {
+ $this->logger->debug('Rolling back transaction');
+
+ return $this->connection->rollBack();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getServerVersion()
+ {
+ if (! $this->connection instanceof ServerInfoAwareConnection) {
+ throw new LogicException('The underlying connection is not a ServerInfoAwareConnection');
+ }
+
+ return $this->connection->getServerVersion();
+ }
+}
diff --git a/src/Logging/DebugStack.php b/src/Logging/DebugStack.php
index 6a9fab5a9e4..24a2d68cfbe 100644
--- a/src/Logging/DebugStack.php
+++ b/src/Logging/DebugStack.php
@@ -2,10 +2,14 @@
namespace Doctrine\DBAL\Logging;
+use Doctrine\Deprecations\Deprecation;
+
use function microtime;
/**
* Includes executed SQLs in a Debug Stack.
+ *
+ * @deprecated
*/
class DebugStack implements SQLLogger
{
@@ -29,6 +33,15 @@ class DebugStack implements SQLLogger
/** @var int */
public $currentQuery = 0;
+ public function __construct()
+ {
+ Deprecation::trigger(
+ 'doctrine/dbal',
+ 'https://github.com/doctrine/dbal/pull/4967',
+ 'DebugStack is deprecated.'
+ );
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Logging/Driver.php b/src/Logging/Driver.php
new file mode 100644
index 00000000000..5ee12b4312a
--- /dev/null
+++ b/src/Logging/Driver.php
@@ -0,0 +1,90 @@
+driver = $driver;
+ $this->logger = $logger;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function connect(array $params)
+ {
+ $this->logger->info('Connecting with parameters {params}', ['params' => $this->maskPassword($params)]);
+
+ return new Connection(
+ $this->driver->connect($params),
+ $this->logger
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getDatabasePlatform()
+ {
+ return $this->driver->getDatabasePlatform();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchemaManager(DBALConnection $conn, AbstractPlatform $platform)
+ {
+ return $this->driver->getSchemaManager($conn, $platform);
+ }
+
+ public function getExceptionConverter(): ExceptionConverter
+ {
+ return $this->driver->getExceptionConverter();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function createDatabasePlatformForVersion($version)
+ {
+ if ($this->driver instanceof VersionAwarePlatformDriver) {
+ return $this->driver->createDatabasePlatformForVersion($version);
+ }
+
+ return $this->driver->getDatabasePlatform();
+ }
+
+ /**
+ * @param array $params Connection parameters
+ *
+ * @return array
+ */
+ private function maskPassword(array $params): array
+ {
+ if (isset($params['password'])) {
+ $params['password'] = '';
+ }
+
+ return $params;
+ }
+}
diff --git a/src/Logging/LoggerChain.php b/src/Logging/LoggerChain.php
index 9b44dc0e3e8..c256dd72cd6 100644
--- a/src/Logging/LoggerChain.php
+++ b/src/Logging/LoggerChain.php
@@ -2,8 +2,12 @@
namespace Doctrine\DBAL\Logging;
+use Doctrine\Deprecations\Deprecation;
+
/**
* Chains multiple SQLLogger.
+ *
+ * @deprecated
*/
class LoggerChain implements SQLLogger
{
@@ -15,6 +19,12 @@ class LoggerChain implements SQLLogger
*/
public function __construct(iterable $loggers = [])
{
+ Deprecation::trigger(
+ 'doctrine/dbal',
+ 'https://github.com/doctrine/dbal/pull/4967',
+ 'LoggerChain is deprecated'
+ );
+
$this->loggers = $loggers;
}
diff --git a/src/Logging/Middleware.php b/src/Logging/Middleware.php
new file mode 100644
index 00000000000..4d5c6b0611b
--- /dev/null
+++ b/src/Logging/Middleware.php
@@ -0,0 +1,25 @@
+logger = $logger;
+ }
+
+ public function wrap(DriverInterface $driver): DriverInterface
+ {
+ return new Driver($driver, $this->logger);
+ }
+}
diff --git a/src/Logging/SQLLogger.php b/src/Logging/SQLLogger.php
index a0bdf1bf6a8..40eb707addd 100644
--- a/src/Logging/SQLLogger.php
+++ b/src/Logging/SQLLogger.php
@@ -6,6 +6,9 @@
/**
* Interface for SQL loggers.
+ *
+ * @deprecated Use {@link \Doctrine\DBAL\Logging\Middleware} or implement
+ * {@link \Doctrine\DBAL\Driver\Middleware} instead.
*/
interface SQLLogger
{
diff --git a/src/Logging/Statement.php b/src/Logging/Statement.php
new file mode 100644
index 00000000000..37ebf343da9
--- /dev/null
+++ b/src/Logging/Statement.php
@@ -0,0 +1,77 @@
+|array */
+ private $params = [];
+
+ /** @var array|array */
+ private $types = [];
+
+ /**
+ * @internal This statement can be only instantiated by its connection.
+ */
+ public function __construct(StatementInterface $statement, LoggerInterface $logger, string $sql)
+ {
+ $this->statement = $statement;
+ $this->logger = $logger;
+ $this->sql = $sql;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
+ {
+ $this->params[$param] = &$variable;
+ $this->types[$param] = $type;
+
+ return $this->statement->bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bindValue($param, $value, $type = ParameterType::STRING)
+ {
+ $this->params[$param] = $value;
+ $this->types[$param] = $type;
+
+ return $this->statement->bindValue($param, $value, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function execute($params = null): ResultInterface
+ {
+ $this->logger->debug('Executing statement: {sql} (parameters: {params}, types: {types})', [
+ 'sql' => $this->sql,
+ 'params' => $params ?? $this->params,
+ 'types' => $this->types,
+ ]);
+
+ return $this->statement->execute($params);
+ }
+}
diff --git a/tests/Functional/ConnectionTest.php b/tests/Functional/ConnectionTest.php
index da129919c2e..68e13d50966 100644
--- a/tests/Functional/ConnectionTest.php
+++ b/tests/Functional/ConnectionTest.php
@@ -360,7 +360,6 @@ public function testDeterminesDatabasePlatformWhenConnectingToNonExistentDatabas
);
self::assertInstanceOf(AbstractPlatform::class, $connection->getDatabasePlatform());
- self::assertFalse($connection->isConnected());
self::assertSame($params, $connection->getParams());
$connection->close();
diff --git a/tests/Functional/Driver/IBMDB2/ConnectionTest.php b/tests/Functional/Driver/IBMDB2/ConnectionTest.php
deleted file mode 100644
index a9e1479bfbe..00000000000
--- a/tests/Functional/Driver/IBMDB2/ConnectionTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-markConnectionNotReusable();
- }
-
- public function testPrepareFailure(): void
- {
- $driverConnection = $this->connection->getWrappedConnection();
-
- $re = new ReflectionProperty($driverConnection, 'connection');
- $re->setAccessible(true);
- $conn = $re->getValue($driverConnection);
- db2_close($conn);
-
- $this->expectException(PrepareFailed::class);
- $driverConnection->prepare('SELECT 1');
- }
-}
diff --git a/tests/Functional/PortabilityTest.php b/tests/Functional/PortabilityTest.php
index 3fef5445a11..ddac7010d03 100644
--- a/tests/Functional/PortabilityTest.php
+++ b/tests/Functional/PortabilityTest.php
@@ -10,18 +10,23 @@
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Throwable;
+use function array_merge;
use function strlen;
class PortabilityTest extends FunctionalTestCase
{
protected function setUp(): void
{
- $this->connection = DriverManager::getConnection(
- $this->connection->getParams(),
- $this->connection->getConfiguration()
- ->setMiddlewares([new Middleware(Connection::PORTABILITY_ALL, ColumnCase::LOWER)])
+ $configuration = $this->connection->getConfiguration();
+ $configuration->setMiddlewares(
+ array_merge(
+ $configuration->getMiddlewares(),
+ [new Middleware(Connection::PORTABILITY_ALL, ColumnCase::LOWER)]
+ )
);
+ $this->connection = DriverManager::getConnection($this->connection->getParams(), $configuration);
+
try {
$table = new Table('portability_table');
$table->addColumn('Test_Int', 'integer');
diff --git a/tests/Logging/MiddlewareTest.php b/tests/Logging/MiddlewareTest.php
new file mode 100644
index 00000000000..4cd565e52d1
--- /dev/null
+++ b/tests/Logging/MiddlewareTest.php
@@ -0,0 +1,146 @@
+createMock(Connection::class);
+
+ $driver = $this->createMock(Driver::class);
+ $driver->method('connect')
+ ->willReturn($connection);
+
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $middleware = new Middleware($this->logger);
+ $this->driver = $middleware->wrap($driver);
+ }
+
+ public function testConnectAndDisconnect(): void
+ {
+ $this->logger->expects(self::exactly(2))
+ ->method('info')
+ ->withConsecutive(
+ [
+ 'Connecting with parameters {params}',
+ [
+ 'params' => [
+ 'username' => 'admin',
+ 'password' => '',
+ ],
+ ],
+ ],
+ ['Disconnecting', []],
+ );
+
+ $this->driver->connect([
+ 'username' => 'admin',
+ 'password' => 'Passw0rd!',
+ ]);
+ }
+
+ public function testQuery(): void
+ {
+ $this->logger->expects(self::once())
+ ->method('debug')
+ ->with('Executing query: {sql}', ['sql' => 'SELECT 1']);
+
+ $connection = $this->driver->connect([]);
+ $connection->query('SELECT 1');
+ }
+
+ public function testExec(): void
+ {
+ $this->logger->expects(self::once())
+ ->method('debug')
+ ->with('Executing statement: {sql}', ['sql' => 'DROP DATABASE doctrine']);
+
+ $connection = $this->driver->connect([]);
+ $connection->exec('DROP DATABASE doctrine');
+ }
+
+ public function testBeginCommitRollback(): void
+ {
+ $this->logger->expects(self::exactly(3))
+ ->method('debug')
+ ->withConsecutive(
+ ['Beginning transaction'],
+ ['Committing transaction'],
+ ['Rolling back transaction'],
+ );
+
+ $connection = $this->driver->connect([]);
+ $connection->beginTransaction();
+ $connection->commit();
+ $connection->rollBack();
+ }
+
+ public function testExecuteStatementWithUntypedParameters(): void
+ {
+ $this->logger->expects(self::once())
+ ->method('debug')
+ ->with('Executing statement: {sql} (parameters: {params}, types: {types})', [
+ 'sql' => 'SELECT ?',
+ 'params' => [42],
+ 'types' => [],
+ ]);
+
+ $connection = $this->driver->connect([]);
+ $statement = $connection->prepare('SELECT ?');
+ $statement->execute([42]);
+ }
+
+ public function testExecuteStatementWithTypedParameters(): void
+ {
+ $this->logger->expects(self::once())
+ ->method('debug')
+ ->with('Executing statement: {sql} (parameters: {params}, types: {types})', [
+ 'sql' => 'SELECT ?, ?',
+ 'params' => [1 => 42, 2 => 'Test'],
+ 'types' => [1 => ParameterType::INTEGER, 2 => ParameterType::STRING],
+ ]);
+
+ $connection = $this->driver->connect([]);
+ $statement = $connection->prepare('SELECT ?, ?');
+ $statement->bindValue(1, 42, ParameterType::INTEGER);
+ $statement->bindParam(2, $byRef, ParameterType::STRING);
+
+ $byRef = 'Test';
+ $statement->execute();
+ }
+
+ public function testExecuteStatementWithNamedParameters(): void
+ {
+ $this->logger->expects(self::once())
+ ->method('debug')
+ ->with('Executing statement: {sql} (parameters: {params}, types: {types})', [
+ 'sql' => 'SELECT :value',
+ 'params' => ['value' => 'Test'],
+ 'types' => ['value' => ParameterType::STRING],
+ ]);
+
+ $connection = $this->driver->connect([]);
+ $statement = $connection->prepare('SELECT :value');
+ $statement->bindValue('value', 'Test');
+
+ $statement->execute();
+ }
+}