develop #2

Merged
aldarien merged 9 commits from develop into release 2025-09-30 17:34:08 -03:00
29 changed files with 555 additions and 178 deletions
Showing only changes of commit 65c6654b4f - Show all commits

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
**/.idea/
**/.cache/
**/.phpunit.cache/

View File

@ -10,14 +10,24 @@
],
"require": {
"php": ">=8",
"ext-pdo": "*"
"ext-pdo": "*",
"provm/query_builder": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"ProVM\\": "src/"
"Database\\": "src/"
}
},
"config": {
"sort-packages": true
},
"repositories": [
{
"type": "vcs",
"url": "https://git.provm.cl/ProVM/query_builder.git"
}
]
}

View File

@ -1,28 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".cache/test-results"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
forceCoversAnnotation="false"
beStrictAboutCoversAnnotation="true"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
convertDeprecationsToExceptions="true"
displayDetailsOnPhpunitDeprecations="true"
colors="true"
failOnPhpunitDeprecation="true"
failOnRisky="true"
failOnWarning="true"
verbose="true">
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage cacheDirectory=".cache/code-coverage"
processUncoveredFiles="true">
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory suffix=".php">src</directory>
<directory>src</directory>
</include>
</coverage>
</source>
</phpunit>

View File

@ -1,15 +0,0 @@
<?php
namespace ProVM\Concept\Database;
use ProVM\Concept\Database;
interface Connection
{
public function connect(): \PDO;
public function transaction(): Transaction;
public function query(string $query): ResultSet;
public function prepare(string $query): ResultSet;
public function execute(string $query, ?array $data = null): ResultSet;
}

View File

