Subiendo proyecto completo sin restricciones de git ignore

This commit is contained in:
Jose Sanchez
2023-08-17 11:44:02 -04:00
parent a0d4f5ba3b
commit 20f1c60600
19921 changed files with 2509159 additions and 45 deletions

4
vendor/doctrine/dbal/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,4 @@
Doctrine has [general contributing guidelines][contributor workflow], make
sure you follow them.
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html

19
vendor/doctrine/dbal/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2018 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
vendor/doctrine/dbal/README.md vendored Normal file
View File

@@ -0,0 +1,34 @@
# Doctrine DBAL
| [4.0-dev][4.0] | [3.6][3.6] |
|:-----------------------------------------------:|:---------------------------------------------------:|
| [![GitHub Actions][GA 4.0 image]][GA 4.0] | [![GitHub Actions][GA 3.6 image]][GA 3.6] |
| [![AppVeyor][AppVeyor 4.0 image]][AppVeyor 4.0] | [![AppVeyor][AppVeyor 3.6 image]][AppVeyor 3.6] |
| [![Code Coverage][Coverage image]][CodeCov 4.0] | [![Code Coverage][Coverage 3.6 image]][CodeCov 3.6] |
| N/A | [![Type Coverage][TypeCov 3.6 image]][TypeCov 3.6] |
Powerful ***D***ata***B***ase ***A***bstraction ***L***ayer with many features for database schema introspection and schema management.
## More resources:
* [Website](http://www.doctrine-project.org/projects/dbal.html)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/)
* [Issue Tracker](https://github.com/doctrine/dbal/issues)
[Coverage image]: https://codecov.io/gh/doctrine/dbal/branch/4.0.x/graph/badge.svg
[4.0]: https://github.com/doctrine/dbal/tree/4.0.x
[CodeCov 4.0]: https://codecov.io/gh/doctrine/dbal/branch/4.0.x
[AppVeyor 4.0]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.0.x
[AppVeyor 4.0 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.0.x?svg=true
[GA 4.0]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x
[GA 4.0 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg
[Coverage 3.6 image]: https://codecov.io/gh/doctrine/dbal/branch/3.6.x/graph/badge.svg
[3.6]: https://github.com/doctrine/dbal/tree/3.6.x
[CodeCov 3.6]: https://codecov.io/gh/doctrine/dbal/branch/3.6.x
[AppVeyor 3.6]: https://ci.appveyor.com/project/doctrine/dbal/branch/3.6.x
[AppVeyor 3.6 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/3.6.x?svg=true
[GA 3.6]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.6.x
[GA 3.6 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=3.6.x
[TypeCov 3.6]: https://shepherd.dev/github/doctrine/dbal
[TypeCov 3.6 image]: https://shepherd.dev/github/doctrine/dbal/coverage.svg

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
require __DIR__ . '/doctrine-dbal.php';

View File

@@ -0,0 +1,55 @@
<?php
use Doctrine\DBAL\Tools\Console\ConsoleRunner;
fwrite(
STDERR,
'[Warning] The use of this script is discouraged.'
. ' You find instructions on how to bootstrap the console runner in our documentation.'
. PHP_EOL,
);
echo PHP_EOL . PHP_EOL;
$files = [__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php'];
$loader = null;
$cwd = getcwd();
$directories = [$cwd, $cwd . DIRECTORY_SEPARATOR . 'config'];
$configFile = null;
foreach ($files as $file) {
if (file_exists($file)) {
$loader = require $file;
break;
}
}
if (! $loader) {
throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
if (! file_exists($configFile)) {
ConsoleRunner::printCliConfigTemplate();
exit(1);
}
if (! is_readable($configFile)) {
echo 'Configuration file [' . $configFile . '] does not have read permission.' . PHP_EOL;
exit(1);
}
$commands = [];
$connectionProvider = require $configFile;
ConsoleRunner::run($connectionProvider, $commands);

72
vendor/doctrine/dbal/composer.json vendored Normal file
View File

@@ -0,0 +1,72 @@
{
"name": "doctrine/dbal",
"type": "library",
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
"keywords": [
"abstraction",
"database",
"dbal",
"db2",
"mariadb",
"mssql",
"mysql",
"pgsql",
"postgresql",
"oci8",
"oracle",
"pdo",
"queryobject",
"sasql",
"sql",
"sqlite",
"sqlserver",
"sqlsrv"
],
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"require": {
"php": "^7.4 || ^8.0",
"composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1",
"doctrine/event-manager": "^1|^2",
"psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.1",
"phpstan/phpstan": "1.10.21",
"phpstan/phpstan-strict-rules": "^1.5",
"phpunit/phpunit": "9.6.9",
"psalm/plugin-phpunit": "0.18.4",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4|^6.0",
"symfony/console": "^4.4|^5.4|^6.0",
"vimeo/psalm": "4.30.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": ["bin/doctrine-dbal"],
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"composer/package-versions-deprecated": true
}
},
"autoload": {
"psr-4": { "Doctrine\\DBAL\\": "src" }
},
"autoload-dev": {
"psr-4": { "Doctrine\\DBAL\\Tests\\": "tests" }
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Doctrine\DBAL;
final class ArrayParameterType
{
/**
* Represents an array of ints to be expanded by Doctrine SQL parsing.
*/
public const INTEGER = ParameterType::INTEGER + Connection::ARRAY_PARAM_OFFSET;
/**
* Represents an array of strings to be expanded by Doctrine SQL parsing.
*/
public const STRING = ParameterType::STRING + Connection::ARRAY_PARAM_OFFSET;
/**
* Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
*/
public const ASCII = ParameterType::ASCII + Connection::ARRAY_PARAM_OFFSET;
/**
* @internal
*
* @psalm-param self::INTEGER|self::STRING|self::ASCII $type
*
* @psalm-return ParameterType::INTEGER|ParameterType::STRING|ParameterType::ASCII
*/
public static function toElementParameterType(int $type): int
{
return $type - Connection::ARRAY_PARAM_OFFSET;
}
private function __construct()
{
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Doctrine\DBAL\ArrayParameters;
use Throwable;
/** @internal */
interface Exception extends Throwable
{
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Doctrine\DBAL\ArrayParameters\Exception;
use Doctrine\DBAL\ArrayParameters\Exception;
use LogicException;
use function sprintf;
/** @psalm-immutable */
class MissingNamedParameter extends LogicException implements Exception
{
public static function new(string $name): self
{
return new self(
sprintf('Named parameter "%s" does not have a bound value.', $name),
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Doctrine\DBAL\ArrayParameters\Exception;
use Doctrine\DBAL\ArrayParameters\Exception;
use LogicException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
class MissingPositionalParameter extends LogicException implements Exception
{
public static function new(int $index): self
{
return new self(
sprintf('Positional parameter at index %d does not have a bound value.', $index),
);
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Doctrine\DBAL\Cache;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use function array_values;
use function count;
use function reset;
/** @internal The class is internal to the caching layer implementation. */
final class ArrayResult implements Result
{
/** @var list<array<string, mixed>> */
private array $data;
private int $columnCount = 0;
private int $num = 0;
/** @param list<array<string, mixed>> $data */
public function __construct(array $data)
{
$this->data = $data;
if (count($data) === 0) {
return;
}
$this->columnCount = count($data[0]);
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
$row = $this->fetch();
if ($row === false) {
return false;
}
return array_values($row);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch();
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
$row = $this->fetch();
if ($row === false) {
return false;
}
return reset($row);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return count($this->data);
}
public function columnCount(): int
{
return $this->columnCount;
}
public function free(): void
{
$this->data = [];
}
/** @return array<string, mixed>|false */
private function fetch()
{
if (! isset($this->data[$this->num])) {
return false;
}
return $this->data[$this->num++];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Doctrine\DBAL\Cache;
use Doctrine\DBAL\Exception;
/** @psalm-immutable */
class CacheException extends Exception
{
/** @return CacheException */
public static function noCacheKey()
{
return new self('No cache key was set.');
}
/** @return CacheException */
public static function noResultDriverConfigured()
{
return new self('Trying to cache a query but no result driver is configured.');
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace Doctrine\DBAL\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;
use Psr\Cache\CacheItemPoolInterface;
use TypeError;
use function get_class;
use function hash;
use function serialize;
use function sha1;
use function sprintf;
/**
* Query Cache Profile handles the data relevant for query caching.
*
* It is a value object, setter methods return NEW instances.
*/
class QueryCacheProfile
{
private ?CacheItemPoolInterface $resultCache = null;
/** @var int */
private $lifetime;
/** @var string|null */
private $cacheKey;
/**
* @param int $lifetime
* @param string|null $cacheKey
* @param CacheItemPoolInterface|Cache|null $resultCache
*/
public function __construct($lifetime = 0, $cacheKey = null, ?object $resultCache = null)
{
$this->lifetime = $lifetime;
$this->cacheKey = $cacheKey;
if ($resultCache instanceof CacheItemPoolInterface) {
$this->resultCache = $resultCache;
} elseif ($resultCache instanceof Cache) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4620',
'Passing an instance of %s to %s as $resultCache is deprecated. Pass an instance of %s instead.',
Cache::class,
__METHOD__,
CacheItemPoolInterface::class,
);
$this->resultCache = CacheAdapter::wrap($resultCache);
} elseif ($resultCache !== null) {
throw new TypeError(sprintf(
'$resultCache: Expected either null or an instance of %s or %s, got %s.',
CacheItemPoolInterface::class,
Cache::class,
get_class($resultCache),
));
}
}
public function getResultCache(): ?CacheItemPoolInterface
{
return $this->resultCache;
}
/**
* @deprecated Use {@see getResultCache()} instead.
*
* @return Cache|null
*/
public function getResultCacheDriver()
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4620',
'%s is deprecated, call getResultCache() instead.',
__METHOD__,
);
return $this->resultCache !== null ? DoctrineProvider::wrap($this->resultCache) : null;
}
/** @return int */
public function getLifetime()
{
return $this->lifetime;
}
/**
* @return string
*
* @throws CacheException
*/
public function getCacheKey()
{
if ($this->cacheKey === null) {
throw CacheException::noCacheKey();
}
return $this->cacheKey;
}
/**
* Generates the real cache key from query, params, types and connection parameters.
*
* @param string $sql
* @param list<mixed>|array<string, mixed> $params
* @param array<int, Type|int|string|null>|array<string, Type|int|string|null> $types
* @param array<string, mixed> $connectionParams
*
* @return array{string, string}
*/
public function generateCacheKeys($sql, $params, $types, array $connectionParams = [])
{
if (isset($connectionParams['password'])) {
unset($connectionParams['password']);
}
$realCacheKey = 'query=' . $sql .
'&params=' . serialize($params) .
'&types=' . serialize($types) .
'&connectionParams=' . hash('sha256', serialize($connectionParams));
// should the key be automatically generated using the inputs or is the cache key set?
$cacheKey = $this->cacheKey ?? sha1($realCacheKey);
return [$cacheKey, $realCacheKey];
}
public function setResultCache(CacheItemPoolInterface $cache): QueryCacheProfile
{
return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache);
}
/**
* @deprecated Use {@see setResultCache()} instead.
*
* @return QueryCacheProfile
*/
public function setResultCacheDriver(Cache $cache)
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4620',
'%s is deprecated, call setResultCache() instead.',
__METHOD__,
);
return new QueryCacheProfile($this->lifetime, $this->cacheKey, CacheAdapter::wrap($cache));
}
/**
* @param string|null $cacheKey
*
* @return QueryCacheProfile
*/
public function setCacheKey($cacheKey)
{
return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCache);
}
/**
* @param int $lifetime
*
* @return QueryCacheProfile
*/
public function setLifetime($lifetime)
{
return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCache);
}
}

28
vendor/doctrine/dbal/src/ColumnCase.php vendored Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace Doctrine\DBAL;
/**
* Contains portable column case conversions.
*/
final class ColumnCase
{
/**
* Convert column names to upper case.
*/
public const UPPER = 1;
/**
* Convert column names to lower case.
*/
public const LOWER = 2;
/**
* This class cannot be instantiated.
*
* @codeCoverageIgnore
*/
private function __construct()
{
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace Doctrine\DBAL;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Logging\SQLLogger;
use Doctrine\DBAL\Schema\SchemaManagerFactory;
use Doctrine\Deprecations\Deprecation;
use Psr\Cache\CacheItemPoolInterface;
use function func_num_args;
/**
* Configuration container for the Doctrine DBAL.
*/
class Configuration
{
/** @var Middleware[] */
private array $middlewares = [];
/**
* The SQL logger in use. If null, SQL logging is disabled.
*
* @var SQLLogger|null
*/
protected $sqlLogger;
/**
* The cache driver implementation that is used for query result caching.
*/
private ?CacheItemPoolInterface $resultCache = null;
/**
* The cache driver implementation that is used for query result caching.
*
* @deprecated Use {@see $resultCache} instead.
*
* @var Cache|null
*/
protected $resultCacheImpl;
/**
* The callable to use to filter schema assets.
*
* @var callable|null
*/
protected $schemaAssetsFilter;
/**
* The default auto-commit mode for connections.
*
* @var bool
*/
protected $autoCommit = true;
private ?SchemaManagerFactory $schemaManagerFactory = null;
public function __construct()
{
$this->schemaAssetsFilter = static function (): bool {
return true;
};
}
/**
* Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled.
*
* @deprecated Use {@see setMiddlewares()} and {@see \Doctrine\DBAL\Logging\Middleware} instead.
*/
public function setSQLLogger(?SQLLogger $logger = null): void
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4967',
'%s is deprecated, use setMiddlewares() and Logging\\Middleware instead.',
__METHOD__,
);
$this->sqlLogger = $logger;
}
/**
* Gets the SQL logger that is used.
*
* @deprecated
*/
public function getSQLLogger(): ?SQLLogger
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4967',
'%s is deprecated.',
__METHOD__,
);
return $this->sqlLogger;
}
/**
* Gets the cache driver implementation that is used for query result caching.
*/
public function getResultCache(): ?CacheItemPoolInterface
{
return $this->resultCache;
}
/**
* Gets the cache driver implementation that is used for query result caching.
*
* @deprecated Use {@see getResultCache()} instead.
*/
public function getResultCacheImpl(): ?Cache
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4620',
'%s is deprecated, call getResultCache() instead.',
__METHOD__,
);
return $this->resultCacheImpl;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*/
public function setResultCache(CacheItemPoolInterface $cache): void
{
$this->resultCacheImpl = DoctrineProvider::wrap($cache);
$this->resultCache = $cache;
}
/**
* Sets the cache driver implementation that is used for query result caching.
*
* @deprecated Use {@see setResultCache()} instead.
*/
public function setResultCacheImpl(Cache $cacheImpl): void
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4620',
'%s is deprecated, call setResultCache() instead.',
__METHOD__,
);
$this->resultCacheImpl = $cacheImpl;
$this->resultCache = CacheAdapter::wrap($cacheImpl);
}
/**
* Sets the callable to use to filter schema assets.
*/
public function setSchemaAssetsFilter(?callable $callable = null): void
{
if (func_num_args() < 1) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5483',
'Not passing an argument to %s is deprecated.',
__METHOD__,
);
} elseif ($callable === null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5483',
'Using NULL as a schema asset filter is deprecated.'
. ' Use a callable that always returns true instead.',
);
}
$this->schemaAssetsFilter = $callable;
}
/**
* Returns the callable to use to filter schema assets.
*/
public function getSchemaAssetsFilter(): ?callable
{
return $this->schemaAssetsFilter;
}
/**
* Sets the default auto-commit mode for connections.
*
* If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
* transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
* the method commit or the method rollback. By default, new connections are in auto-commit mode.
*
* @see getAutoCommit
*
* @param bool $autoCommit True to enable auto-commit mode; false to disable it
*/
public function setAutoCommit(bool $autoCommit): void
{
$this->autoCommit = $autoCommit;
}
/**
* Returns the default auto-commit mode for connections.
*
* @see setAutoCommit
*
* @return bool True if auto-commit mode is enabled by default for connections, false otherwise.
*/
public function getAutoCommit(): bool
{
return $this->autoCommit;
}
/**
* @param Middleware[] $middlewares
*
* @return $this
*/
public function setMiddlewares(array $middlewares): self
{
$this->middlewares = $middlewares;
return $this;
}
/** @return Middleware[] */
public function getMiddlewares(): array
{
return $this->middlewares;
}
public function getSchemaManagerFactory(): ?SchemaManagerFactory
{
return $this->schemaManagerFactory;
}
/** @return $this */
public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFactory): self
{
$this->schemaManagerFactory = $schemaManagerFactory;
return $this;
}
}

1976
vendor/doctrine/dbal/src/Connection.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\DBAL;
/** @psalm-immutable */
class ConnectionException extends Exception
{
/** @return ConnectionException */
public static function commitFailedRollbackOnly()
{
return new self('Transaction commit failed because the transaction has been marked for rollback only.');
}
/** @return ConnectionException */
public static function noActiveTransaction()
{
return new self('There is no active transaction.');
}
/** @return ConnectionException */
public static function savepointsNotSupported()
{
return new self('Savepoints are not supported by this driver.');
}
/** @return ConnectionException */
public static function mayNotAlterNestedTransactionWithSavepointsInTransaction()
{
return new self('May not alter the nested transaction with savepoints behavior while a transaction is open.');
}
}

View File

@@ -0,0 +1,374 @@
<?php
namespace Doctrine\DBAL\Connections;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Statement;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
use SensitiveParameter;
use function array_rand;
use function count;
/**
* Primary-Replica Connection
*
* Connection can be used with primary-replica setups.
*
* Important for the understanding of this connection should be how and when
* it picks the replica or primary.
*
* 1. Replica if primary was never picked before and ONLY if 'getWrappedConnection'
* or 'executeQuery' is used.
* 2. Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint',
* 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called.
* 3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards.
* 4. One replica connection is randomly picked ONCE during a request.
*
* ATTENTION: You can write to the replica with this connection if you execute a write query without
* opening up a transaction. For example:
*
* $conn = DriverManager::getConnection(...);
* $conn->executeQuery("DELETE FROM table");
*
* Be aware that Connection#executeQuery is a method specifically for READ
* operations only.
*
* Use Connection#executeStatement for any SQL statement that changes/updates
* state in the database (UPDATE, INSERT, DELETE or DDL statements).
*
* This connection is limited to replica operations using the
* Connection#executeQuery operation only, because it wouldn't be compatible
* with the ORM or SchemaManager code otherwise. Both use all the other
* operations in a context where writes could happen to a replica, which makes
* this restricted approach necessary.
*
* You can manually connect to the primary at any time by calling:
*
* $conn->ensureConnectedToPrimary();
*
* Instantiation through the DriverManager looks like:
*
* @psalm-import-type Params from DriverManager
* @example
*
* $conn = DriverManager::getConnection(array(
* 'wrapperClass' => 'Doctrine\DBAL\Connections\PrimaryReadReplicaConnection',
* 'driver' => 'pdo_mysql',
* 'primary' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''),
* 'replica' => array(
* array('user' => 'replica1', 'password', 'host' => '', 'dbname' => ''),
* array('user' => 'replica2', 'password', 'host' => '', 'dbname' => ''),
* )
* ));
*
* You can also pass 'driverOptions' and any other documented option to each of this drivers
* to pass additional information.
*/
class PrimaryReadReplicaConnection extends Connection
{
/**
* Primary and Replica connection (one of the randomly picked replicas).
*
* @var DriverConnection[]|null[]
*/
protected $connections = ['primary' => null, 'replica' => null];
/**
* You can keep the replica connection and then switch back to it
* during the request if you know what you are doing.
*
* @var bool
*/
protected $keepReplica = false;
/**
* Creates Primary Replica Connection.
*
* @internal The connection can be only instantiated by the driver manager.
*
* @param array<string,mixed> $params
* @psalm-param Params $params
*
* @throws Exception
* @throws InvalidArgumentException
*/
public function __construct(
array $params,
Driver $driver,
?Configuration $config = null,
?EventManager $eventManager = null
) {
if (! isset($params['replica'], $params['primary'])) {
throw new InvalidArgumentException('primary or replica configuration missing');
}
if (count($params['replica']) === 0) {
throw new InvalidArgumentException('You have to configure at least one replica.');
}
if (isset($params['driver'])) {
$params['primary']['driver'] = $params['driver'];
foreach ($params['replica'] as $replicaKey => $replica) {
$params['replica'][$replicaKey]['driver'] = $params['driver'];
}
}
$this->keepReplica = (bool) ($params['keepReplica'] ?? false);
parent::__construct($params, $driver, $config, $eventManager);
}
/**
* Checks if the connection is currently towards the primary or not.
*/
public function isConnectedToPrimary(): bool
{
return $this->_conn !== null && $this->_conn === $this->connections['primary'];
}
/**
* @param string|null $connectionName
*
* @return bool
*/
public function connect($connectionName = null)
{
if ($connectionName !== null) {
throw new InvalidArgumentException(
'Passing a connection name as first argument is not supported anymore.'
. ' Use ensureConnectedToPrimary()/ensureConnectedToReplica() instead.',
);
}
return $this->performConnect();
}
protected function performConnect(?string $connectionName = null): bool
{
$requestedConnectionChange = ($connectionName !== null);
$connectionName = $connectionName ?? 'replica';
if ($connectionName !== 'replica' && $connectionName !== 'primary') {
throw new InvalidArgumentException('Invalid option to connect(), only primary or replica allowed.');
}
// If we have a connection open, and this is not an explicit connection
// change request, then abort right here, because we are already done.
// This prevents writes to the replica in case of "keepReplica" option enabled.
if ($this->_conn !== null && ! $requestedConnectionChange) {
return false;
}
$forcePrimaryAsReplica = false;
if ($this->getTransactionNestingLevel() > 0) {
$connectionName = 'primary';
$forcePrimaryAsReplica = true;
}
if (isset($this->connections[$connectionName])) {
$this->_conn = $this->connections[$connectionName];
if ($forcePrimaryAsReplica && ! $this->keepReplica) {
$this->connections['replica'] = $this->_conn;
}
return false;
}
if ($connectionName === 'primary') {
$this->connections['primary'] = $this->_conn = $this->connectTo($connectionName);
// Set replica connection to primary to avoid invalid reads
if (! $this->keepReplica) {
$this->connections['replica'] = $this->connections['primary'];
}
} else {
$this->connections['replica'] = $this->_conn = $this->connectTo($connectionName);
}
if ($this->_eventManager->hasListeners(Events::postConnect)) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/5784',
'Subscribing to %s events is deprecated. Implement a middleware instead.',
Events::postConnect,
);
$eventArgs = new ConnectionEventArgs($this);
$this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
}
return true;
}
/**
* Connects to the primary node of the database cluster.
*
* All following statements after this will be executed against the primary node.
*/
public function ensureConnectedToPrimary(): bool
{
return $this->performConnect('primary');
}
/**
* Connects to a replica node of the database cluster.
*
* All following statements after this will be executed against the replica node,
* unless the keepReplica option is set to false and a primary connection
* was already opened.
*/
public function ensureConnectedToReplica(): bool
{
return $this->performConnect('replica');
}
/**
* Connects to a specific connection.
*
* @param string $connectionName
*
* @return DriverConnection
*
* @throws Exception
*/
protected function connectTo($connectionName)
{
$params = $this->getParams();
$connectionParams = $this->chooseConnectionConfiguration($connectionName, $params);
try {
return $this->_driver->connect($connectionParams);
} catch (DriverException $e) {
throw $this->convertException($e);
}
}
/**
* @param string $connectionName
* @param mixed[] $params
*
* @return mixed
*/
protected function chooseConnectionConfiguration(
$connectionName,
#[SensitiveParameter]
$params
) {
if ($connectionName === 'primary') {
return $params['primary'];
}
$config = $params['replica'][array_rand($params['replica'])];
if (! isset($config['charset']) && isset($params['primary']['charset'])) {
$config['charset'] = $params['primary']['charset'];
}
return $config;
}
/**
* {@inheritDoc}
*/
public function executeStatement($sql, array $params = [], array $types = [])
{
$this->ensureConnectedToPrimary();
return parent::executeStatement($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
$this->ensureConnectedToPrimary();
return parent::beginTransaction();
}
/**
* {@inheritDoc}
*/
public function commit()
{
$this->ensureConnectedToPrimary();
return parent::commit();
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
$this->ensureConnectedToPrimary();
return parent::rollBack();
}
/**
* {@inheritDoc}
*/
public function close()
{
unset($this->connections['primary'], $this->connections['replica']);
parent::close();
$this->_conn = null;
$this->connections = ['primary' => null, 'replica' => null];
}
/**
* {@inheritDoc}
*/
public function createSavepoint($savepoint)
{
$this->ensureConnectedToPrimary();
parent::createSavepoint($savepoint);
}
/**
* {@inheritDoc}
*/
public function releaseSavepoint($savepoint)
{
$this->ensureConnectedToPrimary();
parent::releaseSavepoint($savepoint);
}
/**
* {@inheritDoc}
*/
public function rollbackSavepoint($savepoint)
{
$this->ensureConnectedToPrimary();
parent::rollbackSavepoint($savepoint);
}
public function prepare(string $sql): Statement
{
$this->ensureConnectedToPrimary();
return parent::prepare($sql);
}
}

57
vendor/doctrine/dbal/src/Driver.php vendored Normal file
View File

@@ -0,0 +1,57 @@
<?php
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use SensitiveParameter;
/**
* Driver interface.
* Interface that all DBAL drivers must implement.
*
* @psalm-import-type Params from DriverManager
*/
interface Driver
{
/**
* Attempts to create a connection with the database.
*
* @param array<string, mixed> $params All connection parameters.
* @psalm-param Params $params All connection parameters.
*
* @return DriverConnection The database connection.
*
* @throws Exception
*/
public function connect(
#[SensitiveParameter]
array $params
);
/**
* Gets the DatabasePlatform instance that provides all the metadata about
* the platform this driver connects to.
*
* @return AbstractPlatform The database platform.
*/
public function getDatabasePlatform();
/**
* Gets the SchemaManager that can be used to inspect and change the underlying
* database schema of the platform this driver connects to.
*
* @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead.
*
* @return AbstractSchemaManager
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform);
/**
* Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions.
*/
public function getExceptionConverter(): ExceptionConverter;
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Query;
interface ExceptionConverter
{
/**
* Converts a given driver-level exception into a DBAL-level driver exception.
*
* Implementors should use the vendor-specific error code and SQLSTATE of the exception
* and instantiate the most appropriate specialized {@see DriverException} subclass.
*
* @param Exception $exception The driver exception to convert.
* @param Query|null $query The SQL query that triggered the exception, if any.
*
* @return DriverException An instance of {@see DriverException} or one of its subclasses.
*/
public function convert(Exception $exception, ?Query $query): DriverException;
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\IBMDB2;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
/**
* @internal
*
* @link https://www.ibm.com/docs/en/db2/11.5?topic=messages-sql
*/
final class ExceptionConverter implements ExceptionConverterInterface
{
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case -104:
return new SyntaxErrorException($exception, $query);
case -203:
return new NonUniqueFieldNameException($exception, $query);
case -204:
return new TableNotFoundException($exception, $query);
case -206:
return new InvalidFieldNameException($exception, $query);
case -407:
return new NotNullConstraintViolationException($exception, $query);
case -530:
case -531:
case -532:
case -20356:
return new ForeignKeyConstraintViolationException($exception, $query);
case -601:
return new TableExistsException($exception, $query);
case -803:
return new UniqueConstraintViolationException($exception, $query);
case -1336:
case -30082:
return new ConnectionException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\MySQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
/** @internal */
final class ExceptionConverter implements ExceptionConverterInterface
{
/**
* @link https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html
* @link https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
*/
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case 1008:
return new DatabaseDoesNotExist($exception, $query);
case 1213:
return new DeadlockException($exception, $query);
case 1205:
return new LockWaitTimeoutException($exception, $query);
case 1050:
return new TableExistsException($exception, $query);
case 1051:
case 1146:
return new TableNotFoundException($exception, $query);
case 1216:
case 1217:
case 1451:
case 1452:
case 1701:
return new ForeignKeyConstraintViolationException($exception, $query);
case 1062:
case 1557:
case 1569:
case 1586:
return new UniqueConstraintViolationException($exception, $query);
case 1054:
case 1166:
case 1611:
return new InvalidFieldNameException($exception, $query);
case 1052:
case 1060:
case 1110:
return new NonUniqueFieldNameException($exception, $query);
case 1064:
case 1149:
case 1287:
case 1341:
case 1342:
case 1343:
case 1344:
case 1382:
case 1479:
case 1541:
case 1554:
case 1626:
return new SyntaxErrorException($exception, $query);
case 1044:
case 1045:
case 1046:
case 1049:
case 1095:
case 1142:
case 1143:
case 1227:
case 1370:
case 1429:
case 2002:
case 2005:
case 2054:
return new ConnectionException($exception, $query);
case 2006:
return new ConnectionLost($exception, $query);
case 1048:
case 1121:
case 1138:
case 1171:
case 1252:
case 1263:
case 1364:
case 1566:
return new NotNullConstraintViolationException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\OCI;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
/** @internal */
final class ExceptionConverter implements ExceptionConverterInterface
{
/** @link http://www.dba-oracle.com/t_error_code_list.htm */
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case 1:
case 2299:
case 38911:
return new UniqueConstraintViolationException($exception, $query);
case 904:
return new InvalidFieldNameException($exception, $query);
case 918:
case 960:
return new NonUniqueFieldNameException($exception, $query);
case 923:
return new SyntaxErrorException($exception, $query);
case 942:
return new TableNotFoundException($exception, $query);
case 955:
return new TableExistsException($exception, $query);
case 1017:
case 12545:
return new ConnectionException($exception, $query);
case 1400:
return new NotNullConstraintViolationException($exception, $query);
case 1918:
return new DatabaseDoesNotExist($exception, $query);
case 2289:
case 2443:
case 4080:
return new DatabaseObjectNotFoundException($exception, $query);
case 2266:
case 2291:
case 2292:
return new ForeignKeyConstraintViolationException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\PostgreSQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SchemaDoesNotExist;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
use function strpos;
/** @internal */
final class ExceptionConverter implements ExceptionConverterInterface
{
/** @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html */
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getSQLState()) {
case '40001':
case '40P01':
return new DeadlockException($exception, $query);
case '0A000':
// Foreign key constraint violations during a TRUNCATE operation
// are considered "feature not supported" in PostgreSQL.
if (strpos($exception->getMessage(), 'truncate') !== false) {
return new ForeignKeyConstraintViolationException($exception, $query);
}
break;
case '23502':
return new NotNullConstraintViolationException($exception, $query);
case '23503':
return new ForeignKeyConstraintViolationException($exception, $query);
case '23505':
return new UniqueConstraintViolationException($exception, $query);
case '3D000':
return new DatabaseDoesNotExist($exception, $query);
case '3F000':
return new SchemaDoesNotExist($exception, $query);
case '42601':
return new SyntaxErrorException($exception, $query);
case '42702':
return new NonUniqueFieldNameException($exception, $query);
case '42703':
return new InvalidFieldNameException($exception, $query);
case '42P01':
return new TableNotFoundException($exception, $query);
case '42P07':
return new TableExistsException($exception, $query);
case '08006':
return new ConnectionException($exception, $query);
}
// Prior to fixing https://bugs.php.net/bug.php?id=64705 (PHP 7.4.10),
// in some cases (mainly connection errors) the PDO exception wouldn't provide a SQLSTATE via its code.
// We have to match against the SQLSTATE in the error message in these cases.
if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) {
return new ConnectionException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\SQLSrv;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
/**
* @internal
*
* @link https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors
*/
final class ExceptionConverter implements ExceptionConverterInterface
{
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case 102:
return new SyntaxErrorException($exception, $query);
case 207:
return new InvalidFieldNameException($exception, $query);
case 208:
return new TableNotFoundException($exception, $query);
case 209:
return new NonUniqueFieldNameException($exception, $query);
case 515:
return new NotNullConstraintViolationException($exception, $query);
case 547:
case 4712:
return new ForeignKeyConstraintViolationException($exception, $query);
case 2601:
case 2627:
return new UniqueConstraintViolationException($exception, $query);
case 2714:
return new TableExistsException($exception, $query);
case 3701:
case 15151:
return new DatabaseObjectNotFoundException($exception, $query);
case 11001:
case 18456:
return new ConnectionException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;
use function strpos;
/** @internal */
final class ExceptionConverter implements ExceptionConverterInterface
{
/** @link http://www.sqlite.org/c3ref/c_abort.html */
public function convert(Exception $exception, ?Query $query): DriverException
{
if (strpos($exception->getMessage(), 'database is locked') !== false) {
return new LockWaitTimeoutException($exception, $query);
}
if (
strpos($exception->getMessage(), 'must be unique') !== false ||
strpos($exception->getMessage(), 'is not unique') !== false ||
strpos($exception->getMessage(), 'are not unique') !== false ||
strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false
) {
return new UniqueConstraintViolationException($exception, $query);
}
if (
strpos($exception->getMessage(), 'may not be NULL') !== false ||
strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false
) {
return new NotNullConstraintViolationException($exception, $query);
}
if (strpos($exception->getMessage(), 'no such table:') !== false) {
return new TableNotFoundException($exception, $query);
}
if (strpos($exception->getMessage(), 'already exists') !== false) {
return new TableExistsException($exception, $query);
}
if (strpos($exception->getMessage(), 'has no column named') !== false) {
return new InvalidFieldNameException($exception, $query);
}
if (strpos($exception->getMessage(), 'ambiguous column name') !== false) {
return new NonUniqueFieldNameException($exception, $query);
}
if (strpos($exception->getMessage(), 'syntax error') !== false) {
return new SyntaxErrorException($exception, $query);
}
if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) {
return new ReadOnlyException($exception, $query);
}
if (strpos($exception->getMessage(), 'unable to open database file') !== false) {
return new ConnectionException($exception, $query);
}
if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) {
return new ForeignKeyConstraintViolationException($exception, $query);
}
return new DriverException($exception, $query);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\Deprecations\Deprecation;
use function array_merge;
use function strpos;
/**
* User-defined SQLite functions.
*
* @internal
*/
final class UserDefinedFunctions
{
private const DEFAULT_FUNCTIONS = [
'sqrt' => ['callback' => [SqlitePlatform::class, 'udfSqrt'], 'numArgs' => 1],
'mod' => ['callback' => [SqlitePlatform::class, 'udfMod'], 'numArgs' => 2],
'locate' => ['callback' => [SqlitePlatform::class, 'udfLocate'], 'numArgs' => -1],
];
/**
* @param callable(string, callable, int): bool $callback
* @param array<string, array{callback: callable, numArgs: int}> $additionalFunctions
*/
public static function register(callable $callback, array $additionalFunctions = []): void
{
$userDefinedFunctions = array_merge(self::DEFAULT_FUNCTIONS, $additionalFunctions);
foreach ($userDefinedFunctions as $function => $data) {
$callback($function, $data['callback'], $data['numArgs']);
}
}
/**
* User-defined function that implements MOD().
*
* @param int $a
* @param int $b
*/
public static function mod($a, $b): int
{
return $a % $b;
}
/**
* User-defined function that implements LOCATE().
*
* @param string $str
* @param string $substr
* @param int $offset
*/
public static function locate($str, $substr, $offset = 0): int
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5749',
'Relying on DBAL\'s emulated LOCATE() function is deprecated. '
. 'Use INSTR() or %s::getLocateExpression() instead.',
AbstractPlatform::class,
);
// SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
// So we have to make them compatible if an offset is given.
if ($offset > 0) {
$offset -= 1;
}
$pos = strpos($str, $substr, $offset);
if ($pos !== false) {
return $pos + 1;
}
return 0;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\IBMDB2\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\DB2SchemaManager;
use Doctrine\Deprecations\Deprecation;
use function assert;
/**
* Abstract base implementation of the {@see Driver} interface for IBM DB2 based drivers.
*/
abstract class AbstractDB2Driver implements Driver
{
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return new DB2Platform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link DB2Platform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractDB2Driver::getSchemaManager() is deprecated.'
. ' Use DB2Platform::createSchemaManager() instead.',
);
assert($platform instanceof DB2Platform);
return new DB2SchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
use Exception as BaseException;
use Throwable;
/**
* Base implementation of the {@see Exception} interface.
*
* @internal
*
* @psalm-immutable
*/
abstract class AbstractException extends BaseException implements Exception
{
/**
* The SQLSTATE of the driver.
*/
private ?string $sqlState = null;
/**
* @param string $message The driver error message.
* @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any.
* @param int $code The driver specific error code if any.
* @param Throwable|null $previous The previous throwable used for the exception chaining.
*/
public function __construct($message, $sqlState = null, $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->sqlState = $sqlState;
}
/**
* {@inheritDoc}
*/
public function getSQLState()
{
return $this->sqlState;
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\MySQL;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\MySQLSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use Doctrine\Deprecations\Deprecation;
use function assert;
use function preg_match;
use function stripos;
use function version_compare;
/**
* Abstract base implementation of the {@see Driver} interface for MySQL based drivers.
*/
abstract class AbstractMySQLDriver implements VersionAwarePlatformDriver
{
/**
* {@inheritDoc}
*
* @throws Exception
*/
public function createDatabasePlatformForVersion($version)
{
$mariadb = stripos($version, 'mariadb') !== false;
if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) {
return new MariaDb1027Platform();
}
if (! $mariadb) {
$oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version);
if (version_compare($oracleMysqlVersion, '8', '>=')) {
if (! version_compare($version, '8.0.0', '>=')) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/dbal/pull/5779',
'Version detection logic for MySQL will change in DBAL 4. '
. 'Please specify the version as the server reports it, e.g. "8.0.31" instead of "8".',
);
}
return new MySQL80Platform();
}
if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) {
if (! version_compare($version, '5.7.9', '>=')) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/dbal/pull/5779',
'Version detection logic for MySQL will change in DBAL 4. '
. 'Please specify the version as the server reports it, e.g. "5.7.40" instead of "5.7".',
);
}
return new MySQL57Platform();
}
}
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5060',
'MySQL 5.6 support is deprecated and will be removed in DBAL 4.'
. ' Consider upgrading to MySQL 5.7 or later.',
);
return $this->getDatabasePlatform();
}
/**
* Get a normalized 'version number' from the server string
* returned by Oracle MySQL servers.
*
* @param string $versionString Version string returned by the driver, i.e. '5.7.10'
*
* @throws Exception
*/
private function getOracleMysqlVersionNumber(string $versionString): string
{
if (
preg_match(
'/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/',
$versionString,
$versionParts,
) === 0
) {
throw Exception::invalidPlatformVersionSpecified(
$versionString,
'<major_version>.<minor_version>.<patch_version>',
);
}
$majorVersion = $versionParts['major'];
$minorVersion = $versionParts['minor'] ?? 0;
$patchVersion = $versionParts['patch'] ?? null;
if ($majorVersion === '5' && $minorVersion === '7') {
$patchVersion ??= '9';
}
return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
}
/**
* Detect MariaDB server version, including hack for some mariadb distributions
* that starts with the prefix '5.5.5-'
*
* @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial'
*
* @throws Exception
*/
private function getMariaDbMysqlVersionNumber(string $versionString): string
{
if (stripos($versionString, 'MariaDB') === 0) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/dbal/pull/5779',
'Version detection logic for MySQL will change in DBAL 4. '
. 'Please specify the version as the server reports it, '
. 'e.g. "10.9.3-MariaDB" instead of "mariadb-10.9".',
);
}
if (
preg_match(
'/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
$versionString,
$versionParts,
) === 0
) {
throw Exception::invalidPlatformVersionSpecified(
$versionString,
'^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>',
);
}
return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
}
/**
* {@inheritDoc}
*
* @return AbstractMySQLPlatform
*/
public function getDatabasePlatform()
{
return new MySQLPlatform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link AbstractMySQLPlatform::createSchemaManager()} instead.
*
* @return MySQLSchemaManager
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractMySQLDriver::getSchemaManager() is deprecated.'
. ' Use MySQLPlatform::createSchemaManager() instead.',
);
assert($platform instanceof AbstractMySQLPlatform);
return new MySQLSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverter
{
return new MySQL\ExceptionConverter();
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\OCI;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\OracleSchemaManager;
use Doctrine\Deprecations\Deprecation;
use function assert;
/**
* Abstract base implementation of the {@see Driver} interface for Oracle based drivers.
*/
abstract class AbstractOracleDriver implements Driver
{
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return new OraclePlatform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link OraclePlatform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractOracleDriver::getSchemaManager() is deprecated.'
. ' Use OraclePlatform::createSchemaManager() instead.',
);
assert($platform instanceof OraclePlatform);
return new OracleSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverter
{
return new OCI\ExceptionConverter();
}
/**
* Returns an appropriate Easy Connect String for the given parameters.
*
* @param array<string, mixed> $params The connection parameters to return the Easy Connect String for.
*
* @return string
*/
protected function getEasyConnectString(array $params)
{
return (string) EasyConnectString::fromConnectionParameters($params);
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\AbstractOracleDriver;
use function implode;
use function is_array;
use function sprintf;
/**
* Represents an Oracle Easy Connect string
*
* @link https://docs.oracle.com/database/121/NETAG/naming.htm
*/
final class EasyConnectString
{
private string $string;
private function __construct(string $string)
{
$this->string = $string;
}
public function __toString(): string
{
return $this->string;
}
/**
* Creates the object from an array representation
*
* @param mixed[] $params
*/
public static function fromArray(array $params): self
{
return new self(self::renderParams($params));
}
/**
* Creates the object from the given DBAL connection parameters.
*
* @param mixed[] $params
*/
public static function fromConnectionParameters(array $params): self
{
if (isset($params['connectstring'])) {
return new self($params['connectstring']);
}
if (! isset($params['host'])) {
return new self($params['dbname'] ?? '');
}
$connectData = [];
if (isset($params['servicename']) || isset($params['dbname'])) {
$serviceKey = 'SID';
if (isset($params['service'])) {
$serviceKey = 'SERVICE_NAME';
}
$serviceName = $params['servicename'] ?? $params['dbname'];
$connectData[$serviceKey] = $serviceName;
}
if (isset($params['instancename'])) {
$connectData['INSTANCE_NAME'] = $params['instancename'];
}
if (! empty($params['pooled'])) {
$connectData['SERVER'] = 'POOLED';
}
return self::fromArray([
'DESCRIPTION' => [
'ADDRESS' => [
'PROTOCOL' => 'TCP',
'HOST' => $params['host'],
'PORT' => $params['port'] ?? 1521,
],
'CONNECT_DATA' => $connectData,
],
]);
}
/** @param mixed[] $params */
private static function renderParams(array $params): string
{
$chunks = [];
foreach ($params as $key => $value) {
$string = self::renderValue($value);
if ($string === '') {
continue;
}
$chunks[] = sprintf('(%s=%s)', $key, $string);
}
return implode('', $chunks);
}
/** @param mixed $value */
private static function renderValue($value): string
{
if (is_array($value)) {
return self::renderParams($value);
}
return (string) $value;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\PostgreSQL;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use Doctrine\Deprecations\Deprecation;
use function assert;
use function preg_match;
use function version_compare;
/**
* Abstract base implementation of the {@see Driver} interface for PostgreSQL based drivers.
*/
abstract class AbstractPostgreSQLDriver implements VersionAwarePlatformDriver
{
/**
* {@inheritDoc}
*/
public function createDatabasePlatformForVersion($version)
{
if (preg_match('/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $version, $versionParts) === 0) {
throw Exception::invalidPlatformVersionSpecified(
$version,
'<major_version>.<minor_version>.<patch_version>',
);
}
$majorVersion = $versionParts['major'];
$minorVersion = $versionParts['minor'] ?? 0;
$patchVersion = $versionParts['patch'] ?? 0;
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
if (version_compare($version, '10.0', '>=')) {
return new PostgreSQL100Platform();
}
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5060',
'PostgreSQL 9 support is deprecated and will be removed in DBAL 4.'
. ' Consider upgrading to Postgres 10 or later.',
);
return new PostgreSQL94Platform();
}
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return new PostgreSQL94Platform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link PostgreSQLPlatform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractPostgreSQLDriver::getSchemaManager() is deprecated.'
. ' Use PostgreSQLPlatform::createSchemaManager() instead.',
);
assert($platform instanceof PostgreSQLPlatform);
return new PostgreSQLSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverter
{
return new PostgreSQL\ExceptionConverter();
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\SQLSrv\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\SQLServerSchemaManager;
use Doctrine\Deprecations\Deprecation;
use function assert;
/**
* Abstract base implementation of the {@see Driver} interface for Microsoft SQL Server based drivers.
*/
abstract class AbstractSQLServerDriver implements Driver
{
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return new SQLServer2012Platform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link SQLServerPlatform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractSQLServerDriver::getSchemaManager() is deprecated.'
. ' Use SQLServerPlatform::createSchemaManager() instead.',
);
assert($platform instanceof SQLServerPlatform);
return new SQLServerSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class PortWithoutHost extends AbstractException
{
public static function new(): self
{
return new self('Connection port specified without the host');
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\SqliteSchemaManager;
use Doctrine\Deprecations\Deprecation;
use function assert;
/**
* Abstract base implementation of the {@see Doctrine\DBAL\Driver} interface for SQLite based drivers.
*/
abstract class AbstractSQLiteDriver implements Driver
{
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return new SqlitePlatform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link SqlitePlatform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractSQLiteDriver::getSchemaManager() is deprecated.'
. ' Use SqlitePlatform::createSchemaManager() instead.',
);
assert($platform instanceof SqlitePlatform);
return new SqliteSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverter
{
return new SQLite\ExceptionConverter();
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\DBAL\Driver\AbstractSQLiteDriver\Middleware;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
use SensitiveParameter;
class EnableForeignKeys implements Middleware
{
public function wrap(Driver $driver): Driver
{
return new class ($driver) extends AbstractDriverMiddleware {
/**
* {@inheritDoc}
*/
public function connect(
#[SensitiveParameter]
array $params
): Connection {
$connection = parent::connect($params);
$connection->exec('PRAGMA foreign_keys=ON');
return $connection;
}
};
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\ParameterType;
/**
* Connection interface.
* Driver connections must implement this interface.
*
* @method resource|object getNativeConnection()
*/
interface Connection
{
/**
* Prepares a statement for execution and returns a Statement object.
*
* @throws Exception
*/
public function prepare(string $sql): Statement;
/**
* Executes an SQL statement, returning a result set as a Statement object.
*
* @throws Exception
*/
public function query(string $sql): Result;
/**
* Quotes a string for use in a query.
*
* The usage of this method is discouraged. Use prepared statements
* or {@see AbstractPlatform::quoteStringLiteral()} instead.
*
* @param mixed $value
* @param int $type
*
* @return mixed
*/
public function quote($value, $type = ParameterType::STRING);
/**
* Executes an SQL statement and return the number of affected rows.
*
* @throws Exception
*/
public function exec(string $sql): int;
/**
* Returns the ID of the last inserted row or sequence value.
*
* @param string|null $name
*
* @return string|int|false
*
* @throws Exception
*/
public function lastInsertId($name = null);
/**
* Initiates a transaction.
*
* @return bool TRUE on success or FALSE on failure.
*
* @throws Exception
*/
public function beginTransaction();
/**
* Commits a transaction.
*
* @return bool TRUE on success or FALSE on failure.
*
* @throws Exception
*/
public function commit();
/**
* Rolls back the current transaction, as initiated by beginTransaction().
*
* @return bool TRUE on success or FALSE on failure.
*
* @throws Exception
*/
public function rollBack();
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
use Throwable;
/** @psalm-immutable */
interface Exception extends Throwable
{
/**
* Returns the SQLSTATE the driver was in at the time the error occurred.
*
* Returns null if the driver does not provide a SQLSTATE for the error occurred.
*
* @return string|null
*/
public function getSQLState();
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class UnknownParameterType extends AbstractException
{
/** @param mixed $type */
public static function new($type): self
{
return new self(sprintf('Unknown parameter type, %d given.', $type));
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
/** @internal */
final class FetchUtils
{
/**
* @return mixed|false
*
* @throws Exception
*/
public static function fetchOne(Result $result)
{
$row = $result->fetchNumeric();
if ($row === false) {
return false;
}
return $row[0];
}
/**
* @return list<list<mixed>>
*
* @throws Exception
*/
public static function fetchAllNumeric(Result $result): array
{
$rows = [];
while (($row = $result->fetchNumeric()) !== false) {
$rows[] = $row;
}
return $rows;
}
/**
* @return list<array<string,mixed>>
*
* @throws Exception
*/
public static function fetchAllAssociative(Result $result): array
{
$rows = [];
while (($row = $result->fetchAssociative()) !== false) {
$rows[] = $row;
}
return $rows;
}
/**
* @return list<mixed>
*
* @throws Exception
*/
public static function fetchFirstColumn(Result $result): array
{
$rows = [];
while (($row = $result->fetchOne()) !== false) {
$rows[] = $row;
}
return $rows;
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError;
use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use stdClass;
use function assert;
use function db2_autocommit;
use function db2_commit;
use function db2_escape_string;
use function db2_exec;
use function db2_last_insert_id;
use function db2_num_rows;
use function db2_prepare;
use function db2_rollback;
use function db2_server_info;
use function error_get_last;
use const DB2_AUTOCOMMIT_OFF;
use const DB2_AUTOCOMMIT_ON;
final class Connection implements ServerInfoAwareConnection
{
/** @var resource */
private $connection;
/**
* @internal The connection can be only instantiated by its driver.
*
* @param resource $connection
*/
public function __construct($connection)
{
$this->connection = $connection;
}
/**
* {@inheritDoc}
*/
public function getServerVersion()
{
$serverInfo = db2_server_info($this->connection);
assert($serverInfo instanceof stdClass);
return $serverInfo->DBMS_VER;
}
public function prepare(string $sql): DriverStatement
{
$stmt = @db2_prepare($this->connection, $sql);
if ($stmt === false) {
throw PrepareFailed::new(error_get_last());
}
return new Statement($stmt);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
$value = db2_escape_string($value);
if ($type === ParameterType::INTEGER) {
return $value;
}
return "'" . $value . "'";
}
public function exec(string $sql): int
{
$stmt = @db2_exec($this->connection, $sql);
if ($stmt === false) {
throw StatementError::new();
}
return db2_num_rows($stmt);
}
/**
* {@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 db2_last_insert_id($this->connection) ?? false;
}
public function beginTransaction(): bool
{
return db2_autocommit($this->connection, DB2_AUTOCOMMIT_OFF);
}
public function commit(): bool
{
if (! db2_commit($this->connection)) {
throw ConnectionError::new($this->connection);
}
return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
}
public function rollBack(): bool
{
if (! db2_rollback($this->connection)) {
throw ConnectionError::new($this->connection);
}
return db2_autocommit($this->connection, DB2_AUTOCOMMIT_ON);
}
/** @return resource */
public function getNativeConnection()
{
return $this->connection;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
use SensitiveParameter;
use function implode;
use function sprintf;
use function strpos;
/**
* IBM DB2 DSN
*/
final class DataSourceName
{
private string $string;
private function __construct(
#[SensitiveParameter]
string $string
) {
$this->string = $string;
}
public function toString(): string
{
return $this->string;
}
/**
* Creates the object from an array representation
*
* @param array<string,mixed> $params
*/
public static function fromArray(
#[SensitiveParameter]
array $params
): self {
$chunks = [];
foreach ($params as $key => $value) {
$chunks[] = sprintf('%s=%s', $key, $value);
}
return new self(implode(';', $chunks));
}
/**
* Creates the object from the given DBAL connection parameters.
*
* @param array<string,mixed> $params
*/
public static function fromConnectionParameters(
#[SensitiveParameter]
array $params
): self {
if (isset($params['dbname']) && strpos($params['dbname'], '=') !== false) {
return new self($params['dbname']);
}
$dsnParams = [];
foreach (
[
'host' => 'HOSTNAME',
'port' => 'PORT',
'protocol' => 'PROTOCOL',
'dbname' => 'DATABASE',
'user' => 'UID',
'password' => 'PWD',
] as $dbalParam => $dsnParam
) {
if (! isset($params[$dbalParam])) {
continue;
}
$dsnParams[$dsnParam] = $params[$dbalParam];
}
return self::fromArray($dsnParams);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\AbstractDB2Driver;
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed;
use SensitiveParameter;
use function db2_connect;
use function db2_pconnect;
final class Driver extends AbstractDB2Driver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$dataSourceName = DataSourceName::fromConnectionParameters($params)->toString();
$username = $params['user'] ?? '';
$password = $params['password'] ?? '';
$driverOptions = $params['driverOptions'] ?? [];
if (! empty($params['persistent'])) {
$connection = db2_pconnect($dataSourceName, $username, $password, $driverOptions);
} else {
$connection = db2_connect($dataSourceName, $username, $password, $driverOptions);
}
if ($connection === false) {
throw ConnectionFailed::new();
}
return new Connection($connection);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCopyStreamToStream extends AbstractException
{
/** @psalm-param array{message: string}|null $error */
public static function new(?array $error): self
{
$message = 'Could not copy source stream to temporary file';
if ($error !== null) {
$message .= ': ' . $error['message'];
}
return new self($message);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class CannotCreateTemporaryFile extends AbstractException
{
/** @psalm-param array{message: string}|null $error */
public static function new(?array $error): self
{
$message = 'Could not create temporary file';
if ($error !== null) {
$message .= ': ' . $error['message'];
}
return new self($message);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function db2_conn_error;
use function db2_conn_errormsg;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionError extends AbstractException
{
/** @param resource $connection */
public static function new($connection): self
{
$message = db2_conn_errormsg($connection);
$sqlState = db2_conn_error($connection);
return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function db2_conn_error;
use function db2_conn_errormsg;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends AbstractException
{
public static function new(): self
{
$message = db2_conn_errormsg();
$sqlState = db2_conn_error();
return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function preg_match;
/**
* @internal
*
* @psalm-immutable
*/
final class Factory
{
/**
* @param callable(int): T $constructor
*
* @return T
*
* @template T of AbstractException
*/
public static function create(string $message, callable $constructor): AbstractException
{
$code = 0;
if (preg_match('/ SQL(\d+)N /', $message, $matches) === 1) {
$code = -(int) $matches[1];
}
return $constructor($code);
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class PrepareFailed extends AbstractException
{
/** @psalm-param array{message: string}|null $error */
public static function new(?array $error): self
{
if ($error === null) {
return new self('Unknown error');
}
return new self($error['message']);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function db2_stmt_error;
use function db2_stmt_errormsg;
/**
* @internal
*
* @psalm-immutable
*/
final class StatementError extends AbstractException
{
/** @param resource|null $statement */
public static function new($statement = null): self
{
if ($statement !== null) {
$message = db2_stmt_errormsg($statement);
$sqlState = db2_stmt_error($statement);
} else {
$message = db2_stmt_errormsg();
$sqlState = db2_stmt_error();
}
return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_error;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/**
* @internal The result can be only instantiated by its driver connection or statement.
*
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
$row = @db2_fetch_array($this->statement);
if ($row === false && db2_stmt_error($this->statement) !== '02000') {
throw StatementError::new($this->statement);
}
return $row;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$row = @db2_fetch_assoc($this->statement);
if ($row === false && db2_stmt_error($this->statement) !== '02000') {
throw StatementError::new($this->statement);
}
return $row;
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return @db2_num_rows($this->statement);
}
public function columnCount(): int
{
$count = db2_num_fields($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
db2_free_result($this->statement);
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCopyStreamToStream;
use Doctrine\DBAL\Driver\IBMDB2\Exception\CannotCreateTemporaryFile;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use function assert;
use function db2_bind_param;
use function db2_execute;
use function error_get_last;
use function fclose;
use function func_num_args;
use function is_int;
use function is_resource;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function tmpfile;
use const DB2_BINARY;
use const DB2_CHAR;
use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
final class Statement implements StatementInterface
{
/** @var resource */
private $stmt;
/** @var mixed[] */
private array $parameters = [];
/**
* Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
* and the temporary file handle bound to the underlying statement
*
* @var array<int,string|resource|null>
*/
private array $lobs = [];
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $stmt
*/
public function __construct($stmt)
{
$this->stmt = $stmt;
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
return $this->bindParam($param, $value, $type);
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
switch ($type) {
case ParameterType::INTEGER:
$this->bind($param, $variable, DB2_PARAM_IN, DB2_LONG);
break;
case ParameterType::LARGE_OBJECT:
$this->lobs[$param] = &$variable;
break;
default:
$this->bind($param, $variable, DB2_PARAM_IN, DB2_CHAR);
break;
}
return true;
}
/**
* @param int $position Parameter position
* @param mixed $variable
*
* @throws Exception
*/
private function bind($position, &$variable, int $parameterType, int $dataType): void
{
$this->parameters[$position] =& $variable;
if (! db2_bind_param($this->stmt, $position, '', $parameterType, $dataType)) {
throw StatementError::new($this->stmt);
}
}
/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
}
$handles = $this->bindLobs();
$result = @db2_execute($this->stmt, $params ?? $this->parameters);
foreach ($handles as $handle) {
fclose($handle);
}
$this->lobs = [];
if ($result === false) {
throw StatementError::new($this->stmt);
}
return new Result($this->stmt);
}
/**
* @return list<resource>
*
* @throws Exception
*/
private function bindLobs(): array
{
$handles = [];
foreach ($this->lobs as $param => $value) {
if (is_resource($value)) {
$handle = $handles[] = $this->createTemporaryFile();
$path = stream_get_meta_data($handle)['uri'];
$this->copyStreamToStream($value, $handle);
$this->bind($param, $path, DB2_PARAM_FILE, DB2_BINARY);
} else {
$this->bind($param, $value, DB2_PARAM_IN, DB2_CHAR);
}
unset($value);
}
return $handles;
}
/**
* @return resource
*
* @throws Exception
*/
private function createTemporaryFile()
{
$handle = @tmpfile();
if ($handle === false) {
throw CannotCreateTemporaryFile::new(error_get_last());
}
return $handle;
}
/**
* @param resource $source
* @param resource $target
*
* @throws Exception
*/
private function copyStreamToStream($source, $target): void
{
if (@stream_copy_to_stream($source, $target) === false) {
throw CannotCopyStreamToStream::new(error_get_last());
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver;
interface Middleware
{
public function wrap(Driver $driver): Driver;
}

View File

@@ -0,0 +1,113 @@
<?php
namespace Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use LogicException;
use function get_class;
use function method_exists;
use function sprintf;
abstract class AbstractConnectionMiddleware implements ServerInfoAwareConnection
{
private Connection $wrappedConnection;
public function __construct(Connection $wrappedConnection)
{
$this->wrappedConnection = $wrappedConnection;
}
public function prepare(string $sql): Statement
{
return $this->wrappedConnection->prepare($sql);
}
public function query(string $sql): Result
{
return $this->wrappedConnection->query($sql);
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
return $this->wrappedConnection->quote($value, $type);
}
public function exec(string $sql): int
{
return $this->wrappedConnection->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->wrappedConnection->lastInsertId($name);
}
/**
* {@inheritDoc}
*/
public function beginTransaction()
{
return $this->wrappedConnection->beginTransaction();
}
/**
* {@inheritDoc}
*/
public function commit()
{
return $this->wrappedConnection->commit();
}
/**
* {@inheritDoc}
*/
public function rollBack()
{
return $this->wrappedConnection->rollBack();
}
/**
* {@inheritDoc}
*/
public function getServerVersion()
{
if (! $this->wrappedConnection instanceof ServerInfoAwareConnection) {
throw new LogicException('The underlying connection is not a ServerInfoAwareConnection');
}
return $this->wrappedConnection->getServerVersion();
}
/** @return resource|object */
public function getNativeConnection()
{
if (! method_exists($this->wrappedConnection, 'getNativeConnection')) {
throw new LogicException(sprintf(
'The driver connection %s does not support accessing the native connection.',
get_class($this->wrappedConnection),
));
}
return $this->wrappedConnection->getNativeConnection();
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use Doctrine\Deprecations\Deprecation;
use SensitiveParameter;
abstract class AbstractDriverMiddleware implements VersionAwarePlatformDriver
{
private Driver $wrappedDriver;
public function __construct(Driver $wrappedDriver)
{
$this->wrappedDriver = $wrappedDriver;
}
/**
* {@inheritDoc}
*/
public function connect(
#[SensitiveParameter]
array $params
) {
return $this->wrappedDriver->connect($params);
}
/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return $this->wrappedDriver->getDatabasePlatform();
}
/**
* {@inheritDoc}
*
* @deprecated Use {@link AbstractPlatform::createSchemaManager()} instead.
*/
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5458',
'AbstractDriverMiddleware::getSchemaManager() is deprecated.'
. ' Use AbstractPlatform::createSchemaManager() instead.',
);
return $this->wrappedDriver->getSchemaManager($conn, $platform);
}
public function getExceptionConverter(): ExceptionConverter
{
return $this->wrappedDriver->getExceptionConverter();
}
/**
* {@inheritDoc}
*/
public function createDatabasePlatformForVersion($version)
{
if ($this->wrappedDriver instanceof VersionAwarePlatformDriver) {
return $this->wrappedDriver->createDatabasePlatformForVersion($version);
}
return $this->wrappedDriver->getDatabasePlatform();
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Result;
abstract class AbstractResultMiddleware implements Result
{
private Result $wrappedResult;
public function __construct(Result $result)
{
$this->wrappedResult = $result;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->wrappedResult->fetchNumeric();
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->wrappedResult->fetchAssociative();
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return $this->wrappedResult->fetchOne();
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->wrappedResult->fetchAllNumeric();
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->wrappedResult->fetchAllAssociative();
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->wrappedResult->fetchFirstColumn();
}
public function rowCount(): int
{
return $this->wrappedResult->rowCount();
}
public function columnCount(): int
{
return $this->wrappedResult->columnCount();
}
public function free(): void
{
$this->wrappedResult->free();
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use function func_num_args;
abstract class AbstractStatementMiddleware implements Statement
{
private Statement $wrappedStatement;
public function __construct(Statement $wrappedStatement)
{
$this->wrappedStatement = $wrappedStatement;
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
return $this->wrappedStatement->bindValue($param, $value, $type);
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
return $this->wrappedStatement->bindParam($param, $variable, $type, $length);
}
/**
* {@inheritDoc}
*/
public function execute($params = null): Result
{
return $this->wrappedStatement->execute($params);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use mysqli;
use mysqli_sql_exception;
final class Connection implements ServerInfoAwareConnection
{
/**
* Name of the option to set connection flags
*/
public const OPTION_FLAGS = 'flags';
private mysqli $connection;
/** @internal The connection can be only instantiated by its driver. */
public function __construct(mysqli $connection)
{
$this->connection = $connection;
}
/**
* Retrieves mysqli native resource handle.
*
* Could be used if part of your application is not using DBAL.
*
* @deprecated Call {@see getNativeConnection()} instead.
*/
public function getWrappedResourceHandle(): mysqli
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5037',
'%s is deprecated, call getNativeConnection() instead.',
__METHOD__,
);
return $this->getNativeConnection();
}
public function getServerVersion(): string
{
return $this->connection->get_server_info();
}
public function prepare(string $sql): DriverStatement
{
try {
$stmt = $this->connection->prepare($sql);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($stmt === false) {
throw ConnectionError::new($this->connection);
}
return new Statement($stmt);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
return "'" . $this->connection->escape_string($value) . "'";
}
public function exec(string $sql): int
{
try {
$result = $this->connection->query($sql);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($result === false) {
throw ConnectionError::new($this->connection);
}
return $this->connection->affected_rows;
}
/**
* {@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->insert_id;
}
public function beginTransaction(): bool
{
$this->connection->begin_transaction();
return true;
}
public function commit(): bool
{
try {
return $this->connection->commit();
} catch (mysqli_sql_exception $e) {
return false;
}
}
public function rollBack(): bool
{
try {
return $this->connection->rollback();
} catch (mysqli_sql_exception $e) {
return false;
}
}
public function getNativeConnection(): mysqli
{
return $this->connection;
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Options;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure;
use Generator;
use mysqli;
use mysqli_sql_exception;
use SensitiveParameter;
final class Driver extends AbstractMySQLDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
if (! empty($params['persistent'])) {
if (! isset($params['host'])) {
throw HostRequired::forPersistentConnection();
}
$host = 'p:' . $params['host'];
} else {
$host = $params['host'] ?? null;
}
$connection = new mysqli();
foreach ($this->compilePreInitializers($params) as $initializer) {
$initializer->initialize($connection);
}
try {
$success = @$connection->real_connect(
$host,
$params['user'] ?? null,
$params['password'] ?? null,
$params['dbname'] ?? null,
$params['port'] ?? null,
$params['unix_socket'] ?? null,
$params['driverOptions'][Connection::OPTION_FLAGS] ?? 0,
);
} catch (mysqli_sql_exception $e) {
throw ConnectionFailed::upcast($e);
}
if (! $success) {
throw ConnectionFailed::new($connection);
}
foreach ($this->compilePostInitializers($params) as $initializer) {
$initializer->initialize($connection);
}
return new Connection($connection);
}
/**
* @param array<string, mixed> $params
*
* @return Generator<int, Initializer>
*/
private function compilePreInitializers(
#[SensitiveParameter]
array $params
): Generator {
unset($params['driverOptions'][Connection::OPTION_FLAGS]);
if (isset($params['driverOptions']) && $params['driverOptions'] !== []) {
yield new Options($params['driverOptions']);
}
if (
! isset($params['ssl_key']) &&
! isset($params['ssl_cert']) &&
! isset($params['ssl_ca']) &&
! isset($params['ssl_capath']) &&
! isset($params['ssl_cipher'])
) {
return;
}
yield new Secure(
$params['ssl_key'] ?? '',
$params['ssl_cert'] ?? '',
$params['ssl_ca'] ?? '',
$params['ssl_capath'] ?? '',
$params['ssl_cipher'] ?? '',
);
}
/**
* @param array<string, mixed> $params
*
* @return Generator<int, Initializer>
*/
private function compilePostInitializers(
#[SensitiveParameter]
array $params
): Generator {
if (! isset($params['charset'])) {
return;
}
yield new Charset($params['charset']);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionError extends AbstractException
{
public static function new(mysqli $connection): self
{
return new self($connection->error, $connection->sqlstate, $connection->errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
use function assert;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends AbstractException
{
public static function new(mysqli $connection): self
{
$error = $connection->connect_error;
assert($error !== null);
return new self($error, 'HY000', $connection->connect_errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class FailedReadingStreamOffset extends AbstractException
{
public static function new(int $parameter): self
{
return new self(sprintf('Failed reading the stream resource for parameter #%d.', $parameter));
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class HostRequired extends AbstractException
{
public static function forPersistentConnection(): self
{
return new self('The "host" parameter is required for a persistent connection');
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidCharset extends AbstractException
{
public static function fromCharset(mysqli $connection, string $charset): self
{
return new self(
sprintf('Failed to set charset "%s": %s', $charset, $connection->error),
$connection->sqlstate,
$connection->errno,
);
}
public static function upcast(mysqli_sql_exception $exception, string $charset): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self(
sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()),
$p->getValue($exception),
(int) $exception->getCode(),
$exception,
);
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidOption extends AbstractException
{
/** @param mixed $value */
public static function fromOption(int $option, $value): self
{
return new self(
sprintf('Failed to set option %d with value "%s"', $option, $value),
);
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class NonStreamResourceUsedAsLargeObject extends AbstractException
{
public static function new(int $parameter): self
{
return new self(
sprintf('The resource passed as a LARGE_OBJECT parameter #%d must be of type "stream"', $parameter),
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli_sql_exception;
use mysqli_stmt;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class StatementError extends AbstractException
{
public static function new(mysqli_stmt $statement): self
{
return new self($statement->error, $statement->sqlstate, $statement->errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use mysqli;
interface Initializer
{
/** @throws Exception */
public function initialize(mysqli $connection): void;
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use mysqli_sql_exception;
final class Charset implements Initializer
{
private string $charset;
public function __construct(string $charset)
{
$this->charset = $charset;
}
public function initialize(mysqli $connection): void
{
try {
$success = $connection->set_charset($this->charset);
} catch (mysqli_sql_exception $e) {
throw InvalidCharset::upcast($e, $this->charset);
}
if ($success) {
return;
}
throw InvalidCharset::fromCharset($connection, $this->charset);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use function mysqli_options;
final class Options implements Initializer
{
/** @var array<int,mixed> */
private array $options;
/** @param array<int,mixed> $options */
public function __construct(array $options)
{
$this->options = $options;
}
public function initialize(mysqli $connection): void
{
foreach ($this->options as $option => $value) {
if (! mysqli_options($connection, $option, $value)) {
throw InvalidOption::fromOption($option, $value);
}
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use SensitiveParameter;
final class Secure implements Initializer
{
private string $key;
private string $cert;
private string $ca;
private string $capath;
private string $cipher;
public function __construct(
#[SensitiveParameter]
string $key,
string $cert,
string $ca,
string $capath,
string $cipher
) {
$this->key = $key;
$this->cert = $cert;
$this->ca = $ca;
$this->capath = $capath;
$this->cipher = $cipher;
}
public function initialize(mysqli $connection): void
{
$connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher);
}
}

View File

@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use mysqli_sql_exception;
use mysqli_stmt;
use function array_column;
use function array_combine;
use function array_fill;
use function count;
final class Result implements ResultInterface
{
private mysqli_stmt $statement;
/**
* Whether the statement result has columns. The property should be used only after the result metadata
* has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined.
*/
private bool $hasColumns = false;
/**
* Mapping of statement result column indexes to their names. The property should be used only
* if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined.
*
* @var array<int,string>
*/
private array $columnNames = [];
/** @var mixed[] */
private array $boundValues = [];
/**
* @internal The result can be only instantiated by its driver connection or statement.
*
* @throws Exception
*/
public function __construct(mysqli_stmt $statement)
{
$this->statement = $statement;
$meta = $statement->result_metadata();
if ($meta === false) {
return;
}
$this->hasColumns = true;
$this->columnNames = array_column($meta->fetch_fields(), 'name');
$meta->free();
// Store result of every execution which has it. Otherwise it will be impossible
// to execute a new statement in case if the previous one has non-fetched rows
// @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
$this->statement->store_result();
// Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
// it will have to allocate as much memory as it may be needed for the given column type
// (e.g. for a LONGBLOB column it's 4 gigabytes)
// @link https://bugs.php.net/bug.php?id=51386#1270673122
//
// Make sure that the values are bound after each execution. Otherwise, if free() has been
// previously called on the result, the values are unbound making the statement unusable.
//
// It's also important that row values are bound after _each_ call to store_result(). Otherwise,
// if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
// to the length of the ones fetched during the previous execution.
$this->boundValues = array_fill(0, count($this->columnNames), null);
// The following is necessary as PHP cannot handle references to properties properly
$refs = &$this->boundValues;
if (! $this->statement->bind_result(...$refs)) {
throw StatementError::new($this->statement);
}
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
try {
$ret = $this->statement->fetch();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if ($ret === false) {
throw StatementError::new($this->statement);
}
if ($ret === null) {
return false;
}
$values = [];
foreach ($this->boundValues as $v) {
$values[] = $v;
}
return $values;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$values = $this->fetchNumeric();
if ($values === false) {
return false;
}
return array_combine($this->columnNames, $values);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->hasColumns) {
return $this->statement->num_rows;
}
return $this->statement->affected_rows;
}
public function columnCount(): int
{
return $this->statement->field_count;
}
public function free(): void
{
$this->statement->free_result();
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Exception\UnknownParameterType;
use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use mysqli_sql_exception;
use mysqli_stmt;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function func_num_args;
use function get_resource_type;
use function is_int;
use function is_resource;
use function str_repeat;
final class Statement implements StatementInterface
{
/** @var string[] */
private static array $paramTypeMap = [
ParameterType::ASCII => 's',
ParameterType::STRING => 's',
ParameterType::BINARY => 's',
ParameterType::BOOLEAN => 'i',
ParameterType::NULL => 's',
ParameterType::INTEGER => 'i',
ParameterType::LARGE_OBJECT => 'b',
];
private mysqli_stmt $stmt;
/** @var mixed[] */
private array $boundValues;
private string $types;
/**
* Contains ref values for bindValue().
*
* @var mixed[]
*/
private array $values = [];
/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(mysqli_stmt $stmt)
{
$this->stmt = $stmt;
$paramCount = $this->stmt->param_count;
$this->types = str_repeat('s', $paramCount);
$this->boundValues = array_fill(1, $paramCount, null);
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (! isset(self::$paramTypeMap[$type])) {
throw UnknownParameterType::new($type);
}
$this->boundValues[$param] =& $variable;
$this->types[$param - 1] = self::$paramTypeMap[$type];
return true;
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (! isset(self::$paramTypeMap[$type])) {
throw UnknownParameterType::new($type);
}
$this->values[$param] = $value;
$this->boundValues[$param] =& $this->values[$param];
$this->types[$param - 1] = self::$paramTypeMap[$type];
return true;
}
/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
}
if ($params !== null && count($params) > 0) {
if (! $this->bindUntypedValues($params)) {
throw StatementError::new($this->stmt);
}
} elseif (count($this->boundValues) > 0) {
$this->bindTypedParameters();
}
try {
$result = $this->stmt->execute();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if (! $result) {
throw StatementError::new($this->stmt);
}
return new Result($this->stmt);
}
/**
* Binds parameters with known types previously bound to the statement
*
* @throws Exception
*/
private function bindTypedParameters(): void
{
$streams = $values = [];
$types = $this->types;
foreach ($this->boundValues as $parameter => $value) {
assert(is_int($parameter));
if (! isset($types[$parameter - 1])) {
$types[$parameter - 1] = self::$paramTypeMap[ParameterType::STRING];
}
if ($types[$parameter - 1] === self::$paramTypeMap[ParameterType::LARGE_OBJECT]) {
if (is_resource($value)) {
if (get_resource_type($value) !== 'stream') {
throw NonStreamResourceUsedAsLargeObject::new($parameter);
}
$streams[$parameter] = $value;
$values[$parameter] = null;
continue;
}
$types[$parameter - 1] = self::$paramTypeMap[ParameterType::STRING];
}
$values[$parameter] = $value;
}
if (! $this->stmt->bind_param($types, ...$values)) {
throw StatementError::new($this->stmt);
}
$this->sendLongData($streams);
}
/**
* Handle $this->_longData after regular query parameters have been bound
*
* @param array<int, resource> $streams
*
* @throws Exception
*/
private function sendLongData(array $streams): void
{
foreach ($streams as $paramNr => $stream) {
while (! feof($stream)) {
$chunk = fread($stream, 8192);
if ($chunk === false) {
throw FailedReadingStreamOffset::new($paramNr);
}
if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) {
throw StatementError::new($this->stmt);
}
}
}
}
/**
* Binds a array of values to bound parameters.
*
* @param mixed[] $values
*/
private function bindUntypedValues(array $values): bool
{
return $this->stmt->bind_param(str_repeat('s', count($values)), ...$values);
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\OCI8\Exception\SequenceDoesNotExist;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\SQL\Parser;
use Doctrine\Deprecations\Deprecation;
use function addcslashes;
use function assert;
use function is_float;
use function is_int;
use function is_resource;
use function oci_commit;
use function oci_parse;
use function oci_rollback;
use function oci_server_version;
use function preg_match;
use function str_replace;
final class Connection implements ServerInfoAwareConnection
{
/** @var resource */
private $connection;
private Parser $parser;
private ExecutionMode $executionMode;
/**
* @internal The connection can be only instantiated by its driver.
*
* @param resource $connection
*/
public function __construct($connection)
{
$this->connection = $connection;
$this->parser = new Parser(false);
$this->executionMode = new ExecutionMode();
}
public function getServerVersion(): string
{
$version = oci_server_version($this->connection);
if ($version === false) {
throw Error::new($this->connection);
}
$result = preg_match('/\s+(\d+\.\d+\.\d+\.\d+\.\d+)\s+/', $version, $matches);
assert($result === 1);
return $matches[1];
}
/** @throws Parser\Exception */
public function prepare(string $sql): DriverStatement
{
$visitor = new ConvertPositionalToNamedPlaceholders();
$this->parser->parse($sql, $visitor);
$statement = oci_parse($this->connection, $visitor->getSQL());
assert(is_resource($statement));
return new Statement($this->connection, $statement, $visitor->getParameterMap(), $this->executionMode);
}
/**
* @throws Exception
* @throws Parser\Exception
*/
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
if (is_int($value) || is_float($value)) {
return $value;
}
$value = str_replace("'", "''", $value);
return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
}
/**
* @throws Exception
* @throws Parser\Exception
*/
public function exec(string $sql): int
{
return $this->prepare($sql)->execute()->rowCount();
}
/**
* {@inheritDoc}
*
* @param string|null $name
*
* @return int|false
*
* @throws Parser\Exception
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return false;
}
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/4687',
'The usage of Connection::lastInsertId() with a sequence name is deprecated.',
);
$result = $this->query('SELECT ' . $name . '.CURRVAL FROM DUAL')->fetchOne();
if ($result === false) {
throw SequenceDoesNotExist::new();
}
return (int) $result;
}
public function beginTransaction(): bool
{
$this->executionMode->disableAutoCommit();
return true;
}
public function commit(): bool
{
if (! oci_commit($this->connection)) {
throw Error::new($this->connection);
}
$this->executionMode->enableAutoCommit();
return true;
}
public function rollBack(): bool
{
if (! oci_rollback($this->connection)) {
throw Error::new($this->connection);
}
$this->executionMode->enableAutoCommit();
return true;
}
/** @return resource */
public function getNativeConnection()
{
return $this->connection;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\SQL\Parser\Visitor;
use function count;
use function implode;
/**
* Converts positional (?) into named placeholders (:param<num>).
*
* Oracle does not support positional parameters, hence this method converts all
* positional parameters into artificially named parameters.
*
* @internal This class is not covered by the backward compatibility promise
*/
final class ConvertPositionalToNamedPlaceholders implements Visitor
{
/** @var list<string> */
private array $buffer = [];
/** @var array<int,string> */
private array $parameterMap = [];
public function acceptOther(string $sql): void
{
$this->buffer[] = $sql;
}
public function acceptPositionalParameter(string $sql): void
{
$position = count($this->parameterMap) + 1;
$param = ':param' . $position;
$this->parameterMap[$position] = $param;
$this->buffer[] = $param;
}
public function acceptNamedParameter(string $sql): void
{
$this->buffer[] = $sql;
}
public function getSQL(): string
{
return implode('', $this->buffer);
}
/** @return array<int,string> */
public function getParameterMap(): array
{
return $this->parameterMap;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\AbstractOracleDriver;
use Doctrine\DBAL\Driver\OCI8\Exception\ConnectionFailed;
use SensitiveParameter;
use function oci_connect;
use function oci_pconnect;
use const OCI_NO_AUTO_COMMIT;
/**
* A Doctrine DBAL driver for the Oracle OCI8 PHP extensions.
*/
final class Driver extends AbstractOracleDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$username = $params['user'] ?? '';
$password = $params['password'] ?? '';
$charset = $params['charset'] ?? '';
$sessionMode = $params['sessionMode'] ?? OCI_NO_AUTO_COMMIT;
$connectionString = $this->getEasyConnectString($params);
if (! empty($params['persistent'])) {
$connection = @oci_pconnect($username, $password, $connectionString, $charset, $sessionMode);
} else {
$connection = @oci_connect($username, $password, $connectionString, $charset, $sessionMode);
}
if ($connection === false) {
throw ConnectionFailed::new();
}
return new Connection($connection);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function assert;
use function oci_error;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends AbstractException
{
public static function new(): self
{
$error = oci_error();
assert($error !== false);
return new self($error['message'], null, $error['code']);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function assert;
use function oci_error;
/**
* @internal
*
* @psalm-immutable
*/
final class Error extends AbstractException
{
/** @param resource $resource */
public static function new($resource): self
{
$error = oci_error($resource);
assert($error !== false);
return new self($error['message'], null, $error['code']);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class NonTerminatedStringLiteral extends AbstractException
{
public static function new(int $offset): self
{
return new self(
sprintf(
'The statement contains non-terminated string literal starting at offset %d.',
$offset,
),
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class SequenceDoesNotExist extends AbstractException
{
public static function new(): self
{
return new self('lastInsertId failed: Query was executed but no result was returned.');
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class UnknownParameterIndex extends AbstractException
{
public static function new(int $index): self
{
return new self(
sprintf('Could not find variable mapping with index %d, in the SQL statement', $index),
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8;
/**
* Encapsulates the execution mode that is shared between the connection and its statements.
*
* @internal This class is not covered by the backward compatibility promise
*/
final class ExecutionMode
{
private bool $isAutoCommitEnabled = true;
public function enableAutoCommit(): void
{
$this->isAutoCommitEnabled = true;
}
public function disableAutoCommit(): void
{
$this->isAutoCommitEnabled = false;
}
public function isAutoCommitEnabled(): bool
{
return $this->isAutoCommitEnabled;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8\Middleware;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
use SensitiveParameter;
class InitializeSession implements Middleware
{
public function wrap(Driver $driver): Driver
{
return new class ($driver) extends AbstractDriverMiddleware {
/**
* {@inheritDoc}
*/
public function connect(
#[SensitiveParameter]
array $params
): Connection {
$connection = parent::connect($params);
$connection->exec(
'ALTER SESSION SET'
. " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
. " NLS_TIME_FORMAT = 'HH24:MI:SS'"
. " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
. " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
. " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'"
. " NLS_NUMERIC_CHARACTERS = '.,'",
);
return $connection;
}
};
}
}

View File

@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function oci_cancel;
use function oci_error;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_num_fields;
use function oci_num_rows;
use const OCI_ASSOC;
use const OCI_FETCHSTATEMENT_BY_COLUMN;
use const OCI_FETCHSTATEMENT_BY_ROW;
use const OCI_NUM;
use const OCI_RETURN_LOBS;
use const OCI_RETURN_NULLS;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/**
* @internal The result can be only instantiated by its driver connection or statement.
*
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->fetch(OCI_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch(OCI_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
}
public function rowCount(): int
{
$count = oci_num_rows($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function columnCount(): int
{
$count = oci_num_fields($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
oci_cancel($this->statement);
}
/**
* @return mixed|false
*
* @throws Exception
*/
private function fetch(int $mode)
{
$result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
if ($result === false && oci_error($this->statement) !== false) {
throw Error::new($this->statement);
}
return $result;
}
/** @return array<mixed> */
private function fetchAll(int $mode, int $fetchStructure): array
{
oci_fetch_all(
$this->statement,
$result,
0,
-1,
$mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS,
);
return $result;
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\OCI8\Exception\UnknownParameterIndex;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use function func_num_args;
use function is_int;
use function oci_bind_by_name;
use function oci_execute;
use function oci_new_descriptor;
use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_COMMIT_ON_SUCCESS;
use const OCI_D_LOB;
use const OCI_NO_AUTO_COMMIT;
use const OCI_TEMP_BLOB;
use const SQLT_CHR;
final class Statement implements StatementInterface
{
/** @var resource */
private $connection;
/** @var resource */
private $statement;
/** @var array<int,string> */
private array $parameterMap;
private ExecutionMode $executionMode;
/**
* @internal The statement can be only instantiated by its driver connection.
*
* @param resource $connection
* @param resource $statement
* @param array<int,string> $parameterMap
*/
public function __construct($connection, $statement, array $parameterMap, ExecutionMode $executionMode)
{
$this->connection = $connection;
$this->statement = $statement;
$this->parameterMap = $parameterMap;
$this->executionMode = $executionMode;
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
return $this->bindParam($param, $value, $type);
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (is_int($param)) {
if (! isset($this->parameterMap[$param])) {
throw UnknownParameterIndex::new($param);
}
$param = $this->parameterMap[$param];
}
if ($type === ParameterType::LARGE_OBJECT) {
if ($variable !== null) {
$lob = oci_new_descriptor($this->connection, OCI_D_LOB);
$lob->writeTemporary($variable, OCI_TEMP_BLOB);
$variable =& $lob;
} else {
$type = ParameterType::STRING;
}
}
return oci_bind_by_name(
$this->statement,
$param,
$variable,
$length ?? -1,
$this->convertParameterType($type),
);
}
/**
* Converts DBAL parameter type to oci8 parameter type
*/
private function convertParameterType(int $type): int
{
switch ($type) {
case ParameterType::BINARY:
return OCI_B_BIN;
case ParameterType::LARGE_OBJECT:
return OCI_B_BLOB;
default:
return SQLT_CHR;
}
}
/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
foreach ($params as $key => $val) {
if (is_int($key)) {
$this->bindValue($key + 1, $val, ParameterType::STRING);
} else {
$this->bindValue($key, $val, ParameterType::STRING);
}
}
}
if ($this->executionMode->isAutoCommitEnabled()) {
$mode = OCI_COMMIT_ON_SUCCESS;
} else {
$mode = OCI_NO_AUTO_COMMIT;
}
$ret = @oci_execute($this->statement, $mode);
if (! $ret) {
throw Error::new($this->statement);
}
return new Result($this->statement);
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDO\PDOException as DriverPDOException;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;
use PDOException;
use PDOStatement;
use function assert;
final class Connection implements ServerInfoAwareConnection
{
private PDO $connection;
/** @internal The connection can be only instantiated by its driver. */
public function __construct(PDO $connection)
{
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection = $connection;
}
public function exec(string $sql): int
{
try {
$result = $this->connection->exec($sql);
assert($result !== false);
return $result;
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritDoc}
*/
public function getServerVersion()
{
return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
}
/**
* {@inheritDoc}
*
* @return Statement
*/
public function prepare(string $sql): StatementInterface
{
try {
$stmt = $this->connection->prepare($sql);
assert($stmt instanceof PDOStatement);
return new Statement($stmt);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function query(string $sql): ResultInterface
{
try {
$stmt = $this->connection->query($sql);
assert($stmt instanceof PDOStatement);
return new Result($stmt);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
return $this->connection->quote($value, ParameterTypeMap::convertParamType($type));
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
try {
if ($name === null) {
return $this->connection->lastInsertId();
}
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);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function beginTransaction(): bool
{
try {
return $this->connection->beginTransaction();
} catch (PDOException $exception) {
throw DriverPDOException::new($exception);
}
}
public function commit(): bool
{
try {
return $this->connection->commit();
} catch (PDOException $exception) {
throw DriverPDOException::new($exception);
}
}
public function rollBack(): bool
{
try {
return $this->connection->rollBack();
} catch (PDOException $exception) {
throw DriverPDOException::new($exception);
}
}
public function getNativeConnection(): PDO
{
return $this->connection;
}
/** @deprecated Call {@see getNativeConnection()} instead. */
public function getWrappedConnection(): PDO
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5037',
'%s is deprecated, call getNativeConnection() instead.',
__METHOD__,
);
return $this->getNativeConnection();
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\AbstractException;
use PDOException;
/**
* @internal
*
* @psalm-immutable
*/
final class Exception extends AbstractException
{
public static function new(PDOException $exception): self
{
if ($exception->errorInfo !== null) {
[$sqlState, $code] = $exception->errorInfo;
$code ??= 0;
} else {
$code = $exception->getCode();
$sqlState = null;
}
return new self($exception->getMessage(), $sqlState, $code, $exception);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\MySQL;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use PDO;
use PDOException;
use SensitiveParameter;
final class Driver extends AbstractMySQLDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$driverOptions = $params['driverOptions'] ?? [];
if (! empty($params['persistent'])) {
$driverOptions[PDO::ATTR_PERSISTENT] = true;
}
$safeParams = $params;
unset($safeParams['password'], $safeParams['url']);
try {
$pdo = new PDO(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions,
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Connection($pdo);
}
/**
* Constructs the MySQL PDO DSN.
*
* @param mixed[] $params
*/
private function constructPdoDsn(array $params): string
{
$dsn = 'mysql:';
if (isset($params['host']) && $params['host'] !== '') {
$dsn .= 'host=' . $params['host'] . ';';
}
if (isset($params['port'])) {
$dsn .= 'port=' . $params['port'] . ';';
}
if (isset($params['dbname'])) {
$dsn .= 'dbname=' . $params['dbname'] . ';';
}
if (isset($params['unix_socket'])) {
$dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
}
if (isset($params['charset'])) {
$dsn .= 'charset=' . $params['charset'] . ';';
}
return $dsn;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\OCI;
use Doctrine\DBAL\Driver\AbstractOracleDriver;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use PDO;
use PDOException;
use SensitiveParameter;
final class Driver extends AbstractOracleDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$driverOptions = $params['driverOptions'] ?? [];
if (! empty($params['persistent'])) {
$driverOptions[PDO::ATTR_PERSISTENT] = true;
}
$safeParams = $params;
unset($safeParams['password'], $safeParams['url']);
try {
$pdo = new PDO(
$this->constructPdoDsn($params),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions,
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Connection($pdo);
}
/**
* Constructs the Oracle PDO DSN.
*
* @param mixed[] $params
*/
private function constructPdoDsn(array $params): string
{
$dsn = 'oci:dbname=' . $this->getEasyConnectString($params);
if (isset($params['charset'])) {
$dsn .= ';charset=' . $params['charset'];
}
return $dsn;
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\Exception as DriverException;
/**
* @internal
*
* @psalm-immutable
*/
final class PDOException extends \PDOException implements DriverException
{
private ?string $sqlState = null;
public static function new(\PDOException $previous): self
{
$exception = new self($previous->message, 0, $previous);
$exception->errorInfo = $previous->errorInfo;
$exception->code = $previous->code;
$exception->sqlState = $previous->errorInfo[0] ?? null;
return $exception;
}
public function getSQLState(): ?string
{
return $this->sqlState;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\Exception\UnknownParameterType;
use Doctrine\DBAL\ParameterType;
use PDO;
/** @internal */
final class ParameterTypeMap
{
private const PARAM_TYPE_MAP = [
ParameterType::NULL => PDO::PARAM_NULL,
ParameterType::INTEGER => PDO::PARAM_INT,
ParameterType::STRING => PDO::PARAM_STR,
ParameterType::ASCII => PDO::PARAM_STR,
ParameterType::BINARY => PDO::PARAM_LOB,
ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
ParameterType::BOOLEAN => PDO::PARAM_BOOL,
];
/**
* Converts DBAL parameter type to PDO parameter type
*
* @psalm-return PDO::PARAM_*
*
* @throws UnknownParameterType
*/
public static function convertParamType(int $type): int
{
if (! isset(self::PARAM_TYPE_MAP[$type])) {
throw UnknownParameterType::new($type);
}
return self::PARAM_TYPE_MAP[$type];
}
private function __construct()
{
}
private function __clone()
{
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\PgSQL;
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\Deprecations\Deprecation;
use PDO;
use PDOException;
use SensitiveParameter;
final class Driver extends AbstractPostgreSQLDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$driverOptions = $params['driverOptions'] ?? [];
if (! empty($params['persistent'])) {
$driverOptions[PDO::ATTR_PERSISTENT] = true;
}
$safeParams = $params;
unset($safeParams['password'], $safeParams['url']);
try {
$pdo = new PDO(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions,
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
if (
! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES])
|| $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] === true
) {
$pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true);
}
$connection = new Connection($pdo);
/* defining client_encoding via SET NAMES to avoid inconsistent DSN support
* - passing client_encoding via the 'options' param breaks pgbouncer support
*/
if (isset($params['charset'])) {
$connection->exec('SET NAMES \'' . $params['charset'] . '\'');
}
return $connection;
}
/**
* Constructs the Postgres PDO DSN.
*
* @param array<string, mixed> $params
*/
private function constructPdoDsn(array $params): string
{
$dsn = 'pgsql:';
if (isset($params['host']) && $params['host'] !== '') {
$dsn .= 'host=' . $params['host'] . ';';
}
if (isset($params['port']) && $params['port'] !== '') {
$dsn .= 'port=' . $params['port'] . ';';
}
if (isset($params['dbname'])) {
$dsn .= 'dbname=' . $params['dbname'] . ';';
} elseif (isset($params['default_dbname'])) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5705',
'The "default_dbname" connection parameter is deprecated. Use "dbname" instead.',
);
$dsn .= 'dbname=' . $params['default_dbname'] . ';';
} else {
if (isset($params['user']) && $params['user'] !== 'postgres') {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5705',
'Relying on the DBAL connecting to the "postgres" database by default is deprecated.'
. ' Unless you want to have the server determine the default database for the connection,'
. ' specify the database name explicitly.',
);
}
// Used for temporary connections to allow operations like dropping the database currently connected to.
$dsn .= 'dbname=postgres;';
}
if (isset($params['sslmode'])) {
$dsn .= 'sslmode=' . $params['sslmode'] . ';';
}
if (isset($params['sslrootcert'])) {
$dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';';
}
if (isset($params['sslcert'])) {
$dsn .= 'sslcert=' . $params['sslcert'] . ';';
}
if (isset($params['sslkey'])) {
$dsn .= 'sslkey=' . $params['sslkey'] . ';';
}
if (isset($params['sslcrl'])) {
$dsn .= 'sslcrl=' . $params['sslcrl'] . ';';
}
if (isset($params['application_name'])) {
$dsn .= 'application_name=' . $params['application_name'] . ';';
}
return $dsn;
}
}

View File

@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use PDO;
use PDOException;
use PDOStatement;
final class Result implements ResultInterface
{
private PDOStatement $statement;
/** @internal The result can be only instantiated by its driver connection or statement. */
public function __construct(PDOStatement $statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->fetch(PDO::FETCH_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch(PDO::FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return $this->fetch(PDO::FETCH_COLUMN);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(PDO::FETCH_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(PDO::FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(PDO::FETCH_COLUMN);
}
public function rowCount(): int
{
try {
return $this->statement->rowCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function columnCount(): int
{
try {
return $this->statement->columnCount();
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
public function free(): void
{
$this->statement->closeCursor();
}
/**
* @psalm-param PDO::FETCH_* $mode
*
* @return mixed
*
* @throws Exception
*/
private function fetch(int $mode)
{
try {
return $this->statement->fetch($mode);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* @psalm-param PDO::FETCH_* $mode
*
* @return list<mixed>
*
* @throws Exception
*/
private function fetchAll(int $mode): array
{
try {
return $this->statement->fetchAll($mode);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\Deprecations\Deprecation;
use PDO;
final class Connection extends AbstractConnectionMiddleware
{
private PDOConnection $connection;
public function __construct(PDOConnection $connection)
{
parent::__construct($connection);
$this->connection = $connection;
}
public function prepare(string $sql): StatementInterface
{
return new Statement(
$this->connection->prepare($sql),
);
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return parent::lastInsertId($name);
}
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/4687',
'The usage of Connection::lastInsertId() with a sequence name is deprecated.',
);
$statement = $this->prepare(
'SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?',
);
$statement->bindValue(1, $name);
return $statement->execute()
->fetchOne();
}
public function getNativeConnection(): PDO
{
return $this->connection->getNativeConnection();
}
/** @deprecated Call {@see getNativeConnection()} instead. */
public function getWrappedConnection(): PDO
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5037',
'%s is deprecated, call getNativeConnection() instead.',
__METHOD__,
);
return $this->connection->getWrappedConnection();
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\PDO\Exception as PDOException;
use PDO;
use SensitiveParameter;
use function is_int;
use function sprintf;
final class Driver extends AbstractSQLServerDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$driverOptions = $dsnOptions = [];
if (isset($params['driverOptions'])) {
foreach ($params['driverOptions'] as $option => $value) {
if (is_int($option)) {
$driverOptions[$option] = $value;
} else {
$dsnOptions[$option] = $value;
}
}
}
if (! empty($params['persistent'])) {
$driverOptions[PDO::ATTR_PERSISTENT] = true;
}
$safeParams = $params;
unset($safeParams['password'], $safeParams['url']);
try {
$pdo = new PDO(
$this->constructDsn($safeParams, $dsnOptions),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions,
);
} catch (\PDOException $exception) {
throw PDOException::new($exception);
}
return new Connection(new PDOConnection($pdo));
}
/**
* Constructs the Sqlsrv PDO DSN.
*
* @param mixed[] $params
* @param string[] $connectionOptions
*
* @throws Exception
*/
private function constructDsn(array $params, array $connectionOptions): string
{
$dsn = 'sqlsrv:server=';
if (isset($params['host'])) {
$dsn .= $params['host'];
if (isset($params['port'])) {
$dsn .= ',' . $params['port'];
}
} elseif (isset($params['port'])) {
throw PortWithoutHost::new();
}
if (isset($params['dbname'])) {
$connectionOptions['Database'] = $params['dbname'];
}
if (isset($params['MultipleActiveResultSets'])) {
$connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false';
}
return $dsn . $this->getConnectionOptionsDsn($connectionOptions);
}
/**
* Converts a connection options array to the DSN
*
* @param string[] $connectionOptions
*/
private function getConnectionOptionsDsn(array $connectionOptions): string
{
$connectionOptionsDsn = '';
foreach ($connectionOptions as $paramName => $paramValue) {
$connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue);
}
return $connectionOptionsDsn;
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLSrv;
use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
use Doctrine\DBAL\Driver\PDO\Statement as PDOStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDO;
use function func_num_args;
final class Statement extends AbstractStatementMiddleware
{
private PDOStatement $statement;
/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(PDOStatement $statement)
{
parent::__construct($statement);
$this->statement = $statement;
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*
* @param string|int $param
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions The usage of the argument is deprecated.
*/
public function bindParam(
$param,
&$variable,
$type = ParameterType::STRING,
$length = null,
$driverOptions = null
): bool {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (func_num_args() > 4) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/4533',
'The $driverOptions argument of Statement::bindParam() is deprecated.',
);
}
switch ($type) {
case ParameterType::LARGE_OBJECT:
case ParameterType::BINARY:
$driverOptions ??= PDO::SQLSRV_ENCODING_BINARY;
break;
case ParameterType::ASCII:
$type = ParameterType::STRING;
$length = 0;
$driverOptions = PDO::SQLSRV_ENCODING_SYSTEM;
break;
}
return $this->statement->bindParam($param, $variable, $type, $length ?? 0, $driverOptions);
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
return $this->bindParam($param, $value, $type);
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Doctrine\DBAL\Driver\PDO\SQLite;
use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver\API\SQLite\UserDefinedFunctions;
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\Deprecations\Deprecation;
use PDO;
use PDOException;
use SensitiveParameter;
use function array_intersect_key;
final class Driver extends AbstractSQLiteDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
$driverOptions = $params['driverOptions'] ?? [];
$userDefinedFunctions = [];
if (isset($driverOptions['userDefinedFunctions'])) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5742',
'The SQLite-specific driver option "userDefinedFunctions" is deprecated.'
. ' Register function directly on the native connection instead.',
);
$userDefinedFunctions = $driverOptions['userDefinedFunctions'];
unset($driverOptions['userDefinedFunctions']);
}
try {
$pdo = new PDO(
$this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions,
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
UserDefinedFunctions::register(
[$pdo, 'sqliteCreateFunction'],
$userDefinedFunctions,
);
return new Connection($pdo);
}
/**
* Constructs the Sqlite PDO DSN.
*
* @param array<string, mixed> $params
*/
private function constructPdoDsn(array $params): string
{
$dsn = 'sqlite:';
if (isset($params['path'])) {
$dsn .= $params['path'];
} elseif (isset($params['memory'])) {
$dsn .= ':memory:';
}
return $dsn;
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use PDOException;
use PDOStatement;
use function array_slice;
use function func_get_args;
use function func_num_args;
final class Statement implements StatementInterface
{
private PDOStatement $stmt;
/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(PDOStatement $stmt)
{
$this->stmt = $stmt;
}
/**
* {@inheritDoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
$pdoType = ParameterTypeMap::convertParamType($type);
try {
return $this->stmt->bindValue($param, $value, $pdoType);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritDoc}
*
* @deprecated Use {@see bindValue()} instead.
*
* @param mixed $param
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions The usage of the argument is deprecated.
*/
public function bindParam(
$param,
&$variable,
$type = ParameterType::STRING,
$length = null,
$driverOptions = null
): bool {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (func_num_args() > 4) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/4533',
'The $driverOptions argument of Statement::bindParam() is deprecated.',
);
}
$pdoType = ParameterTypeMap::convertParamType($type);
try {
return $this->stmt->bindParam(
$param,
$variable,
$pdoType,
$length ?? 0,
...array_slice(func_get_args(), 4),
);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
}
try {
$this->stmt->execute($params);
} catch (PDOException $exception) {
throw Exception::new($exception);
}
return new Result($this->stmt);
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace Doctrine\DBAL\Driver\PgSQL;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\SQL\Parser;
use Doctrine\Deprecations\Deprecation;
use PgSql\Connection as PgSqlConnection;
use TypeError;
use function assert;
use function get_class;
use function gettype;
use function is_object;
use function is_resource;
use function pg_close;
use function pg_escape_bytea;
use function pg_escape_literal;
use function pg_get_result;
use function pg_last_error;
use function pg_result_error;
use function pg_send_prepare;
use function pg_send_query;
use function pg_version;
use function sprintf;
use function uniqid;
final class Connection implements ServerInfoAwareConnection
{
/** @var PgSqlConnection|resource */
private $connection;
private Parser $parser;
/** @param PgSqlConnection|resource $connection */
public function __construct($connection)
{
if (! is_resource($connection) && ! $connection instanceof PgSqlConnection) {
throw new TypeError(sprintf(
'Expected connection to be a resource or an instance of %s, got %s.',
PgSqlConnection::class,
is_object($connection) ? get_class($connection) : gettype($connection),
));
}
$this->connection = $connection;
$this->parser = new Parser(false);
}
public function __destruct()
{
if (! isset($this->connection)) {
return;
}
@pg_close($this->connection);
}
public function prepare(string $sql): Statement
{
$visitor = new ConvertParameters();
$this->parser->parse($sql, $visitor);
$statementName = uniqid('dbal', true);
if (@pg_send_prepare($this->connection, $statementName, $visitor->getSQL()) !== true) {
throw new Exception(pg_last_error($this->connection));
}
$result = @pg_get_result($this->connection);
assert($result !== false);
if ((bool) pg_result_error($result)) {
throw Exception::fromResult($result);
}
return new Statement($this->connection, $statementName, $visitor->getParameterMap());
}
public function query(string $sql): Result
{
if (@pg_send_query($this->connection, $sql) !== true) {
throw new Exception(pg_last_error($this->connection));
}
$result = @pg_get_result($this->connection);
assert($result !== false);
if ((bool) pg_result_error($result)) {
throw Exception::fromResult($result);
}
return new Result($result);
}
/** {@inheritDoc} */
public function quote($value, $type = ParameterType::STRING)
{
if ($type === ParameterType::BINARY || $type === ParameterType::LARGE_OBJECT) {
return sprintf("'%s'", pg_escape_bytea($this->connection, $value));
}
return pg_escape_literal($this->connection, $value);
}
public function exec(string $sql): int
{
return $this->query($sql)->rowCount();
}
/** {@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->query(sprintf('SELECT CURRVAL(%s)', $this->quote($name)))->fetchOne();
}
return $this->query('SELECT LASTVAL()')->fetchOne();
}
/** @return true */
public function beginTransaction(): bool
{
$this->exec('BEGIN');
return true;
}
/** @return true */
public function commit(): bool
{
$this->exec('COMMIT');
return true;
}
/** @return true */
public function rollBack(): bool
{
$this->exec('ROLLBACK');
return true;
}
public function getServerVersion(): string
{
return (string) pg_version($this->connection)['server'];
}
/** @return PgSqlConnection|resource */
public function getNativeConnection()
{
return $this->connection;
}
}

Some files were not shown because too many files have changed in this diff Show More