diff --git a/docs/db.md b/docs/db.md index 50a10ba..02742bf 100644 --- a/docs/db.md +++ b/docs/db.md @@ -384,6 +384,39 @@ $resource = $result->getResource(); assert($resource !== FALSE); ``` +#### Use custom result factory + +You can use your own result object. Your result object must `extends` existing `Result` object, and you must implement your own `ResultFactory` to create your own results. Then you can set your factory to the `Connection` and it will be used for all new query results. + +```php +class MyOwnResult extends Forrest79\PhPgSql\Db\Result +{ + public function age(): string + { + return $this->age . ' years'; + } +} + +class MyOwnResultFactory implements Forrest79\PhPgSql\Db\ResultFactory +{ + public function createResult(Forrest79\PhPgSql\Db\ColumnValueParser $columnValueParser, array $rawValues): Forrest79\PhPgSql\Db\Result + { + return new MyOwnRow($columnValueParser, $rawValues); + } +} + +$result = $connection->query('SELECT age FROM users WHERE id = 1'); +$result->setResultFactory(new MyOwnResultFactory()); +$row = $result->fetch(); +dump($row->age()); // (string) '45 years' + +$connection->setRowFactory(new MyOwnResultFactory()); +$row = $connection->query('SELECT age FROM users WHERE id = 2')->fetch(); +dump($row->age()); // (string) '24 years' +``` + +> By default, is used `Forrest79\PhPgSql\Db\ResultFactories\Basic` result factory that produces default `Result` objects. + #### Safely passing parameters Important is to know how to safety pass parameters to a query. You can do something like this: @@ -594,7 +627,7 @@ $result->setRowFactory(new MyOwnRowFactory()); $row = $result->fetch(); dump($row->age()); // (string) '45 years' -$connection->setDefaultRowFactory(new MyOwnRowFactory()); +$connection->setRowFactory(new MyOwnRowFactory()); $row = $connection->query('SELECT age FROM users WHERE id = 2')->fetch(); dump($row->age()); // (string) '24 years' ``` diff --git a/phpcs-ignores.neon b/phpcs-ignores.neon index 1cefa23..8c38f5d 100644 --- a/phpcs-ignores.neon +++ b/phpcs-ignores.neon @@ -225,7 +225,19 @@ ignoreErrors: sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal message: All classes should be declared using either the "abstract" or "final" keyword. count: 1 - path: src/Db/ResultIterator.php + path: src/Db/ResultBuilder.php + + - + sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal + message: All classes should be declared using either the "abstract" or "final" keyword. + count: 1 + path: src/Db/ResultFactories/Basic.php + + - + sniff: Squiz.Scope.MethodScope.Missing + message: Visibility must be declared on method "create" + count: 1 + path: src/Db/ResultFactory.php - sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal @@ -251,6 +263,12 @@ ignoreErrors: count: 1 path: src/Db/RowFactory.php + - + sniff: SlevomatCodingStandard.Classes.RequireAbstractOrFinal.ClassNeitherAbstractNorFinal + message: All classes should be declared using either the "abstract" or "final" keyword. + count: 1 + path: src/Db/RowIterator.php + - sniff: Squiz.Scope.MethodScope.Missing message: Visibility must be declared on method "getParams" @@ -371,6 +389,18 @@ ignoreErrors: count: 1 path: src/Fluent/Sql.php + - + sniff: Squiz.Scope.MethodScope.Missing + message: Visibility must be declared on method "doNothing" + count: 1 + path: src/Fluent/Sql.php + + - + sniff: Squiz.Scope.MethodScope.Missing + message: Visibility must be declared on method "doUpdate" + count: 1 + path: src/Fluent/Sql.php + - sniff: Squiz.Scope.MethodScope.Missing message: Visibility must be declared on method "except" @@ -491,18 +521,6 @@ ignoreErrors: count: 1 path: src/Fluent/Sql.php - - - sniff: Squiz.Scope.MethodScope.Missing - message: Visibility must be declared on method "doUpdate" - count: 1 - path: src/Fluent/Sql.php - - - - sniff: Squiz.Scope.MethodScope.Missing - message: Visibility must be declared on method "doNothing" - count: 1 - path: src/Fluent/Sql.php - - sniff: Squiz.Scope.MethodScope.Missing message: Visibility must be declared on method "orderBy" @@ -625,13 +643,13 @@ ignoreErrors: - sniff: Squiz.Scope.MethodScope.Missing - message: Visibility must be declared on method "whereIf" + message: Visibility must be declared on method "whereAnd" count: 1 path: src/Fluent/Sql.php - sniff: Squiz.Scope.MethodScope.Missing - message: Visibility must be declared on method "whereAnd" + message: Visibility must be declared on method "whereIf" count: 1 path: src/Fluent/Sql.php @@ -684,14 +702,14 @@ ignoreErrors: path: tests/Integration/CollectingResultsTest.php - - sniff: PSR1.Files.SideEffects.FoundWithSymbols - message: 'A file should declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it should execute logic with side effects, but should not do both. The first symbol is defined on line 15 and the first side effect is on line 9.' + sniff: SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter + message: Unused parameter $connection. count: 1 - path: tests/Integration/CustomPrepareQueryTest.php + path: tests/Integration/CollectingResultsTest.php - - sniff: SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter - message: Unused parameter $connection. + sniff: PSR1.Files.SideEffects.FoundWithSymbols + message: 'A file should declare new symbols (classes, functions, constants, etc.) and cause no other side effects, or it should execute logic with side effects, but should not do both. The first symbol is defined on line 15 and the first side effect is on line 9.' count: 1 path: tests/Integration/CustomPrepareQueryTest.php @@ -699,7 +717,7 @@ ignoreErrors: sniff: SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter message: Unused parameter $connection. count: 1 - path: tests/Integration/CollectingResultsTest.php + path: tests/Integration/CustomPrepareQueryTest.php - sniff: PSR1.Files.SideEffects.FoundWithSymbols diff --git a/phpstan.neon b/phpstan.neon index 74e18fc..61eb859 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -183,16 +183,16 @@ parameters: path: %rootDir%/../../../src/Db/Result.php count: 1 - - - message: '#^Method Forrest79\\PhPgSql\\Db\\ResultIterator::current\(\) should return Forrest79\\PhPgSql\\Db\\Row but returns Forrest79\\PhPgSql\\Db\\Row\|null\.$#' - path: %rootDir%/../../../src/Db/ResultIterator.php - count: 1 - - message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#' path: %rootDir%/../../../src/Db/Row.php count: 3 + - + message: '#^Method Forrest79\\PhPgSql\\Db\\RowIterator::current\(\) should return Forrest79\\PhPgSql\\Db\\Row but returns Forrest79\\PhPgSql\\Db\\Row\|null\.$#' + path: %rootDir%/../../../src/Db/RowIterator.php + count: 1 + - message: '#^Parameter \#2 \$expected of static method Forrest79\\PhPgSql\\Fluent\\Exceptions\\QueryBuilderException::badParamsCount\(\) expects int, int(\<0, max\>)?\|false(\|null)? given\.$#' # (|null)? is for forward PHP 8 compatibility path: %rootDir%/../../../src/Fluent/QueryBuilder.php @@ -208,6 +208,6 @@ parameters: # === Fix PhPgSql-PHPStan rules === - - message: '#^Method Forrest79\\PhPgSql\\Fluent\\QueryExecute::getIterator\(\) should return Forrest79\\PhPgSql\\Db\\ResultIterator but returns iterable\.$#' + message: '#^Method Forrest79\\PhPgSql\\Fluent\\QueryExecute::getIterator\(\) should return Forrest79\\PhPgSql\\Db\\RowIterator but returns iterable\.$#' path: %rootDir%/../../../src/Fluent/QueryExecute.php count: 1 diff --git a/src/Db/AsyncHelper.php b/src/Db/AsyncHelper.php index 654bd1b..0f33f0c 100644 --- a/src/Db/AsyncHelper.php +++ b/src/Db/AsyncHelper.php @@ -19,9 +19,13 @@ public function __construct(Connection $connection) } - public function createAndSetAsyncQuery(Query $query, string|NULL $preparedStatementName = NULL): AsyncQuery + public function createAndSetAsyncQuery( + ResultBuilder $resultBuilder, + Query $query, + string|NULL $preparedStatementName = NULL, + ): AsyncQuery { - $this->asyncQuery = new AsyncQuery($this->connection, $this, $query, $preparedStatementName); + $this->asyncQuery = new AsyncQuery($this->connection, $resultBuilder, $this, $query, $preparedStatementName); $this->asyncExecuteQuery = NULL; return $this->asyncQuery; diff --git a/src/Db/AsyncPreparedStatement.php b/src/Db/AsyncPreparedStatement.php index 728111f..e904427 100644 --- a/src/Db/AsyncPreparedStatement.php +++ b/src/Db/AsyncPreparedStatement.php @@ -7,9 +7,15 @@ class AsyncPreparedStatement extends PreparedStatementHelper private AsyncHelper $asyncHelper; - public function __construct(Connection $connection, AsyncHelper $asyncHelper, Events $events, string $query) + public function __construct( + AsyncHelper $asyncHelper, + Connection $connection, + ResultBuilder $resultBuilder, + Events $events, + string $query, + ) { - parent::__construct($connection, $events, $query); + parent::__construct($connection, $resultBuilder, $events, $query); $this->asyncHelper = $asyncHelper; } @@ -45,7 +51,7 @@ public function executeArgs(array $params): AsyncQuery $this->events->onQuery($query, NULL, $statementName); } - return $this->asyncHelper->createAndSetAsyncQuery($query, $statementName); + return $this->asyncHelper->createAndSetAsyncQuery($this->resultBuilder, $query, $statementName); } diff --git a/src/Db/AsyncQuery.php b/src/Db/AsyncQuery.php index 21c284a..9b2e75a 100644 --- a/src/Db/AsyncQuery.php +++ b/src/Db/AsyncQuery.php @@ -6,6 +6,8 @@ class AsyncQuery { private Connection $connection; + private ResultBuilder $resultBuilder; + private AsyncHelper $asyncHelper; private Query $query; @@ -15,12 +17,14 @@ class AsyncQuery public function __construct( Connection $connection, + ResultBuilder $resultBuilder, AsyncHelper $asyncHelper, Query $query, string|NULL $preparedStatementName = NULL, ) { $this->connection = $connection; + $this->resultBuilder = $resultBuilder; $this->asyncHelper = $asyncHelper; $this->query = $query; $this->preparedStatementName = $preparedStatementName; @@ -67,7 +71,7 @@ public function getNextResult(): Result } } - return $this->connection->createResult($resource, $this->getQuery()); + return $this->resultBuilder->buildResult($resource, $this->getQuery()); } } diff --git a/src/Db/Connection.php b/src/Db/Connection.php index 3c389db..d93967f 100644 --- a/src/Db/Connection.php +++ b/src/Db/Connection.php @@ -20,11 +20,7 @@ class Connection private Events $events; - private RowFactory|NULL $defaultRowFactory = NULL; - - private DataTypeParser|NULL $dataTypeParser = NULL; - - private DataTypeCache|NULL $dataTypeCache = NULL; + private ResultBuilder $resultBuilder; private Transaction|NULL $transaction = NULL; @@ -54,6 +50,7 @@ public function __construct( $this->asyncHelper = new AsyncHelper($this); $this->events = new Events($this); + $this->resultBuilder = new ResultBuilder($this, $this->events); } @@ -239,59 +236,47 @@ public function close(): static } - public function setDefaultRowFactory(RowFactory $rowFactory): static + public function setResultFactory(ResultFactory $resultObjectFactory): static { - $this->defaultRowFactory = $rowFactory; + $this->resultBuilder->setResultFactory($resultObjectFactory); return $this; } - private function getDefaultRowFactory(): RowFactory + public function setRowFactory(RowFactory $rowFactory): static { - if ($this->defaultRowFactory === NULL) { - $this->defaultRowFactory = new RowFactories\Basic(); - } + $this->resultBuilder->setRowFactory($rowFactory); - return $this->defaultRowFactory; + return $this; } - public function setDataTypeParser(DataTypeParser $dataTypeParser): static + /** + * @deprecated Use setRowFactory() method. + */ + public function setDefaultRowFactory(RowFactory $rowFactory): static { - $this->dataTypeParser = $dataTypeParser; - - return $this; + return $this->setRowFactory($rowFactory); } - private function getDataTypeParser(): DataTypeParser + public function setDataTypeParser(DataTypeParser $dataTypeParser): static { - if ($this->dataTypeParser === NULL) { - $this->dataTypeParser = new DataTypeParsers\Basic(); - } + $this->resultBuilder->setDataTypeParser($dataTypeParser); - return $this->dataTypeParser; + return $this; } public function setDataTypeCache(DataTypeCache $dataTypeCache): static { - $this->dataTypeCache = $dataTypeCache; + $this->resultBuilder->setDataTypeCache($dataTypeCache); return $this; } - /** - * @return array|NULL - */ - private function getDataTypesCache(): array|NULL - { - return $this->dataTypeCache?->load($this) ?? NULL; - } - - /** * @throws Exceptions\ConnectionException * @throws Exceptions\QueryException @@ -329,26 +314,7 @@ public function queryArgs(string|Query|Sql\Query $sql, array $params): Result $this->events->onQuery($query, \hrtime(TRUE) - $startTime); } - return $this->createResult($resource, $query); - } - - - /** - * @internal - */ - public function createResult(PgSql\Result $resource, Query $query): Result - { - $result = new Result( - $resource, - $query, - $this->getDefaultRowFactory(), - $this->getDataTypeParser(), - $this->getDataTypesCache(), - ); - - $this->events->onResult($result); - - return $result; + return $this->resultBuilder->buildResult($resource, $query); } @@ -410,7 +376,7 @@ public function asyncQueryArgs(string|Query|Sql\Query $sql, array $params): Asyn $this->events->onQuery($query); } - return $this->asyncHelper->createAndSetAsyncQuery($query); + return $this->asyncHelper->createAndSetAsyncQuery($this->resultBuilder, $query); } @@ -480,13 +446,13 @@ public function cancelAsyncQuery(): static public function prepareStatement(string $sql): PreparedStatement { - return new PreparedStatement($this, $this->events, $this->prepareQuery($sql)); + return new PreparedStatement($this, $this->resultBuilder, $this->events, $this->prepareQuery($sql)); } public function asyncPrepareStatement(string $sql): AsyncPreparedStatement { - return new AsyncPreparedStatement($this, $this->asyncHelper, $this->events, $this->prepareQuery($sql)); + return new AsyncPreparedStatement($this->asyncHelper, $this, $this->resultBuilder, $this->events, $this->prepareQuery($sql)); } diff --git a/src/Db/PreparedStatement.php b/src/Db/PreparedStatement.php index 8d5481c..67cbed5 100644 --- a/src/Db/PreparedStatement.php +++ b/src/Db/PreparedStatement.php @@ -34,7 +34,7 @@ public function executeArgs(array $params): Result $this->events->onQuery($query, \hrtime(TRUE) - $startTime, $statementName); } - return $this->connection->createResult($resource, $query); + return $this->resultBuilder->buildResult($resource, $query); } diff --git a/src/Db/PreparedStatementHelper.php b/src/Db/PreparedStatementHelper.php index 6dea1f3..db7c6f7 100644 --- a/src/Db/PreparedStatementHelper.php +++ b/src/Db/PreparedStatementHelper.php @@ -8,6 +8,8 @@ abstract class PreparedStatementHelper protected Connection $connection; + protected ResultBuilder $resultBuilder; + protected Events $events; protected string $query; @@ -15,9 +17,10 @@ abstract class PreparedStatementHelper protected string|NULL $statementName = NULL; - public function __construct(Connection $connection, Events $events, string $query) + public function __construct(Connection $connection, ResultBuilder $resultBuilder, Events $events, string $query) { $this->connection = $connection; + $this->resultBuilder = $resultBuilder; $this->events = $events; $this->query = $query; } diff --git a/src/Db/Result.php b/src/Db/Result.php index 23b0e1d..9f3e94e 100644 --- a/src/Db/Result.php +++ b/src/Db/Result.php @@ -88,10 +88,10 @@ public function setColumnsFetchMutator(array $columnsFetchMutator): static /** * @deprecated Use fetchIterator() method. */ - public function getIterator(): ResultIterator + public function getIterator(): RowIterator { \trigger_error('Use fetchIterator() method.', \E_USER_DEPRECATED); - return new ResultIterator($this); + return new RowIterator($this); } @@ -372,11 +372,11 @@ public function fetchPairs(string|NULL $key = NULL, string|NULL $value = NULL): /** - * @return ResultIterator + * @return RowIterator */ - public function fetchIterator(): ResultIterator + public function fetchIterator(): RowIterator { - return new ResultIterator($this); + return new RowIterator($this); } diff --git a/src/Db/ResultBuilder.php b/src/Db/ResultBuilder.php new file mode 100644 index 0000000..3d37673 --- /dev/null +++ b/src/Db/ResultBuilder.php @@ -0,0 +1,109 @@ +connection = $connection; + $this->events = $events; + } + + + public function buildResult(PgSql\Result $resource, Query $query): Result + { + $result = $this->getResultFactory()->createResult( + $resource, + $query, + $this->getRowFactory(), + $this->getDataTypeParser(), + $this->getDataTypesCache(), + ); + + $this->events->onResult($result); + + return $result; + } + + + public function setResultFactory(ResultFactory $resultFactory): static + { + $this->resultFactory = $resultFactory; + + return $this; + } + + + private function getResultFactory(): ResultFactory + { + if ($this->resultFactory === NULL) { + $this->resultFactory = new ResultFactories\Basic(); + } + + return $this->resultFactory; + } + + + public function setRowFactory(RowFactory $rowFactory): void + { + $this->rowFactory = $rowFactory; + } + + + private function getRowFactory(): RowFactory + { + if ($this->rowFactory === NULL) { + $this->rowFactory = new RowFactories\Basic(); + } + + return $this->rowFactory; + } + + + public function setDataTypeParser(DataTypeParser $dataTypeParser): void + { + $this->dataTypeParser = $dataTypeParser; + } + + + private function getDataTypeParser(): DataTypeParser + { + if ($this->dataTypeParser === NULL) { + $this->dataTypeParser = new DataTypeParsers\Basic(); + } + + return $this->dataTypeParser; + } + + + public function setDataTypeCache(DataTypeCache $dataTypeCache): void + { + $this->dataTypeCache = $dataTypeCache; + } + + + /** + * @return array|NULL + */ + private function getDataTypesCache(): array|NULL + { + return $this->dataTypeCache?->load($this->connection) ?? NULL; + } + +} diff --git a/src/Db/ResultFactories/Basic.php b/src/Db/ResultFactories/Basic.php new file mode 100644 index 0000000..67f2a57 --- /dev/null +++ b/src/Db/ResultFactories/Basic.php @@ -0,0 +1,25 @@ +|NULL $dataTypesCache + */ + public function create( + PgSql\Result $queryResource, + Db\Query $query, + Db\RowFactory $rowFactory, + Db\DataTypeParser $dataTypeParser, + array|NULL $dataTypesCache, + ): Db\Result + { + return new Db\Result($queryResource, $query, $rowFactory, $dataTypeParser, $dataTypesCache); + } + +} diff --git a/src/Db/ResultFactory.php b/src/Db/ResultFactory.php new file mode 100644 index 0000000..2c8b68a --- /dev/null +++ b/src/Db/ResultFactory.php @@ -0,0 +1,21 @@ +|NULL $dataTypesCache + */ + function createResult( + PgSql\Result $queryResource, + Query $query, + RowFactory $rowFactory, + DataTypeParser $dataTypeParser, + array|NULL $dataTypesCache, + ): Result; + +} diff --git a/src/Db/ResultIterator.php b/src/Db/RowIterator.php similarity index 94% rename from src/Db/ResultIterator.php rename to src/Db/RowIterator.php index a994d48..71ce43d 100644 --- a/src/Db/ResultIterator.php +++ b/src/Db/RowIterator.php @@ -5,7 +5,7 @@ /** * @implements \Iterator */ -class ResultIterator implements \Iterator +class RowIterator implements \Iterator { private Result $result; diff --git a/src/Fluent/QueryExecute.php b/src/Fluent/QueryExecute.php index 942d920..8e969d9 100644 --- a/src/Fluent/QueryExecute.php +++ b/src/Fluent/QueryExecute.php @@ -151,7 +151,7 @@ public function count(): int * @throws Exceptions\QueryException * @throws Exceptions\QueryBuilderException */ - public function getIterator(): Db\ResultIterator + public function getIterator(): Db\RowIterator { \trigger_error('Use fetchIterator() method.', \E_USER_DEPRECATED); return $this->execute()->getIterator(); @@ -235,15 +235,15 @@ public function fetchPairs(string|NULL $key = NULL, string|NULL $value = NULL): /** - * @return Db\ResultIterator + * @return Db\RowIterator * @throws Db\Exceptions\ConnectionException * @throws Db\Exceptions\QueryException * @throws Exceptions\QueryException * @throws Exceptions\QueryBuilderException */ - public function fetchIterator(): Db\ResultIterator + public function fetchIterator(): Db\RowIterator { - /** @phpstan-var Db\ResultIterator */ + /** @phpstan-var Db\RowIterator */ return $this->execute()->fetchIterator(); } diff --git a/tests/Integration/FetchTest.php b/tests/Integration/FetchTest.php index 7572545..17dac12 100644 --- a/tests/Integration/FetchTest.php +++ b/tests/Integration/FetchTest.php @@ -695,7 +695,7 @@ public function testResultHasRows(): void public function testCustomRowFactoryOnConnection(): void { - $this->connection->setDefaultRowFactory($this->createCustomRowFactory()); + $this->connection->setRowFactory($this->createCustomRowFactory()); $this->connection->query(' CREATE TABLE test(