diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5657f6e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c534789 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2023 Sergey Zatulivetrov + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d053b94 --- /dev/null +++ b/README.md @@ -0,0 +1,204 @@ +# Менеджер для работы с БД + +Достаточно простой в использовании и легко расширяется для создания адаптеров подключения. В данный момент описаны адаптеры, которые работают с PDO, MySQLi и SQLite3. + +## Установка в проект + +Выполните команду в консоли: + +```ssh +composer require remils/database +``` + +## Менеджер БД + +Инициализируйте класс менеджера в нужном вам месте, либо добавьте его в контейнер зависимостей. + +```php +setConnect('default', new Connect('sqlite:test.db')); + +$connect = $manager->getConnect('default'); + +$connect->execute(<<id; + } + + public function getName(): string + { + return $this->name; + } +} + +class UserRepository extends AbstractRepository +{ + public function getConnectName(): string + { + return 'default'; + } + + public function getTableName(): string + { + return 'users'; + } + + public function getEntityClassName(): string + { + return User::class; + } +} + +$userRepository = new UserRepository($manager); + +$user = $userRepository->insert([ + 'name' => 'Иван', +]); + +var_dump($user); + +$userRepository->update([ + 'name' => 'Василий', +], [ + 'id' => $user->getId(), +]); + +$user = $userRepository->first([ + 'id' => $user->getId(), +]); + +var_dump($user); + +$userRepository->delete([ + 'id' => $user->getId(), +]); +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6070326 --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "remils/database", + "type": "library", + "version": "1.0.0", + "description": "Менеджер баз данных", + "keywords": [ + "database", + "pdo", + "sqlite", + "mysqli" + ], + "autoload": { + "psr-4": { + "Remils\\Database\\": "src/" + } + }, + "authors": [ + { + "name": "Sergey Zatulivetrov", + "email": "remils@mail.ru" + } + ], + "homepage": "https://github.com/remils/database", + "license": "MIT", + "require": { + "php": "^8.1" + }, + "require-dev": { + "squizlabs/php_codesniffer": "3.*", + "phpstan/phpstan": "^1.10" + }, + "scripts": { + "analyse": [ + "vendor/bin/phpcs -d memory_limit=256M --standard=PSR12 src --colors -p", + "vendor/bin/phpstan analyse -l 7 src" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2acc49e --- /dev/null +++ b/composer.lock @@ -0,0 +1,140 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "61b869486d2a8bbcf4f7350e384f87c7", + "packages": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "1.10.27", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "a9f44dcea06f59d1363b100bb29f297b311fa640" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a9f44dcea06f59d1363b100bb29f297b311fa640", + "reference": "a9f44dcea06f59d1363b100bb29f297b311fa640", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-08-05T09:57:55+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2023-02-22T23:07:41+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/src/AbstractRepository.php b/src/AbstractRepository.php new file mode 100644 index 0000000..e670c46 --- /dev/null +++ b/src/AbstractRepository.php @@ -0,0 +1,258 @@ +connect = $manager->getConnect($this->getConnectName()); + } + + /** + * @inheritDoc + */ + public function getConnect(): ConnectContract + { + return $this->connect; + } + + /** + * Возвращает все записи + * + * @return array + */ + public function all(): array + { + $sql = sprintf( + 'SELECT * FROM `%s`;', + $this->getTableName(), + ); + + return $this->getConnect() + ->execute($sql) + ->fetchAllObject($this->getEntityClassName()); + } + + /** + * Возвращает первую запись, либо первую подходящую запись по учетным данным + * + * @param array $credentials + * @return object|null + */ + public function first(array $credentials = []): ?object + { + $sql = sprintf( + 'SELECT * FROM `%s` /* CREDENTIALS */ LIMIT 1;', + $this->getTableName(), + ); + + if ($credentials) { + $sql = strtr($sql, [ + '/* CREDENTIALS */' => 'WHERE ' . implode(' AND ', $this->prepareColumns($credentials)), + ]); + } + + $statement = $this->getConnect()->prepare($sql); + + $this->setParameters($statement, $credentials); + + return $statement->execute() + ->fetchObject($this->getEntityClassName()); + } + + /** + * Постраничный вывод записей + * + * @param integer $offset + * @param integer $limit + * @return array + */ + public function paginate(int $offset, int $limit = 15): array + { + $sql = sprintf( + 'SELECT * FROM `%s` LIMIT :limit OFFSET :offset;', + $this->getTableName(), + ); + + return $this->getConnect() + ->prepare($sql) + ->setParameter('offset', $offset, ParameterType::INTEGER) + ->setParameter('limit', $limit, ParameterType::INTEGER) + ->execute() + ->fetchAllObject($this->getEntityClassName()); + } + + /** + * Возвращает количество записей в таблице + * + * @return integer + */ + public function count(): int + { + $sql = sprintf( + 'SELECT COUNT(*) FROM `%s`;', + $this->getTableName(), + ); + + return $this->getConnect() + ->execute($sql) + ->fetchColumn(0); + } + + /** + * Вставка записи + * + * @param array $data + * @return object + */ + public function insert(array $data): object + { + if (!$data) { + throw new RepositoryException('Нет данных для вставки.'); + } + + $keys = array_keys($data); + $columns = array_map(fn (string $key) => sprintf('`%s`', $key), $keys); + $params = array_map(fn (string $key) => sprintf(':%s', $key), $keys); + + $sql = sprintf( + 'INSERT INTO `%s` (%s) VALUES (%s) RETURNING *;', + $this->getTableName(), + implode(', ', $columns), + implode(', ', $params), + ); + + $statement = $this->getConnect()->prepare($sql); + + $this->setParameters($statement, $data); + + return $statement->execute() + ->fetchObject($this->getEntityClassName()); + } + + /** + * Удаляет все записи, либо записи которые удовлетворяют учетным данным + * + * @param array $credentials + * @return void + */ + public function delete(array $credentials = []): void + { + if ($credentials) { + $sql = sprintf( + 'DELETE FROM `%s` WHERE %s;', + $this->getTableName(), + implode(' AND ', $this->prepareColumns($credentials)), + ); + } else { + $sql = sprintf( + 'DELETE FROM `%s`;', + $this->getTableName(), + ); + } + + $statement = $this->connect->prepare($sql); + + $this->setParameters($statement, $credentials); + + $statement->execute(); + } + + /** + * Обновляет записи, либо записи которые удовлетворяют учетным данным + * + * @param array $data + * @param array $credentials + * @return void + */ + public function update(array $data, array $credentials = []): void + { + if (!$data) { + throw new RepositoryException('Нет данных для обновления.'); + } + + $sql = sprintf( + 'UPDATE `%s` SET %s /* CREDENTIALS */;', + $this->getTableName(), + implode(', ', $this->prepareColumns($data)), + ); + + if ($credentials) { + $sql = strtr($sql, [ + '/* CREDENTIALS */' => 'WHERE ' . implode(' AND ', $this->prepareColumns($credentials)), + ]); + } + + $statement = $this->getConnect()->prepare($sql); + + $this->setParameters($statement, $data, $credentials); + + $statement->execute(); + } + + /** + * Возвращает подготовленый набор колонок с параметрами + * + * @param array $data + * @return array + */ + protected function prepareColumns(array $data): array + { + return array_map( + fn (string $key) => sprintf( + '`%s` = :%s', + $key, + $key + ), + array_keys($data) + ); + } + + /** + * Устанавливает параметры в подготовительный запрос + * + * @param StatementContract $statement + * @param array ...$params + * @return void + */ + protected function setParameters(StatementContract $statement, ...$params): void + { + $params = array_merge(...$params); + + foreach ($params as $key => $value) { + $type = match (gettype($value)) { + 'integer' => ParameterType::INTEGER, + 'double' => ParameterType::FLOAT, + 'string' => ParameterType::STRING, + 'boolean' => ParameterType::BOOLEAN, + 'NULL' => ParameterType::NULL, + 'array' => ParameterType::JSON, + default => ParameterType::STRING, + }; + + $statement->setParameter($key, $value, $type); + } + } +} diff --git a/src/Contract/ConnectContract.php b/src/Contract/ConnectContract.php new file mode 100644 index 0000000..c8fb1ff --- /dev/null +++ b/src/Contract/ConnectContract.php @@ -0,0 +1,52 @@ +|null + */ + public function fetch(): ?array; + + /** + * Выбирает строку из набора результатов в виде объекта + * + * @param string $className Имя объекта + * @return object|null + */ + public function fetchObject(string $className = stdClass::class): ?object; + + /** + * Выбирает все строки из результирующего набора и помещает их в ассоциативный массив + * + * @return array> + */ + public function fetchAll(): array; + + /** + * Выбирает все строки из результирующего набора и помещает их в объект + * + * @param string $className Имя объекта + * @return array + */ + public function fetchAllObject(string $className = stdClass::class): array; + + /** + * Получает один столбец из строки набора результатов + * + * @param integer $column Номер колонки (начиная с 0) + * @return mixed + */ + public function fetchColumn(int $column = 0): mixed; + + /** + * Получает все строки, в виде массива, содержащие значения столбца + * + * @param integer $column Номер колонки (начиная с 0) + * @return array + */ + public function fetchAllColumn(int $column = 0): array; +} diff --git a/src/Contract/StatementContract.php b/src/Contract/StatementContract.php new file mode 100644 index 0000000..95bb606 --- /dev/null +++ b/src/Contract/StatementContract.php @@ -0,0 +1,34 @@ + */ + protected array $connections = []; + + /** + * Добавляет подключение в менеджер + * + * @param string $name Имя подключения + * @param ConnectContract $connect Объект подключения + * @return Manager Менеджер подключений + */ + public function setConnect(string $name, ConnectContract $connect): Manager + { + if (array_key_exists($name, $this->connections)) { + throw new ConnectAlreadyExistsException($name); + } + + $this->connections[$name] = $connect; + + return $this; + } + + /** + * Возвращает объект подключения из менеджера + * + * @param string $name Имя подключения + * @return ConnectContract Объект подключения + */ + public function getConnect(string $name): ConnectContract + { + if (array_key_exists($name, $this->connections)) { + return $this->connections[$name]; + } + + throw new ConnectNotFoundException($name); + } +} diff --git a/src/MySQLi/Connect.php b/src/MySQLi/Connect.php new file mode 100644 index 0000000..7a8c4ab --- /dev/null +++ b/src/MySQLi/Connect.php @@ -0,0 +1,92 @@ +connect = new mysqli($host, $username, $password, $database, $port, $socket); + } + + /** + * @inheritDoc + */ + public function customizer(callable $callback): void + { + call_user_func($callback, $this->connect); + } + + /** + * @inheritDoc + */ + public function transaction(Closure $closure): mixed + { + try { + $this->connect->begin_transaction(); + + $result = $closure->call($this); + + $this->connect->commit(); + + return $result; + } catch (Throwable $exception) { + $this->connect->rollback(); + + throw $exception; + } + } + + /** + * @inheritDoc + */ + public function lastInsertId(): mixed + { + return $this->connect->insert_id; + } + + /** + * @inheritDoc + */ + public function prepare(string $sql): StatementContract + { + $statement = $this->connect->prepare($sql); + + return new Statement($statement); + } + + /** + * @inheritDoc + */ + public function execute(string $sql): ResultContract + { + $query = $this->connect->query($sql); + + return new Result($query); + } +} diff --git a/src/MySQLi/Result.php b/src/MySQLi/Result.php new file mode 100644 index 0000000..658d89b --- /dev/null +++ b/src/MySQLi/Result.php @@ -0,0 +1,101 @@ +query instanceof mysqli_result) { + return $this->query->fetch_array(MYSQLI_ASSOC); + } + + return null; + } + + /** + * @inheritDoc + */ + public function fetchObject(string $className = stdClass::class): ?object + { + if ($this->query instanceof mysqli_result) { + return $this->query->fetch_object($className); + } + + return null; + } + + /** + * @inheritDoc + */ + public function fetchAll(): array + { + if ($this->query instanceof mysqli_result) { + return $this->query->fetch_all(MYSQLI_ASSOC); + } + + return []; + } + + /** + * @inheritDoc + */ + public function fetchAllObject(string $className = stdClass::class): array + { + if ($this->query instanceof mysqli_result) { + $items = []; + + while ($item = $this->fetchObject($className)) { + $items[] = $item; + } + + return $items; + } + + return []; + } + + /** + * @inheritDoc + */ + public function fetchColumn(int $column = 0): mixed + { + if ($this->query instanceof mysqli_result) { + return $this->query->fetch_column($column); + } + + return null; + } + + /** + * @inheritDoc + */ + public function fetchAllColumn(int $column = 0): array + { + if ($this->query instanceof mysqli_result) { + $items = []; + + while ($item = $this->fetchColumn($column)) { + $items[] = $item; + } + + return $items; + } + + return []; + } +} diff --git a/src/MySQLi/Statement.php b/src/MySQLi/Statement.php new file mode 100644 index 0000000..43e62d1 --- /dev/null +++ b/src/MySQLi/Statement.php @@ -0,0 +1,84 @@ + + */ + protected array $params = []; + + public function __construct( + protected mysqli_stmt|bool $statement, + ) { + } + + public function __destruct() + { + unset($this->statement); + } + + /** + * @inheritDoc + */ + public function setParameter( + string $key, + mixed $value, + ParameterType $type = ParameterType::STRING + ): StatementContract { + switch ($type) { + case ParameterType::INTEGER: + $this->types .= 'i'; + break; + case ParameterType::FLOAT: + $this->types .= 'd'; + break; + case ParameterType::STRING: + $this->types .= 's'; + break; + case ParameterType::BOOLEAN: + $this->types .= 'i'; + break; + case ParameterType::BLOB: + $this->types .= 'b'; + break; + case ParameterType::NULL: + $this->types .= 's'; + break; + case ParameterType::JSON: + $this->types .= 's'; + $value = json_encode($value); + break; + } + + $this->params[] = $value; + + return $this; + } + + /** + * @inheritDoc + */ + public function execute(): ResultContract + { + if ($this->statement instanceof mysqli_stmt) { + $this->statement->bind_param($this->types, ...$this->params); + $this->statement->execute(); + $result = $this->statement->get_result(); + + return new Result($result); + } + + return new Result(false); + } +} diff --git a/src/PDO/Connect.php b/src/PDO/Connect.php new file mode 100644 index 0000000..f72de30 --- /dev/null +++ b/src/PDO/Connect.php @@ -0,0 +1,94 @@ +|null $options + */ + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + $this->connect = new PDO($dsn, $username, $password, $options); + } + + /** + * @inheritDoc + */ + public function customizer(callable $callback): void + { + call_user_func($callback, $this->connect); + } + + /** + * @inheritDoc + */ + public function transaction(Closure $closure): mixed + { + try { + $this->connect->beginTransaction(); + + $result = $closure->call($this); + + $this->connect->commit(); + + return $result; + } catch (Throwable $exception) { + $this->connect->rollBack(); + + throw $exception; + } + } + + /** + * @inheritDoc + * @param string|null $name Имя объекта последовательности, который должен выдать ID + */ + public function lastInsertId(string $name = null): mixed + { + return $this->connect->lastInsertId($name); + } + + /** + * @inheritDoc + */ + public function prepare(string $sql): StatementContract + { + $statement = $this->connect->prepare($sql); + + if ($statement) { + return new Statement($statement); + } + + throw new DatabaseException('Ошибка PDO.'); + } + + /** + * @inheritDoc + */ + public function execute(string $sql): ResultContract + { + $query = $this->connect->query($sql); + + if ($query && $query->execute()) { + return new Result($query); + } + + throw new DatabaseException('Ошибка PDO.'); + } +} diff --git a/src/PDO/Result.php b/src/PDO/Result.php new file mode 100644 index 0000000..d13c312 --- /dev/null +++ b/src/PDO/Result.php @@ -0,0 +1,80 @@ +query->setFetchMode(PDO::FETCH_ASSOC); + + return $this->query->fetch() ?: null; + } + + /** + * @inheritDoc + */ + public function fetchObject(string $className = stdClass::class): ?object + { + $this->query->setFetchMode(PDO::FETCH_CLASS, $className, null); + + return $this->query->fetch() ?: null; + } + + /** + * @inheritDoc + */ + public function fetchAll(): array + { + $this->query->setFetchMode(PDO::FETCH_ASSOC); + + return $this->query->fetchAll(); + } + + /** + * @inheritDoc + */ + public function fetchAllObject(string $className = stdClass::class): array + { + $this->query->setFetchMode(PDO::FETCH_CLASS, $className, null); + + return $this->query->fetchAll(); + } + + /** + * @inheritDoc + */ + public function fetchColumn(int $column = 0): mixed + { + return $this->query->fetchColumn($column); + } + + /** + * @inheritDoc + */ + public function fetchAllColumn(int $column = 0): array + { + $items = []; + + while ($item = $this->fetchColumn($column)) { + $items[] = $item; + } + + return $items; + } +} diff --git a/src/PDO/Statement.php b/src/PDO/Statement.php new file mode 100644 index 0000000..ad36905 --- /dev/null +++ b/src/PDO/Statement.php @@ -0,0 +1,68 @@ +statement->bindParam($key, $value, PDO::PARAM_INT); + break; + case ParameterType::FLOAT: + $this->statement->bindParam($key, $value, PDO::PARAM_STR); + break; + case ParameterType::STRING: + $this->statement->bindParam($key, $value, PDO::PARAM_STR); + break; + case ParameterType::BOOLEAN: + $this->statement->bindParam($key, $value, PDO::PARAM_BOOL); + break; + case ParameterType::BLOB: + $this->statement->bindParam($key, $value, PDO::PARAM_LOB); + break; + case ParameterType::NULL: + $this->statement->bindParam($key, $value, PDO::PARAM_NULL); + break; + case ParameterType::JSON: + $json = json_encode($value); + $this->statement->bindParam($key, $json, PDO::PARAM_STR); + break; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function execute(): ResultContract + { + if ($this->statement->execute()) { + return new Result($this->statement); + } + + throw new DatabaseException('Ошибка PDO.'); + } +} diff --git a/src/SQLite3/Connect.php b/src/SQLite3/Connect.php new file mode 100644 index 0000000..2351b18 --- /dev/null +++ b/src/SQLite3/Connect.php @@ -0,0 +1,95 @@ +connect = new SQLite3($filename, $flags, $encryptionKey); + } + + /** + * @inheritDoc + */ + public function customizer(callable $callback): void + { + call_user_func($callback, $this->connect); + } + + /** + * @inheritDoc + */ + public function transaction(Closure $closure): mixed + { + try { + $this->connect->exec('BEGIN;'); + + $result = $closure->call($this); + + $this->connect->exec('COMMIT;'); + + return $result; + } catch (Throwable $exception) { + $this->connect->exec('ROLLBACK;'); + + throw $exception; + } + } + + /** + * @inheritDoc + */ + public function lastInsertId(): mixed + { + return $this->connect->lastInsertRowID(); + } + + /** + * @inheritDoc + */ + public function prepare(string $sql): StatementContract + { + $statement = $this->connect->prepare($sql); + + if ($statement) { + return new Statement($statement); + } + + throw new DatabaseException('Ошибка SQLite3.'); + } + + /** + * @inheritDoc + */ + public function execute(string $sql): ResultContract + { + $query = $this->connect->query($sql); + + if ($query) { + return new Result($query); + } + + throw new DatabaseException('Ошибка SQLite3.'); + } +} diff --git a/src/SQLite3/Result.php b/src/SQLite3/Result.php new file mode 100644 index 0000000..e121af1 --- /dev/null +++ b/src/SQLite3/Result.php @@ -0,0 +1,117 @@ +query->fetchArray(SQLITE3_ASSOC) ?: null; + } + + /** + * @inheritDoc + */ + public function fetchObject(string $className = stdClass::class): ?object + { + if ($item = $this->fetch()) { + $object = new $className(); + + $reflectionClass = new ReflectionClass($object); + + foreach ($item as $key => $value) { + if ($reflectionClass->hasProperty($key)) { + $reflectionClass->getProperty($key)->setValue($object, $value); + } else { + $object->{$key} = $value; + } + + unset($key, $value); + } + + unset($reflectionClass, $item); + + return $object; + } + + return null; + } + + /** + * @inheritDoc + */ + public function fetchAll(): array + { + $items = []; + + while ($item = $this->fetch()) { + $items[] = $item; + + unset($item); + } + + return $items; + } + + /** + * @inheritDoc + */ + public function fetchAllObject(string $className = stdClass::class): array + { + $items = []; + + while ($item = $this->fetchObject($className)) { + $items[] = $item; + + unset($item); + } + + return $items; + } + + /** + * @inheritDoc + */ + public function fetchColumn(int $column = 0): mixed + { + if ($item = $this->query->fetchArray(SQLITE3_NUM)) { + if (!array_key_exists($column, $item)) { + throw new ValueError('Invalid column index'); + } + + return $item[$column]; + } + + return null; + } + + /** + * @inheritDoc + */ + public function fetchAllColumn(int $column = 0): array + { + $items = []; + + while ($item = $this->fetchColumn($column)) { + $items[] = $item; + } + + return $items; + } +} diff --git a/src/SQLite3/Statement.php b/src/SQLite3/Statement.php new file mode 100644 index 0000000..5e159a4 --- /dev/null +++ b/src/SQLite3/Statement.php @@ -0,0 +1,69 @@ +statement->bindParam($key, $value, SQLITE3_INTEGER); + break; + case ParameterType::FLOAT: + $this->statement->bindParam($key, $value, SQLITE3_FLOAT); + break; + case ParameterType::STRING: + $this->statement->bindParam($key, $value, SQLITE3_TEXT); + break; + case ParameterType::BOOLEAN: + $this->statement->bindParam($key, $value, SQLITE3_INTEGER); + break; + case ParameterType::BLOB: + $this->statement->bindParam($key, $value, SQLITE3_BLOB); + break; + case ParameterType::NULL: + $this->statement->bindParam($key, $value, SQLITE3_NULL); + break; + case ParameterType::JSON: + $json = json_encode($value); + $this->statement->bindParam($key, $json, SQLITE3_TEXT); + break; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function execute(): ResultContract + { + $query = $this->statement->execute(); + + if ($query) { + return new Result($query); + } + + throw new DatabaseException('Ошибка SQLite3.'); + } +}