@ -1,86 +0,0 @@
<?php
namespace ProVM\Database;
use PDO;
use ProVM\Concept\Database;
use ProVM\Exception\Database\InvalidQuery;
class Connection implements Database\Connection
{
public function __construct(Database $database)
{
$this->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
{
$statement = $this->connect()->query($query);
if ($statement === false) {
throw new InvalidQuery($query);
}
return new ResultSet($statement);
}
public function prepare(string $query): Database\ResultSet
{
$statement = $this->connect()->prepare($query);
if ($statement === false) {
throw new InvalidQuery($query);
}
return new ResultSet($statement);
}
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);
}
public function fetchOne(string $query, ?array $data = null): array
{
return $this->execute($query, $data)->fetchFirst();
}
public function fetchMany(string $query, ?array $data = null): array
{
return $this->execute($query, $data)->fetchAll();
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace ProVM\Database;
use PDO;
use ProVM\Concept;
class Transaction implements Concept\Database\Transaction
{
public function __construct(Concept\Database\Connection $connection)
{
$this->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();
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace ProVM\Concept;
namespace Database\Define;
interface Database
{
@ -9,10 +9,10 @@ interface Database
public function getUser(): string;
public function getPassword(): string;
public function needsUser(): bool;
public function setHost(string $host): Database;
public function setPort(int $port): Database;
public function setName(string $name): Database;
public function setUser(string $username): Database;
public function setPassword(string $password): Database;
public function setHost(string $host): self;
public function setPort(int $port): self;
public function setName(string $name): self;
public function setUser(string $username): self;
public function setPassword(string $password): self;
public function getDsn(): string;
}

View File

@ -0,0 +1,43 @@
<?php
namespace Database\Define\Database;
use PDO;
use PDOException;
use Database\Define\Query\Builder;
use Database\Exception\Database\InvalidQuery;
interface Connection
{
/**
* @return self
* @throws PDOException
*/
public function connect(): self;
public function getPDO(): PDO;
public function queryBuilder(): Builder;
public function transaction(): Transaction;
/**
* @param string $query
* @return ResultSet
* @throws InvalidQuery
*/
public function query(string $query): ResultSet;
/**
* @param string $query
* @return ResultSet
* @throws InvalidQuery
*/
public function prepare(string $query): ResultSet;
/**
* @param string $query
* @param array|null $data
* @return ResultSet
* @throws InvalidQuery
*/
public function execute(string $query, ?array $data = null): ResultSet;
}

View File

@ -1,9 +1,12 @@
<?php
namespace ProVM\Concept\Database;
namespace Database\Define\Database;
use PDOStatement;
interface ResultSet
{
public function execute(array $data): ResultSet;
public function getStatement(): PDOStatement;
public function execute(array $data): self;
public function fetchFirst(): array;
public function fetchAll(): array;

View File

@ -1,5 +1,5 @@
<?php
namespace ProVM\Concept\Database;
namespace Database\Define\Database;
interface Transaction
{

View File

@ -1,12 +1,12 @@
<?php
namespace ProVM\Exception;
namespace Database\Exception;
use Exception;
use Throwable;
abstract class Database extends Exception
{
const BASE_CODE = 600;
const BASE_CODE = 1000;
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{

View File

@ -1,8 +1,8 @@
<?php
namespace ProVM\Exception\Database;
namespace Database\Exception\Database;
use Throwable;
use ProVM\Exception\Database;
use Database\Exception\Database;
class BlankResult extends Database
{

View File

@ -1,8 +1,8 @@
<?php
namespace ProVM\Exception\Database;
namespace Database\Exception\Database;
use Throwable;
use ProVM\Exception;
use Database\Exception;
class InvalidQuery extends Exception\Database
{

View File

@ -1,9 +1,9 @@
<?php
namespace ProVM\Implement;
namespace Database\Ideal;
use ProVM\Concept;
use Database\Define;
abstract class Database implements Concept\Database
abstract class Database implements Define\Database
{
protected string $host;
protected int $port;
@ -32,27 +32,27 @@ abstract class Database implements Concept\Database
return $this->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;

View File

@ -0,0 +1,121 @@
<?php
namespace Database\Implement;
use PDO;
use PDOException;
use Database\Define;
use Database\Exception\Database\InvalidQuery;
class Connection implements Define\Database\Connection
{
public function __construct(Define\Database $database)
{
$this->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();
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace ProVM\Database;
namespace Database\Implement;
use ProVM\Implement\Database;
use Database\Ideal\Database;
class MySQL extends Database
{

View File

@ -1,7 +1,7 @@
<?php
namespace ProVM\Database;
namespace Database\Implement;
use ProVM\Implement\Database;
use Database\Ideal\Database;
class PostgreSQL extends Database
{

View File

@ -1,12 +1,12 @@
<?php
namespace ProVM\Database;
namespace Database\Implement;
use PDO;
use PDOStatement;
use ProVM\Concept\Database;
use ProVM\Exception\Database\BlankResult;
use Database\Define;
use Database\Exception\Database\BlankResult;
class ResultSet implements Database\ResultSet
class ResultSet implements Define\Database\ResultSet
{
public function __construct(PDOStatement $statement)
{
@ -15,22 +15,26 @@ class ResultSet implements Database\ResultSet
protected PDOStatement $statement;
protected function getStatement(): PDOStatement
public function getStatement(): PDOStatement
{
return $this->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);

View File

@ -1,7 +1,7 @@
<?php
namespace ProVM\Database;
namespace Database\Implement;
use ProVM\Implement\Database;
use Database\Ideal\Database;
class SQLite extends Database
{

View File

@ -0,0 +1,39 @@
<?php
namespace Database\Implement;
use Database\Define;
class Transaction implements Define\Database\Transaction
{
public function __construct(Define\Database\Connection $connection)
{
$this->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();
}
}

View File

@ -1,65 +1,61 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\Connection;
use ProVM\Concept;
use Database\Implement\Connection;
use Database\Define;
class ConnectionTest extends TestCase
{
protected string $host = '/tmp/test.db3';
protected string $tableName = 'test_table';
protected PDO $pdo;
protected function setUp(): void
{
$this->pdo = new PDO('sqlite::memory:');
$query = "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, test TEXT)";
$this->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()
{
$host = "memory";
$database = $this->getMockBuilder(Concept\Database::class)->getMock();
$database->method('getHost')->willReturn($host);
$database->method('getDsn')->willReturn("sqlite::{$host}");
$database->method('needsUser')->willReturn(false);
$connection = new Connection($database);
$this->assertEquals($this->pdo, $connection->connect());
$connection = $this->getConnection();
$this->assertEquals($this->pdo, $connection->getPDO());
}
public function testQuery()
{
$host = "memory";
$database = $this->getMockBuilder(Concept\Database::class)->getMock();
$database->method('getHost')->willReturn($host);
$database->method('getDsn')->willReturn("sqlite::{$host}");
$database->method('needsUser')->willReturn(false);
$connection = new Connection($database);
$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";
$result = $connection->query($query);
$this->assertInstanceOf(Concept\Database\ResultSet::class, $result);
$connection->query($query);
$this->assertTrue(true);
}
public function testPrepare()
{
$host = "memory";
$database = $this->getMockBuilder(Concept\Database::class)->getMock();
$database->method('getHost')->willReturn($host);
$database->method('getDsn')->willReturn("sqlite::{$host}");
$database->method('needsUser')->willReturn(false);
$connection = new Connection($database);
$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";
$result = $connection->prepare($query);
$this->assertInstanceOf(Concept\Database\ResultSet::class, $result);
$connection->prepare($query);
$this->assertTrue(true);
}
}

View File

@ -1,6 +1,6 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\MySQL;
use Database\Implement\MySQL;
class MySQLTest extends TestCase
{

View File

@ -1,6 +1,6 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\PostgreSQL;
use Database\Implement\PostgreSQL;
class PostgreSQLTest extends TestCase
{

View File

@ -1,7 +1,7 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\ResultSet;
use Database\Implement\ResultSet;
class ResultSetTest extends TestCase
{

View File

@ -1,6 +1,6 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\SQLite;
use Database\Implement\SQLite;
class SQLiteTest extends TestCase
{

View File

@ -1,12 +1,12 @@
<?php
use PHPUnit\Framework\TestCase;
use ProVM\Database\Transaction;
use Database\Implement\Transaction;
class TransactionTest extends TestCase
{
public function testTransaction()
{
$connection = $this->createMock(ProVM\Concept\Database\Connection::class);
$connection = $this->createMock(Database\Define\Database\Connection::class);
$transaction = new Transaction($connection);
$transaction->begin();
$this->assertTrue(true);