diff --git a/.gitignore b/.gitignore index eed7ff6..7ce6e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ # PHPStorm **/.idea/ + +**/.cache/ +**/.phpunit.cache/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3e14266 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM composer:lts as deps +WORKDIR /app +RUN --mount=type=bind,source=./composer.json,target=composer.json \ + --mount=type=bind,source=./composer.lock,target=composer.lock \ + --mount=type=cache,target=/tmp/cache \ + composer install --no-interaction + +FROM php:8-cli as base +WORKDIR /app +RUN apt-get update && \ + apt-get install -yq --no-install-recommends libsqlite3-dev && \ + rm -rf /var/lib/apt/lists/* && \ + docker-php-ext-install pdo pdo_sqlite +COPY ./src /app/src + +FROM base as dev +COPY ./tests /app/tests +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" +COPY --from=deps /app/vendor/ /app/vendor + +FROM dev as test +ENTRYPOINT [ "./vendor/bin/phpunit" ] diff --git a/Readme.md b/Readme.md index f2c9f89..1cd1ae4 100644 --- a/Readme.md +++ b/Readme.md @@ -21,7 +21,7 @@ Database Abstraction Layer ... { "type": "git", - "path": "https://git.provm.cl/ProVM/database.git" + "url": "https://git.provm.cl/ProVM/database.git" } ... ], diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..97f3091 --- /dev/null +++ b/compose.yml @@ -0,0 +1,5 @@ +services: + database: + build: . + volumes: + - ./:/app diff --git a/composer.json b/composer.json index 85a3f71..0252a19 100644 --- a/composer.json +++ b/composer.json @@ -1,23 +1,33 @@ { - "name": "provm/database", - "type": "library", - "authors": [ - { - "name": "Aldarien", - "email": "aldarien85@gmail.com" - } - ], - "require": { - "php": ">=8", - "ext-pdo": "*" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "kint-php/kint": "^5.0" - }, - "autoload": { - "psr-4": { - "ProVM\\": "src/" - } + "name": "provm/database", + "type": "library", + "version": "1.2.0", + "authors": [ + { + "name": "Aldarien", + "email": "aldarien85@gmail.com" } + ], + "require": { + "php": ">=8", + "ext-pdo": "*", + "provm/query_builder": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "autoload": { + "psr-4": { + "Database\\": "src/" + } + }, + "config": { + "sort-packages": true + }, + "repositories": [ + { + "type": "vcs", + "url": "https://git.provm.cl/ProVM/query_builder.git" + } + ] } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d19d6d9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Concept/Database/Connection.php b/src/Concept/Database/Connection.php deleted file mode 100644 index 705400d..0000000 --- a/src/Concept/Database/Connection.php +++ /dev/null @@ -1,15 +0,0 @@ -setDatabase($database); - } - - protected Database $database; - - protected function getDatabase(): Database - { - return $this->database; - } - - protected function setDatabase(Database $database): Database\Connection - { - $this->database = $database; - return $this; - } - - protected PDO $pdo; - public function connect(): PDO - { - if (!isset($this->pdo)) { - $dsn = $this->getDatabase()->getDsn(); - if ($this->getDatabase()->needsUser()) { - $this->pdo = new PDO($dsn, $this->getDatabase()->getUser(), $this->getDatabase()->getPassword()); - } else { - $this->pdo = new PDO($dsn); - } - } - return $this->pdo; - } - - protected Database\Transaction $transaction; - - public function transaction(): Database\Transaction - { - if (!isset($this->transaction)) { - $this->transaction = new Transaction($this); - } - return $this->transaction; - } - - public function query(string $query): Database\ResultSet - { - return new ResultSet($this->connect()->query($query)); - } - public function prepare(string $query): Database\ResultSet - { - return new ResultSet($this->connect()->prepare($query)); - } - public function execute(string $query, ?array $data = null): Database\ResultSet - { - if ($data !== null) { - $rs = $this->prepare($query); - $rs->execute($data); - return $rs; - } - return $this->query($query); - } -} diff --git a/src/Database/Transaction.php b/src/Database/Transaction.php deleted file mode 100644 index 3181903..0000000 --- a/src/Database/Transaction.php +++ /dev/null @@ -1,40 +0,0 @@ -setConnection($connection); - } - - protected Concept\Database\Connection $connection; - - public function getConnection(): Concept\Database\Connection - { - return $this->connection; - } - - public function setConnection(Concept\Database\Connection $connection): Concept\Database\Transaction - { - $this->connection = $connection; - return $this; - } - - public function begin(): Concept\Database\Transaction - { - $this->getConnection()->connect()->beginTransaction(); - return $this; - } - public function commit(): void - { - $this->getConnection()->connect()->commit(); - } - public function rollBack(): void - { - $this->getConnection()->connect()->rollBack(); - } -} diff --git a/src/Concept/Database.php b/src/Define/Database.php similarity index 50% rename from src/Concept/Database.php rename to src/Define/Database.php index bc9f2a9..75f9804 100644 --- a/src/Concept/Database.php +++ b/src/Define/Database.php @@ -1,5 +1,5 @@ password; } - public function setHost(string $host): Concept\Database + public function setHost(string $host): Define\Database { $this->host = $host; return $this; } - public function setPort(int $port): Concept\Database + public function setPort(int $port): Define\Database { $this->port = $port; return $this; } - public function setName(string $name): Concept\Database + public function setName(string $name): Define\Database { $this->name = $name; return $this; } - public function setUser(string $username): Concept\Database + public function setUser(string $username): Define\Database { $this->user = $username; return $this; } - public function setPassword(string $password): Concept\Database + public function setPassword(string $password): Define\Database { $this->password = $password; return $this; diff --git a/src/Implement/Connection.php b/src/Implement/Connection.php new file mode 100644 index 0000000..959269b --- /dev/null +++ b/src/Implement/Connection.php @@ -0,0 +1,121 @@ +setDatabase($database); + } + + protected Define\Database $database; + + protected function getDatabase(): Define\Database + { + return $this->database; + } + + protected function setDatabase(Define\Database $database): self + { + $this->database = $database; + return $this; + } + protected Define\Query\Builder $builder; + public function queryBuilder(): Define\Query\Builder + { + return $this->builder; + } + public function setBuilder(Define\Query\Builder $builder): self + { + $this->builder = $builder; + return $this; + } + + protected PDO $pdo; + public function connect(): self + { + if (!isset($this->pdo)) { + $dsn = $this->getDatabase()->getDsn(); + if ($this->getDatabase()->needsUser()) { + $this->pdo = new PDO($dsn, $this->getDatabase()->getUser(), $this->getDatabase()->getPassword()); + } else { + $this->pdo = new PDO($dsn); + } + } + return $this; + } + public function getPDO(): PDO + { + return $this->connect()->pdo; + } + + protected Define\Database\Transaction $transaction; + + public function transaction(): Define\Database\Transaction + { + if (!isset($this->transaction)) { + $this->transaction = new Transaction($this); + } + return $this->transaction; + } + + public function query(string $query): Define\Database\ResultSet + { + try { + $statement = $this->getPDO()->query($query); + } catch (PDOException $exception) { + throw new InvalidQuery($query, $exception); + } + if ($statement === false) { + throw new InvalidQuery($query); + } + return new ResultSet($statement); + } + public function prepare(string $query): Define\Database\ResultSet + { + try { + $statement = $this->getPDO()->prepare($query); + } catch (PDOException $exception) { + throw new InvalidQuery($query, $exception); + } + if ($statement === false) { + throw new InvalidQuery($query); + } + return new ResultSet($statement); + } + public function execute(string $query, ?array $data = null): Define\Database\ResultSet + { + if ($data !== null) { + return $this->prepare($query) + ->execute($data); + } + return $this->query($query); + } + + /** + * @param string $query + * @param array|null $data + * @return array + * @throws InvalidQuery + */ + public function fetchOne(string $query, ?array $data = null): array + { + return $this->execute($query, $data)->fetchFirst(); + } + + /** + * @param string $query + * @param array|null $data + * @return array + * @throws InvalidQuery + */ + public function fetchMany(string $query, ?array $data = null): array + { + return $this->execute($query, $data)->fetchAll(); + } +} diff --git a/src/Database/MySQL.php b/src/Implement/MySQL.php similarity index 86% rename from src/Database/MySQL.php rename to src/Implement/MySQL.php index a4e009f..546366c 100644 --- a/src/Database/MySQL.php +++ b/src/Implement/MySQL.php @@ -1,7 +1,7 @@ statement; } - protected function setStatement(PDOStatement $statement): ResultSet + protected function setStatement(PDOStatement $statement): self { $this->statement = $statement; return $this; } - public function execute(array $data): Database\ResultSet + public function execute(array $data): self { $this->statement->execute($data); return $this; } + /** + * @return PDOStatement + * @throws BlankResult + */ protected function checkResults(): PDOStatement { if ($this->getStatement()->rowCount() === 0) { @@ -38,18 +42,38 @@ class ResultSet implements Database\ResultSet } return $this->getStatement(); } + + /** + * @return array + * @throws BlankResult + */ public function fetchFirst(): array { return $this->checkResults()->fetch(PDO::FETCH_ASSOC); } + + /** + * @return array + * @throws BlankResult + */ public function fetchAll(): array { return $this->checkResults()->fetchAll(PDO::FETCH_ASSOC); } + + /** + * @return object + * @throws BlankResult + */ public function fetchFirstAsObject(): object { return $this->checkResults()->fetch(PDO::FETCH_OBJ); } + + /** + * @return array + * @throws BlankResult + */ public function fetchAllAsObjects(): array { return $this->checkResults()->fetchAll(PDO::FETCH_OBJ); diff --git a/src/Database/SQLite.php b/src/Implement/SQLite.php similarity index 69% rename from src/Database/SQLite.php rename to src/Implement/SQLite.php index f8ab299..e43481c 100644 --- a/src/Database/SQLite.php +++ b/src/Implement/SQLite.php @@ -1,7 +1,7 @@ setConnection($connection); + } + + protected Define\Database\Connection $connection; + + public function getConnection(): Define\Database\Connection + { + return $this->connection; + } + + public function setConnection(Define\Database\Connection $connection): self + { + $this->connection = $connection; + return $this; + } + + public function begin(): self + { + $this->getConnection()->getPDO()->beginTransaction(); + return $this; + } + public function commit(): void + { + $this->getConnection()->getPDO()->commit(); + } + public function rollBack(): void + { + $this->getConnection()->getPDO()->rollBack(); + } +} diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php new file mode 100644 index 0000000..9286f22 --- /dev/null +++ b/tests/ConnectionTest.php @@ -0,0 +1,61 @@ +pdo = new PDO("sqlite:{$this->host}"); + $query = "CREATE TABLE IF NOT EXISTS {$this->tableName} (id INTEGER PRIMARY KEY, test TEXT)"; + $this->pdo->query($query); + $query = "INSERT INTO {$this->tableName} (test) VALUES ('test')"; + $this->pdo->query($query); + } + protected function tearDown(): void + { + unset($this->pdo); + unlink($this->host); + } + + protected function getConnection(): Connection + { + $host = $this->host; + + $database = $this->getMockBuilder(Define\Database::class)->getMock(); + $database->method('getHost')->willReturn($host); + $database->method('getDsn')->willReturn("sqlite:{$host}"); + $database->method('needsUser')->willReturn(false); + + return new Connection($database); + } + + public function testConnection() + { + $connection = $this->getConnection(); + $this->assertEquals($this->pdo, $connection->getPDO()); + } + public function testQuery() + { + $connection = $this->getConnection(); + $query = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, test TEXT)"; + $connection->query($query); + $query = "SELECT * FROM test_table"; + $connection->query($query); + $this->assertTrue(true); + } + public function testPrepare() + { + $connection = $this->getConnection(); + $query = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, test TEXT)"; + $connection->query($query); + $query = "SELECT * FROM test_table"; + $connection->prepare($query); + $this->assertTrue(true); + } +} diff --git a/tests/MySQLTest.php b/tests/MySQLTest.php new file mode 100644 index 0000000..cce4c82 --- /dev/null +++ b/tests/MySQLTest.php @@ -0,0 +1,32 @@ +setHost($host); + $database->setPort($port); + $database->setName($name); + $database->setUser($user); + $database->setPassword($pass); + + $this->assertEquals($host, $database->getHost()); + $this->assertEquals($port, $database->getPort()); + $this->assertEquals($name, $database->getName()); + $this->assertEquals($user, $database->getUser()); + $this->assertEquals($pass, $database->getPassword()); + $this->assertTrue($database->needsUser()); + $this->assertEquals($dsn, $database->getDsn()); + } +} diff --git a/tests/PostgreSQLTest.php b/tests/PostgreSQLTest.php new file mode 100644 index 0000000..21b26c3 --- /dev/null +++ b/tests/PostgreSQLTest.php @@ -0,0 +1,32 @@ +setHost($host); + $database->setPort($port); + $database->setName($name); + $database->setUser($user); + $database->setPassword($pass); + + $this->assertEquals($host, $database->getHost()); + $this->assertEquals($port, $database->getPort()); + $this->assertEquals($name, $database->getName()); + $this->assertEquals($user, $database->getUser()); + $this->assertEquals($pass, $database->getPassword()); + $this->assertFalse($database->needsUser()); + $this->assertEquals($dsn, $database->getDsn()); + } +} diff --git a/tests/ResultSetTest.php b/tests/ResultSetTest.php new file mode 100644 index 0000000..a01c98a --- /dev/null +++ b/tests/ResultSetTest.php @@ -0,0 +1,26 @@ +getMockBuilder(PDOStatement::class)->getMock(); + $statement->method('execute')->willReturn(true); + $statement->method('fetch')->willReturn($result1); + $statement->method('fetchAll')->willReturn($result2); + $statement->method('rowCount')->willReturn(2); + + $resultSet = new ResultSet($statement); + + $resultSet->execute(['foo' => 'bar']); + $this->assertTrue(true); + $this->assertEquals($result1, $resultSet->fetchFirst()); + $this->assertEquals($result2, $resultSet->fetchAll()); + } +} diff --git a/tests/SQLiteTest.php b/tests/SQLiteTest.php new file mode 100644 index 0000000..f4f5cb6 --- /dev/null +++ b/tests/SQLiteTest.php @@ -0,0 +1,20 @@ +setHost($host); + + $this->assertEquals($host, $database->getHost()); + $this->assertFalse($database->needsUser()); + $this->assertEquals($dsn, $database->getDsn()); + } +} diff --git a/tests/TransactionTest.php b/tests/TransactionTest.php new file mode 100644 index 0000000..e5265bd --- /dev/null +++ b/tests/TransactionTest.php @@ -0,0 +1,19 @@ +createMock(Database\Define\Database\Connection::class); + $transaction = new Transaction($connection); + $transaction->begin(); + $this->assertTrue(true); + $transaction->commit(); + $this->assertTrue(true); + $transaction->begin(); + $transaction->rollback(); + $this->assertTrue(true); + } +}