diff --git a/composer.json b/composer.json index af60d3f..488552d 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "test-integration": "phpunit --colors=always --testsuite \"integration test\"", "sa": "vendor/bin/phpstan analyse --memory-limit=256M", - "sa-generate-baseline": "vendor/bin/phpstan analyse --memory-limit=256M --generate-baseline", + "sa-gen-baseline": "vendor/bin/phpstan analyse --memory-limit=256M --generate-baseline", "sa-verbose": "vendor/bin/phpstan analyse --memory-limit=256M -vv", "upload-coverage": "coveralls -v" } diff --git a/composer.lock b/composer.lock index 3e9ca16..2a27a6a 100644 --- a/composer.lock +++ b/composer.lock @@ -263,12 +263,12 @@ "source": { "type": "git", "url": "https://github.com/php-db/phpdb.git", - "reference": "358211307cf82dbc107d6f2759276c737b741117" + "reference": "abf7549cfeaa8d62977313a13dd57435737e1a9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-db/phpdb/zipball/358211307cf82dbc107d6f2759276c737b741117", - "reference": "358211307cf82dbc107d6f2759276c737b741117", + "url": "https://api.github.com/repos/php-db/phpdb/zipball/abf7549cfeaa8d62977313a13dd57435737e1a9b", + "reference": "abf7549cfeaa8d62977313a13dd57435737e1a9b", "shasum": "" }, "require": { @@ -324,7 +324,7 @@ "issues": "https://github.com/php-db/phpdb/issues", "source": "https://github.com/php-db/phpdb" }, - "time": "2026-01-06T06:52:43+00:00" + "time": "2026-01-09T04:38:26+00:00" }, { "name": "psr/container", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 096cae4..0bdc0a7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,2 +1,19 @@ parameters: - ignoreErrors: + ignoreErrors: + - + message: '#^Parameter \#2 \$array of function implode expects array\, array\\> given\.$#' + identifier: argument.type + count: 1 + path: src/Metadata/Source.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, array\\|string\> given\.$#' + identifier: argument.type + count: 1 + path: src/Metadata/Source.php + + - + message: '#^Parameter \#2 \$array of function implode expects array\, array\\> given\.$#' + identifier: argument.type + count: 1 + path: src/Metadata/Source.php diff --git a/src/AdapterPlatform.php b/src/AdapterPlatform.php index 9ba5f64..20d6c45 100644 --- a/src/AdapterPlatform.php +++ b/src/AdapterPlatform.php @@ -27,12 +27,6 @@ class AdapterPlatform extends AbstractPlatform */ protected string $quoteIdentifierTo = '""'; - /** @var string[] */ - private array $knownPgsqlResources = [ - 'pgsql link', - 'pgsql link persistent', - ]; - public function __construct( private readonly DriverInterface|PdoDriverInterface|PDO $driver, ) { @@ -89,6 +83,7 @@ public function quoteTrustedValue($value): string */ protected function quoteViaDriver($value): ?string { + /** @var PgSqlConnection|string $resource */ $resource = $this->driver instanceof DriverInterface ? $this->driver->getConnection()->getResource() : $this->driver; diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index cefed62..df9a2fd 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -4,30 +4,29 @@ namespace PhpDb\Adapter\Pgsql; -use Laminas\ServiceManager\Factory\InvokableFactory; use PhpDb\Adapter\Adapter; use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Driver\DriverInterface; +use PhpDb\Adapter\Driver\Pdo\Statement as PdoStatement; use PhpDb\Adapter\Driver\PdoConnectionInterface; use PhpDb\Adapter\Driver\PdoDriverInterface; -use PhpDb\Adapter\Driver\Pdo\Statement as PdoStatement; -use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\Pgsql\Pdo\Connection as PdoConnection; use PhpDb\Adapter\Pgsql\Pdo\Driver as PdoDriver; -use PhpDb\Adapter\Profiler\Profiler; -use PhpDb\Adapter\Profiler\ProfilerInterface; -use PhpDb\Container\AdapterAbstractServiceFactory; +use PhpDb\Adapter\Platform\PlatformInterface; +use PhpDb\ConfigProvider as PhpDbConfigProvider; use PhpDb\Metadata\MetadataInterface; -use PhpDb\ResultSet\ResultSetInterface; +/** + * @internal + */ final readonly class ConfigProvider { public function __invoke(): array { return [ 'dependencies' => $this->getDependencies(), - AdapterInterface::class => $this->getConfig(), + //AdapterInterface::class => $this->getConfig(), ]; } @@ -44,7 +43,7 @@ public function getConfig(): array 'database' => 'your_database', ], // Named Adapter configurations - 'adapters' => [ + PhpDbConfigProvider::NAMED_ADAPTER_KEY => [ AdapterInterface::class => [ 'driver' => Driver::class, 'connection' => [ @@ -62,11 +61,7 @@ public function getConfig(): array public function getDependencies(): array { return [ - 'abstract_factories' => [ - Container\AdapterAbstractServiceFactory::class, - ], 'aliases' => [ - AdapterInterface::class => Adapter::class, DriverInterface::class => Driver::class, 'pgsql' => Driver::class, 'PgSQL' => Driver::class, @@ -88,15 +83,14 @@ public function getDependencies(): array PlatformInterface::class => AdapterPlatform::class, ], 'factories' => [ - Adapter::class => Container\AdapterInterfaceFactory::class, - AdapterPlatform::class => Container\PlatformInterfaceFactory::class, - Connection::class => Container\ConnectionInterfaceFactory::class, - Metadata\Source::class => Container\MetadataInterfaceFactory::class, - Statement::class => Container\StatementInterfaceFactory::class, - Driver::class => Container\DriverInterfaceFactory::class, - PdoConnection::class => Container\PdoConnectionInterfaceFactory::class, - PdoDriver::class => Container\PdoDriverInterfaceFactory::class, - PdoStatement::class => Container\PdoStatemenFactory::class, + AdapterPlatform::class => Container\PlatformInterfaceFactory::class, + Connection::class => Container\ConnectionInterfaceFactory::class, + Driver::class => Container\DriverInterfaceFactory::class, + Metadata\Source::class => Container\MetadataInterfaceFactory::class, + PdoConnection::class => Container\PdoConnectionInterfaceFactory::class, + PdoDriver::class => Container\PdoDriverInterfaceFactory::class, + PdoStatement::class => Container\PdoStatementFactory::class, + Statement::class => Container\StatementInterfaceFactory::class, // Provide the following if you wish to override the Profiler implementation //ProfilerInterface::class => YourCustomProfilerFactory::class, // Provide the following if you wish to override the ResultSet implementation diff --git a/src/Connection.php b/src/Connection.php index b617f9f..bb0fd8f 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -33,7 +33,7 @@ class Connection extends AbstractConnection implements DriverAwareInterface { - protected Driver $driver; + protected DriverInterface|Driver $driver; /** @var ?PgSqlConnection */ protected $resource; @@ -58,6 +58,7 @@ public function setResource( return $this; } + /** @phpstan-ignore method.childReturnType */ #[Override] public function getResource(): ?PgSqlConnection { @@ -66,7 +67,7 @@ public function getResource(): ?PgSqlConnection #[Override] public function setDriver( - DriverInterface $driver + DriverInterface|Driver $driver ): ConnectionInterface&DriverAwareInterface { $this->driver = $driver; @@ -120,8 +121,8 @@ public function connect(): static set_error_handler(function ($number, $string) { throw new Exception\RuntimeException( self::class . '::connect: Unable to connect to database', - $number ?? 0, - new Exception\ErrorException($string, $number ?? 0) + $number, + new Exception\ErrorException($string, $number) ); }); try { @@ -253,6 +254,7 @@ public function execute($sql): ResultInterface throw new Exception\InvalidQueryException(pg_last_error($this->resource)); } + /** @phpstan-ignore argument.type */ return $this->driver->createResult($resultResource); } diff --git a/src/Container/AdapterAbstractServiceFactory.php b/src/Container/AdapterAbstractServiceFactory.php deleted file mode 100644 index 3906a54..0000000 --- a/src/Container/AdapterAbstractServiceFactory.php +++ /dev/null @@ -1,124 +0,0 @@ -getConfig($container); - - if ($config === []) { - return false; - } - - return isset($config[$requestedName]) - && is_array($config[$requestedName]) - && ! empty($config[$requestedName]); - } - - /** - * Create a DB adapter - * - * @param string $requestedName - */ - public function __invoke( - ContainerInterface|ServiceManager $container, - $requestedName, - ?array $options = null - ): AdapterInterface&Adapter { - /** @var string|null $driverClass */ - $driverClass = $this->config[$requestedName]['driver'] ?? null; - if ($driverClass === null) { - throw new ServiceNotCreatedException( - sprintf('Cannot create adapter "%s"; no driver configured', $requestedName) - ); - } - - /** @var DriverInterface|PdoDriverInterface $driver */ - $driver = $container->build($driverClass, $this->config[$requestedName]); - /** @var PlatformInterface&Pgsql\AdapterPlatform $platform */ - $platform = $container->build(PlatformInterface::class, ['driver' => $driver]); - /** @var ResultSetInterface|null $resultSet */ - $resultSet = $container->has(ResultSetInterface::class) - ? $container->build(ResultSetInterface::class) - : null; - /** @var ProfilerInterface|null $profiler */ - $profiler = $container->has(ProfilerInterface::class) - ? $container->build(ProfilerInterface::class) - : null; - - return match(true) { - $resultSet !== null && $profiler !== null => new Adapter( - driver: $driver, - platform: $platform, - queryResultSetPrototype: $resultSet, - profiler: $profiler, - ), - $resultSet !== null => new Adapter( - driver: $driver, - platform: $platform, - queryResultSetPrototype: $resultSet, - ), - $profiler !== null => new Adapter( - driver: $driver, - platform: $platform, - profiler: $profiler, - ), - default => new Adapter( - driver: $driver, - platform: $platform, - ), - }; - } - - /** - * Get db configuration, if any - * todo: refactor to use PhpDb\ConfigProvider::NAMED_ADAPTER_KEY instead of hardcoding 'adapters' - */ - protected function getConfig(ContainerInterface $container): array - { - if ($this->config !== null) { - return $this->config; - } - - if (! $container->has('config')) { - $this->config = []; - return $this->config; - } - - $config = $container->get('config'); - $this->config = $config[AdapterInterface::class]['adapters'] ?? []; - return $this->config; - } -} diff --git a/src/Container/AdapterInterfaceFactory.php b/src/Container/AdapterInterfaceFactory.php deleted file mode 100644 index d7ba655..0000000 --- a/src/Container/AdapterInterfaceFactory.php +++ /dev/null @@ -1,100 +0,0 @@ -get('config') ?? []; - $adapterConfig = null; - - if ( - isset($config[AdapterInterface::class]['connection']) - || isset($config[Adapter::class]['connection']) - ) { - $adapterConfig = $config[AdapterInterface::class] ?? $config[Adapter::class]; - } else { - $adapterConfig = $config['adapters'][$requestedName] ?? $config; - } - - if ($adapterConfig === []) { - throw Pgsql\Exception\ContainerException::forServiceFailure( - AdapterInterface::class, - sprintf( - 'No configuration found for adapter "%s"', - $requestedName - ) - ); - } - - /** @var class-string|class-string|null */ - $driverClass = $adapterConfig['driver'] ?? null; - - if ($driverClass === null || ! $container->has($driverClass)) { - throw Pgsql\Exception\ContainerException::forServiceFailure( - AdapterInterface::class, - sprintf( - 'Invalid or missing driver provided for adapter "%s"', - $requestedName - ) - ); - } - - /** @var DriverInterface|PdoDriverInterface */ - $driver = $container->build($driverClass, $adapterConfig); - - /** @var PlatformInterface&AdapterPlatform */ - $adapterPlatform = $container->build(PlatformInterface::class, ['driver' => $driver]); - - /** @var ProfilerInterface|null */ - $profilerInterface = $container->has(ProfilerInterface::class) - ? $container->get(ProfilerInterface::class) - : null; - - /** @var ResultSetInterface|null */ - $queryResultSetPrototype = $container->has(ResultSetInterface::class) - ? $container->get(ResultSetInterface::class) - : null; - - return match(true) { - $queryResultSetPrototype !== null && $profilerInterface !== null => new Adapter( - driver: $driver, - platform: $adapterPlatform, - profiler: $profilerInterface, - queryResultSetPrototype: $queryResultSetPrototype, - ), - $queryResultSetPrototype !== null => new Adapter( - driver: $driver, - platform: $adapterPlatform, - queryResultSetPrototype: $queryResultSetPrototype, - ), - $profilerInterface !== null => new Adapter( - driver: $driver, - platform: $adapterPlatform, - profiler: $profilerInterface, - ), - default => new Adapter( - driver: $driver, - platform: $adapterPlatform, - ), - }; - } -} diff --git a/src/Container/ConnectionInterfaceFactory.php b/src/Container/ConnectionInterfaceFactory.php index 45c4c1e..915fce2 100644 --- a/src/Container/ConnectionInterfaceFactory.php +++ b/src/Container/ConnectionInterfaceFactory.php @@ -4,15 +4,17 @@ namespace PhpDb\Adapter\Pgsql\Container; -use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Exception\InvalidConnectionParametersException; use PhpDb\Adapter\Pgsql\Connection; use PhpDb\Adapter\Pgsql\Exception\ContainerException; use Psr\Container\ContainerInterface; +use function is_array; + /** * This factory can only be used via the ServiceManager's build() method + * * @internal */ final class ConnectionInterfaceFactory diff --git a/src/Container/DriverInterfaceFactory.php b/src/Container/DriverInterfaceFactory.php index 5a6e8cb..8678dfb 100644 --- a/src/Container/DriverInterfaceFactory.php +++ b/src/Container/DriverInterfaceFactory.php @@ -12,7 +12,7 @@ final class DriverInterfaceFactory { public function __invoke( - ContainerInterface|ServiceManager $container, + ContainerInterface&ServiceManager $container, string $requestedName, ?array $options = null ): DriverInterface { diff --git a/src/Container/MetadataInterfaceFactory.php b/src/Container/MetadataInterfaceFactory.php index 574af18..7e2f361 100644 --- a/src/Container/MetadataInterfaceFactory.php +++ b/src/Container/MetadataInterfaceFactory.php @@ -6,5 +6,4 @@ final class MetadataInterfaceFactory { - } diff --git a/src/Container/PdoConnectionInterfaceFactory.php b/src/Container/PdoConnectionInterfaceFactory.php index 16866da..10a4fe6 100644 --- a/src/Container/PdoConnectionInterfaceFactory.php +++ b/src/Container/PdoConnectionInterfaceFactory.php @@ -11,6 +11,8 @@ use PhpDb\Adapter\Pgsql\Pdo; use Psr\Container\ContainerInterface; +use function is_array; + /** * @internal */ diff --git a/src/Container/PdoDriverInterfaceFactory.php b/src/Container/PdoDriverInterfaceFactory.php index e6cc9df..c723ca7 100644 --- a/src/Container/PdoDriverInterfaceFactory.php +++ b/src/Container/PdoDriverInterfaceFactory.php @@ -14,13 +14,12 @@ use Psr\Container\ContainerInterface; /** - * * @internal */ final class PdoDriverInterfaceFactory { public function __invoke( - ContainerInterface|ServiceManager $container, + ContainerInterface&ServiceManager $container, string $requestedName, ?array $options = null ): PdoDriverInterface&Pdo\Driver { @@ -36,7 +35,7 @@ public function __invoke( ?? $config[$requestedName]['connection'] ?? $config[AdapterInterface::class]['adapters'][$requestedName]['connection'] ?? null; - $connection = $container->build(Pdo\Connection::class, ['connection' => $connectionParameters]); + $connection = $container->build(Pdo\Connection::class, ['connection' => $connectionParameters]); return new Pdo\Driver( connection: $connection, statementPrototype: $container->build(Statement::class, ['options' => $options['options'] ?? []]), diff --git a/src/Container/PdoStatemenFactory.php b/src/Container/PdoStatementFactory.php similarity index 95% rename from src/Container/PdoStatemenFactory.php rename to src/Container/PdoStatementFactory.php index d866063..f3caafe 100644 --- a/src/Container/PdoStatemenFactory.php +++ b/src/Container/PdoStatementFactory.php @@ -9,7 +9,7 @@ use PhpDb\Adapter\ParameterContainer; use Psr\Container\ContainerInterface; -final class PdoStatemenFactory +final class PdoStatementFactory { public function __invoke( ContainerInterface $container, diff --git a/src/Container/PlatformInterfaceFactory.php b/src/Container/PlatformInterfaceFactory.php index 9f1312f..a83ed6d 100644 --- a/src/Container/PlatformInterfaceFactory.php +++ b/src/Container/PlatformInterfaceFactory.php @@ -4,8 +4,8 @@ namespace PhpDb\Adapter\Pgsql\Container; -use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\Pgsql; +use PhpDb\Adapter\Platform\PlatformInterface; use Psr\Container\ContainerInterface; final class PlatformInterfaceFactory @@ -20,8 +20,8 @@ public function __invoke( ): PlatformInterface&Pgsql\AdapterPlatform { $driver = $options['driver'] ?? null; if ( - ! ($driver instanceof Pgsql\Driver) - && ! ($driver instanceof Pgsql\Pdo\Driver) + ! $driver instanceof Pgsql\Driver + && ! $driver instanceof Pgsql\Pdo\Driver ) { // todo: Once latest PR is merged for 0.5.0 update to use PhpDB\Exception\ContainerException throw Pgsql\Exception\ContainerException::forServiceFailure( diff --git a/src/Container/StatementInterfaceFactory.php b/src/Container/StatementInterfaceFactory.php index b096165..8e95512 100644 --- a/src/Container/StatementInterfaceFactory.php +++ b/src/Container/StatementInterfaceFactory.php @@ -4,10 +4,10 @@ namespace PhpDb\Adapter\Pgsql\Container; -use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\Adapter\Pgsql; use Psr\Container\ContainerInterface; + final class StatementInterfaceFactory { public function __invoke( @@ -15,7 +15,6 @@ public function __invoke( string $requestedName, ?array $options = null ): StatementInterface { - return new Pgsql\Statement( options: $options['options'] ?? false ); diff --git a/src/Driver.php b/src/Driver.php index a5a3b5a..c7d86fd 100644 --- a/src/Driver.php +++ b/src/Driver.php @@ -7,7 +7,6 @@ use Override; use PgSql\Result as PgSqlResult; use PhpDb\Adapter\Driver\ConnectionInterface; -use PhpDb\Adapter\Driver\DriverAwareInterface; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; @@ -20,6 +19,9 @@ use function extension_loaded; use function is_string; +/** + * @final + */ class Driver implements DriverInterface, ProfilerAwareInterface { use DatabasePlatformNameTrait; @@ -34,7 +36,7 @@ class Driver implements DriverInterface, ProfilerAwareInterface public function __construct( protected readonly ConnectionInterface&Connection $connection, protected readonly StatementInterface&Statement $statementPrototype, - protected readonly ResultInterface $resultPrototype, + protected readonly ResultInterface&Result $resultPrototype, array $options = [] ) { $this->checkEnvironment(); @@ -42,12 +44,9 @@ public function __construct( //todo: verify this usage $options = array_intersect_key(array_merge($this->options, $options), $this->options); - if ($this->connection instanceof DriverAwareInterface) { - $this->connection->setDriver($this); - } - if ($this->statementPrototype instanceof DriverAwareInterface) { - $this->statementPrototype->setDriver($this); - } + $this->connection->setDriver($this); + + $this->statementPrototype->setDriver($this); } #[Override] @@ -55,13 +54,10 @@ public function setProfiler( ProfilerInterface $profiler ): DriverInterface&ProfilerAwareInterface { $this->profiler = $profiler; - if ($this->connection instanceof ProfilerAwareInterface) { - $this->connection->setProfiler($profiler); - } - if ($this->statementPrototype instanceof ProfilerAwareInterface) { - $this->statementPrototype->setProfiler($profiler); - } + $this->connection->setProfiler($profiler); + + $this->statementPrototype->setProfiler($profiler); return $this; } @@ -114,7 +110,7 @@ public function createStatement($sqlOrResource = null): StatementInterface&State /** * Create result * - * @param PgSqlResult $resource + * @param PgSqlResult|resource $resource */ #[Override] public function createResult($resource): ResultInterface&Result diff --git a/src/Exception/ContainerException.php b/src/Exception/ContainerException.php index 639e953..ce913c0 100644 --- a/src/Exception/ContainerException.php +++ b/src/Exception/ContainerException.php @@ -7,6 +7,8 @@ use Psr\Container\ContainerExceptionInterface; use RuntimeException; +use function sprintf; + final class ContainerException extends RuntimeException implements ContainerExceptionInterface { public static function forServiceFailure( @@ -16,8 +18,8 @@ public static function forServiceFailure( return new self( sprintf( 'Failed to create service "%s": %s', - $serviceName, - $reason + $serviceName, + $reason ), 0, ); diff --git a/src/Metadata/Source.php b/src/Metadata/Source.php index e82fd2b..fd6335e 100644 --- a/src/Metadata/Source.php +++ b/src/Metadata/Source.php @@ -8,6 +8,7 @@ use Override; use PhpDb\Adapter\AdapterInterface; use PhpDb\Metadata\Source\AbstractSource; +use PhpDb\ResultSet; use function array_change_key_case; use function array_walk; @@ -36,6 +37,7 @@ protected function loadSchemaData(): void . ' != \'information_schema\'' . ' AND ' . $p->quoteIdentifier('schema_name') . " NOT LIKE 'pg_%'"; + /** @var ResultSet\ResultSetInterface&ResultSet\ResultSet $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $schemas = []; @@ -88,6 +90,7 @@ protected function loadTableNameData(string $schema): void . ' != \'information_schema\''; } + /** @var ResultSet\ResultSetInterface&ResultSet\ResultSet $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $tables = []; @@ -144,6 +147,7 @@ protected function loadColumnData(string $table, string $schema): void . ' = ' . $platform->quoteTrustedValue($schema); } + /** @var ResultSet\ResultSetInterface&ResultSet\ResultSet $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $columns = []; foreach ($results->toArray() as $row) { @@ -257,10 +261,12 @@ protected function loadConstraintData(string $table, string $schema): void . ', ' . $p->quoteIdentifierChain(['tc', 'constraint_name']) . ', ' . $p->quoteIdentifierChain(['kcu', 'ordinal_position']); + /** @var ResultSet\ResultSetInterface&ResultSet\ResultSet $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $name = null; $constraints = []; + $isFK = false; foreach ($results->toArray() as $row) { if ($row['constraint_name'] !== $name) { $name = $row['constraint_name']; @@ -345,6 +351,7 @@ protected function loadTriggerData(string $schema): void . ' != \'information_schema\''; } + /** @var ResultSet\ResultSetInterface&ResultSet\ResultSet $results */ $results = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); $data = []; diff --git a/src/Pdo/Connection.php b/src/Pdo/Connection.php index 163112a..2fecfe3 100644 --- a/src/Pdo/Connection.php +++ b/src/Pdo/Connection.php @@ -14,7 +14,9 @@ use PhpDb\Adapter\Exception; use function array_diff_key; +use function implode; use function is_int; +use function str_contains; use function strtolower; class Connection extends AbstractPdoConnection @@ -56,7 +58,7 @@ public function connect(): ConnectionInterface&PdoConnectionInterface foreach ($this->connectionParameters as $key => $value) { $result = match (strtolower($key)) { 'dsn' => $dsn = (string) $value, - 'user', 'username' => $username = (string)$value, + 'user', 'username' => $username = (string) $value, 'password', 'pass' => $password = (string) $value, 'host', 'hostname' => $hostname = (string) $value, 'port' => $port = (int) $value, @@ -98,7 +100,11 @@ public function connect(): ConnectionInterface&PdoConnectionInterface $dsn = 'pgsql:' . implode(';', $dsn); } - if (! is_string($dsn)) { + if ( + ! str_contains($dsn, 'host=') + && ! str_contains($dsn, 'dbname=') + && ! str_contains($dsn, 'user=') + ) { throw new Exception\InvalidConnectionParametersException( 'A dsn was not provided or could not be constructed from your parameters', $this->connectionParameters @@ -125,7 +131,7 @@ public function connect(): ConnectionInterface&PdoConnectionInterface /** * {@inheritDoc} * - * @param string $name + * @param ?string $name */ #[Override] public function getLastGeneratedValue($name = null): string|int|false diff --git a/src/Pdo/Driver.php b/src/Pdo/Driver.php index 48f21a2..9fed4fd 100644 --- a/src/Pdo/Driver.php +++ b/src/Pdo/Driver.php @@ -16,7 +16,7 @@ class Driver extends AbstractPdo use DatabasePlatformNameTrait; /** - * @param PDOStatement $resource + * @param PDOStatement|resource $resource */ #[Override] public function createResult($resource): ResultInterface diff --git a/src/Statement.php b/src/Statement.php index 8ea3752..6c15e23 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -33,9 +33,9 @@ class Statement implements protected string $statementName = 'statement'; - protected Driver $driver; + protected DriverInterface|Driver $driver; - protected ProfilerInterface $profiler; + protected ?ProfilerInterface $profiler = null; protected PgSqlConnection $pgsql; @@ -70,7 +70,7 @@ public function setProfiler( return $this; } - public function getProfiler(): (StatementInterface&ProfilerInterface)|null + public function getProfiler(): ?ProfilerInterface { return $this->profiler; } @@ -154,13 +154,9 @@ public function execute(ParameterContainer|array|null $parameters = null): ?Resu } /** START Standard ParameterContainer Merging Block */ - if (! $this->parameterContainer instanceof ParameterContainer) { - if ($parameters instanceof ParameterContainer) { - $this->parameterContainer = $parameters; - $parameters = null; - } else { - $this->parameterContainer = new ParameterContainer(); - } + if ($parameters instanceof ParameterContainer) { + $this->parameterContainer = $parameters; + $parameters = null; } if (is_array($parameters)) { @@ -181,7 +177,7 @@ public function execute(ParameterContainer|array|null $parameters = null): ?Resu if ($resultResource === false) { throw new Exception\InvalidQueryException(pg_last_error()); } - + /** @phpstan-ignore argument.type */ return $this->driver->createResult($resultResource); } } diff --git a/test/asset/SetupTrait.php b/test/asset/SetupTrait.php index a4e26ea..1a2f60e 100644 --- a/test/asset/SetupTrait.php +++ b/test/asset/SetupTrait.php @@ -11,19 +11,22 @@ use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\PdoConnectionInterface; use PhpDb\Adapter\Driver\PdoDriverInterface; -use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\Pgsql; +use PhpDb\Adapter\Platform\PlatformInterface; +use PhpDb\ConfigProvider as PhpDbConfigProvider; use Psr\Container\ContainerInterface; +use function getenv; + trait SetupTrait { public final const NATIVE_ADAPTER = 'Pgsql\Adapter'; public final const PDO_ADAPTER = 'Pgsql\Pdo\Adapter'; - protected array $conn = []; + protected array $conn = []; protected ServiceManager $serviceManager; protected function setUp(): void { - $conn = [ + $conn = [ 'host' => (string) getenv('TESTS_PHPDB_ADAPTER_PGSQL_HOSTNAME'), 'port' => 5432, 'username' => (string) getenv('TESTS_PHPDB_ADAPTER_PGSQL_USERNAME'), @@ -45,23 +48,28 @@ public function getAdapter(string $adapter = self::NATIVE_ADAPTER): AdapterInter ], ] ); + $serviceManagerConfig = ArrayUtils::merge( + $serviceManagerConfig, + (new PhpDbConfigProvider())()['dependencies'] + ); $this->serviceManager = new ServiceManager($serviceManagerConfig); return $this->serviceManager->get($adapter); } public function getTestConfig(): array { - return [ AdapterInterface::class => [ - 'driver' => Pgsql\Driver::class, - 'adapters' => [ + 'driver' => Pgsql\Driver::class, + 'connection' => $this->conn, + // Named Adapter configurations + PhpDbConfigProvider::NAMED_ADAPTER_KEY => [ self::NATIVE_ADAPTER => [ - 'driver' => Pgsql\Driver::class, + 'driver' => Pgsql\Driver::class, 'connection' => $this->conn, ], - self::PDO_ADAPTER => [ - 'driver' => Pgsql\Pdo\Driver::class, + self::PDO_ADAPTER => [ + 'driver' => Pgsql\Pdo\Driver::class, 'connection' => $this->conn, ], ], @@ -75,9 +83,9 @@ public function getMockedAdapter(string $adapter = self::NATIVE_ADAPTER): Adapte $pdoDriverMock = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); $testConfig = [ AdapterInterface::class => [ - 'adapters' => [ + PhpDbConfigProvider::NAMED_ADAPTER_KEY => [ self::NATIVE_ADAPTER => [ - 'driver' => Pgsql\Driver::class, + 'driver' => Pgsql\Driver::class, 'connection' => [ 'host' => (string) getenv('TESTS_PHPDB_ADAPTER_PGSQL_HOSTNAME'), 'port' => 5432, @@ -86,8 +94,8 @@ public function getMockedAdapter(string $adapter = self::NATIVE_ADAPTER): Adapte 'database' => (string) getenv('TESTS_PHPDB_ADAPTER_PGSQL_DATABASE'), ], ], - self::PDO_ADAPTER => [ - 'driver' => Pgsql\Pdo\Driver::class, + self::PDO_ADAPTER => [ + 'driver' => Pgsql\Pdo\Driver::class, 'connection' => [ 'host' => (string) getenv('TESTS_PHPDB_ADAPTER_PGSQL_HOSTNAME'), 'port' => 5432, @@ -98,10 +106,7 @@ public function getMockedAdapter(string $adapter = self::NATIVE_ADAPTER): Adapte ], ], ], - 'dependencies' => [ - 'abstract_factories' => [ - Pgsql\Container\AdapterAbstractServiceFactory::class, - ], + 'dependencies' => [ 'aliases' => [ DriverInterface::class => Pgsql\Driver::class, 'pgsql' => Pgsql\Driver::class, @@ -124,7 +129,7 @@ public function getMockedAdapter(string $adapter = self::NATIVE_ADAPTER): Adapte PlatformInterface::class => Pgsql\AdapterPlatform::class, ], 'factories' => [ - Pgsql\Driver::class => function (ContainerInterface $container) use ($driverMock) { + Pgsql\Driver::class => function (ContainerInterface $container) use ($driverMock) { return $driverMock; }, Pgsql\Pdo\Driver::class => function (ContainerInterface $container) use ($pdoDriverMock) { diff --git a/test/integration/AdapterPlatformTest.php b/test/integration/AdapterPlatformTest.php index e9f6f96..ad4a0a3 100644 --- a/test/integration/AdapterPlatformTest.php +++ b/test/integration/AdapterPlatformTest.php @@ -4,46 +4,36 @@ namespace PhpDbIntegrationTest\Adapter\Pgsql; -use Laminas\Stdlib\ErrorHandler; +use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; use PhpDb\Adapter\Pgsql\AdapterPlatform; use PhpDbTestAsset\Pgsql\SetupTrait; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\TestCase; -use const E_USER_NOTICE; - #[CoversClass(AdapterPlatform::class)] #[CoversMethod(AdapterPlatform::class, 'quoteValue')] final class AdapterPlatformTest extends TestCase { use SetupTrait; - /** - * @return void - */ public function testQuoteValueWithPgsql(): void { $adapter = $this->getAdapter(self::NATIVE_ADAPTER); $pgsql = $adapter->getPlatform(); - ErrorHandler::start(E_USER_NOTICE); - $value = $pgsql->quoteValue('value'); - ErrorHandler::stop(); + $this->expectException(VunerablePlatformQuoteException::class); + $value = $pgsql->quoteValue('value'); self::assertEquals('\'value\'', $value); // Should this assertion be? //todo: self::assertEquals('E\'value\'', $value); } - /** - * @return void - */ public function testQuoteValueWithPdoPgsql(): void { $adapter = $this->getAdapter(self::PDO_ADAPTER); $pgsql = $adapter->getPlatform(); - ErrorHandler::start(E_USER_NOTICE); + //$this->expectException(VunerablePlatformQuoteException::class); $value = $pgsql->quoteValue('value'); - ErrorHandler::stop(); self::assertEquals('\'value\'', $value); } } diff --git a/test/integration/Pdo/AdapterTest.php b/test/integration/Pdo/AdapterTest.php index 3341ea9..16273be 100644 --- a/test/integration/Pdo/AdapterTest.php +++ b/test/integration/Pdo/AdapterTest.php @@ -43,7 +43,7 @@ public function testDriverDisconnectAfterQuoteWithPlatform(): void $isTcpConnection = $this->isTcpConnection(); /** @var AdapterInterface&Adapter $adapter */ - $adapter = $this->getAdapter(self::PDO_ADAPTER); + $adapter = $this->getAdapter(self::PDO_ADAPTER); $connection = $adapter->getDriver()->getConnection(); $connection->connect(); $isConnected = $connection->isConnected(); diff --git a/test/integration/Pdo/TableGatewayTest.php b/test/integration/Pdo/TableGatewayTest.php index 1602d7e..1e08c9c 100644 --- a/test/integration/Pdo/TableGatewayTest.php +++ b/test/integration/Pdo/TableGatewayTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace PhpDbIntegrationTest\Adapter\Pgsql\Driver\Pdo; +namespace PhpDbIntegrationTest\Adapter\Pgsql\Pdo; use PhpDb\Sql\TableIdentifier; use PhpDb\TableGateway\Feature\FeatureSet; diff --git a/test/unit/Adapter/AdapterTest.php b/test/unit/Adapter/AdapterTest.php deleted file mode 100644 index cdfb8f0..0000000 --- a/test/unit/Adapter/AdapterTest.php +++ /dev/null @@ -1,248 +0,0 @@ -adapter->setProfiler(new Profiler\Profiler()); - self::assertSame($this->adapter, $ret); - } - - #[TestDox('unit test: Test getProfiler() will store profiler')] - public function testGetProfiler(): void - { - $this->adapter->setProfiler($profiler = new Profiler\Profiler()); - self::assertSame($profiler, $this->adapter->getProfiler()); - - $adapter = new Adapter( - $this->mockDriver, - $this->mockPlatform, - $this->getMockBuilder(ResultSetInterface::class)->getMock(), - $profiler - ); - self::assertInstanceOf(Profiler\Profiler::class, $adapter->getProfiler()); - } - - #[TestDox('unit test: Test getDriver() will return driver object')] - public function testGetDriver(): void - { - self::assertSame($this->mockDriver, $this->adapter->getDriver()); - } - - #[TestDox('unit test: Test getPlatform() returns platform object')] - public function testGetPlatform(): void - { - self::assertSame($this->mockPlatform, $this->adapter->getPlatform()); - } - - #[TestDox('unit test: Test getPlatform() returns platform object')] - public function testGetQueryResultSetPrototype(): void - { - self::assertInstanceOf(ResultSetInterface::class, $this->adapter->getQueryResultSetPrototype()); - } - - #[TestDox('unit test: Test getCurrentSchema() returns current schema from connection object')] - public function testGetCurrentSchema(): void - { - $this->mockConnection->expects($this->any())->method('getCurrentSchema')->willReturn('FooSchema'); - self::assertEquals('FooSchema', $this->adapter->getCurrentSchema()); - } - - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode produces a statement object')] - public function testQueryWhenPreparedProducesStatement(): void - { - $s = $this->adapter->query('SELECT foo'); - self::assertSame($this->mockStatement, $s); - } - - /** - * @throws Exception - * @throws \Exception - */ - #[Group('#210')] - public function testProducedResultSetPrototypeIsDifferentForEachQuery(): void - { - $statement = $this->createMock(StatementInterface::class); - $result = $this->createMock(ResultInterface::class); - - $this->mockDriver->method('createStatement') - ->willReturn($statement); - $this->mockStatement->method('execute') - ->willReturn($result); - $result->method('isQueryResult') - ->willReturn(true); - - self::assertNotSame( - $this->adapter->query('SELECT foo', []), - $this->adapter->query('SELECT foo', []) - ); - } - - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode, with array of parameters, produces a result object')] - public function testQueryWhenPreparedWithParameterArrayProducesResult(): void - { - $parray = ['bar' => 'foo']; - $sql = 'SELECT foo, :bar'; - $statement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockDriver->expects($this->any())->method('createStatement') - ->with($sql)->willReturn($statement); - $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); - - $r = $this->adapter->query($sql, $parray); - self::assertSame($result, $r); - } - - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode, with ParameterContainer, produces a result object')] - public function testQueryWhenPreparedWithParameterContainerProducesResult(): void - { - $sql = 'SELECT foo'; - $parameterContainer = $this->getMockBuilder(ParameterContainer::class)->getMock(); - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockDriver->expects($this->any())->method('createStatement') - ->with($sql)->willReturn($this->mockStatement); - $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(true); - - $r = $this->adapter->query($sql, $parameterContainer); - self::assertInstanceOf(ResultSet::class, $r); - } - - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in execute mode produces a driver result object')] - public function testQueryWhenExecutedProducesAResult(): void - { - $sql = 'SELECT foo'; - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); - - $r = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE); - self::assertSame($result, $r); - } - - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in execute mode produces a resultset object')] - public function testQueryWhenExecutedProducesAResultSetObjectWhenResultIsQuery(): void - { - $sql = 'SELECT foo'; - - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(true); - - $r = $this->adapter->query($sql, Adapter::QUERY_MODE_EXECUTE); - self::assertInstanceOf(ResultSet::class, $r); - } - - #[TestDox('unit test: Test createStatement() produces a statement object')] - public function testCreateStatement(): void - { - self::assertSame($this->mockStatement, $this->adapter->createStatement()); - } - - #[TestDox('unit test: Test __get() magic method')] - public function testMagicGet(): void - { - self::assertSame($this->mockDriver, $this->adapter->driver); - /** @psalm-suppress UndefinedMagicPropertyFetch */ - self::assertSame($this->mockDriver, $this->adapter->DrivER); - /** @psalm-suppress UndefinedMagicPropertyFetch */ - self::assertSame($this->mockPlatform, $this->adapter->PlatForm); - self::assertSame($this->mockPlatform, $this->adapter->platform); - - $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('Invalid magic'); - $this->adapter->foo; - } - - // #[Override] - // protected function setUp(): void - // { - // $this->mockConnection = $this->createMock(ConnectionInterface::class); - - // $this->mockStatement = $this->getMockBuilder(Statement::class)->getMock(); - - // $this->mockDriver = $this->getMockBuilder(Driver::class) - // ->setConstructorArgs([ - // $this->mockConnection, - // $this->mockStatement, - // ]) - // ->getMock(); - - // $this->mockDriver->method('getDatabasePlatformName')->willReturn('Pgsql'); - // $this->mockDriver->method('checkEnvironment')->willReturn(true); - // $this->mockDriver->method('getConnection')->willReturn($this->mockConnection); - // $this->mockDriver->method('createStatement')->willReturn($this->mockStatement); - - // $this->mockPlatform = new AdapterPlatform($this->mockDriver); - - // $this->mockResultSet = $this->getMockBuilder(ResultSetInterface::class)->getMock(); - - // $this->adapter = new Adapter( - // $this->mockDriver, - // $this->mockPlatform, - // $this->mockResultSet - // ); - // } -} diff --git a/test/unit/Adapter/ConfigProviderTest.php b/test/unit/Adapter/ConfigProviderTest.php index 8d1c86b..5b2597e 100644 --- a/test/unit/Adapter/ConfigProviderTest.php +++ b/test/unit/Adapter/ConfigProviderTest.php @@ -5,16 +5,15 @@ namespace PhpDbTest\Pgsql; use Laminas\ServiceManager\Factory\InvokableFactory; -use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\ConnectionInterface; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\Driver\PdoConnectionInterface; use PhpDb\Adapter\Driver\PdoDriverInterface; +use PhpDb\Adapter\Driver\Pdo\Statement as PdoStatement; use PhpDb\Adapter\Pgsql; +use PhpDb\Adapter\Pgsql\Pdo\Connection as PdoConnection; +use PhpDb\Adapter\Pgsql\Pdo\Driver as PdoDriver; use PhpDb\Adapter\Platform\PlatformInterface; -use PhpDb\Adapter\Profiler\Profiler; -use PhpDb\Adapter\Profiler\ProfilerInterface; -use PhpDb\Container\AdapterAbstractServiceFactory; use PhpDb\Metadata\MetadataInterface; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Depends; @@ -32,31 +31,30 @@ final class ConfigProviderTest extends TestCase 'PgSQL' => Pgsql\Driver::class, 'Postgresql' => Pgsql\Driver::class, 'PostgreSQL' => Pgsql\Driver::class, - PdoDriverInterface::class => Pgsql\Pdo\Driver::class, - 'pdo_pgsql' => Pgsql\Pdo\Driver::class, - 'PDO_pgsql' => Pgsql\Pdo\Driver::class, - 'PDO_PgSQL' => Pgsql\Pdo\Driver::class, - 'Pdo_PgSQL' => Pgsql\Pdo\Driver::class, - 'PDO_postgresql' => Pgsql\Pdo\Driver::class, - 'pdo_postgresql' => Pgsql\Pdo\Driver::class, - 'PDO_Postgresql' => Pgsql\Pdo\Driver::class, - 'Pdo_Postgresql' => Pgsql\Pdo\Driver::class, - 'PDO_PostgreSQL' => Pgsql\Pdo\Driver::class, - 'pdo_PostgreSQL' => Pgsql\Pdo\Driver::class, + PdoDriverInterface::class => PdoDriver::class, + 'pdo_pgsql' => PdoDriver::class, + 'PDO_pgsql' => PdoDriver::class, + 'PDO_PgSQL' => PdoDriver::class, + 'Pdo_PgSQL' => PdoDriver::class, + 'PDO_postgresql' => PdoDriver::class, + 'pdo_postgresql' => PdoDriver::class, + 'PDO_Postgresql' => PdoDriver::class, + 'Pdo_Postgresql' => PdoDriver::class, + 'PDO_PostgreSQL' => PdoDriver::class, ConnectionInterface::class => Pgsql\Connection::class, MetadataInterface::class => Pgsql\Metadata\Source::class, PdoConnectionInterface::class => Pgsql\Pdo\Connection::class, PlatformInterface::class => Pgsql\AdapterPlatform::class, - ProfilerInterface::class => Profiler::class, ], 'factories' => [ - AdapterInterface::class => Pgsql\Container\AdapterInterfaceFactory::class, - Pgsql\Driver::class => Pgsql\Container\DriverInterfaceFactory::class, - Pgsql\Pdo\Driver::class => Pgsql\Container\PdoDriverInterfaceFactory::class, - Pgsql\Connection::class => Pgsql\Container\ConnectionInterfaceFactory::class, - Pgsql\Pdo\Connection::class => Pgsql\Container\PdoConnectionInterfaceFactory::class, Pgsql\AdapterPlatform::class => Pgsql\Container\PlatformInterfaceFactory::class, - Profiler::class => InvokableFactory::class, + Pgsql\Connection::class => Pgsql\Container\ConnectionInterfaceFactory::class, + Pgsql\Driver::class => Pgsql\Container\DriverInterfaceFactory::class, + Pgsql\Metadata\Source::class => Pgsql\Container\MetadataInterfaceFactory::class, + Pgsql\Statement::class => Pgsql\Container\StatementInterfaceFactory::class, + PdoConnection::class => Pgsql\Container\PdoConnectionInterfaceFactory::class, + PdoDriver::class => Pgsql\Container\PdoDriverInterfaceFactory::class, + PdoStatement::class => Pgsql\Container\PdoStatementFactory::class, ], ]; diff --git a/test/unit/Adapter/SetupTest.php b/test/unit/Adapter/SetupTest.php index 0506f0f..58b4209 100644 --- a/test/unit/Adapter/SetupTest.php +++ b/test/unit/Adapter/SetupTest.php @@ -6,8 +6,10 @@ use PhpDb\Adapter\AdapterInterface; use PhpDbTestAsset\Pgsql\SetupTrait; +use PHPUnit\Framework\Attributes\CoversTrait; use PHPUnit\Framework\TestCase; +#[CoversTrait(SetupTrait::class)] final class SetupTest extends TestCase { use SetupTrait;