From d46a4b8f8d44026b67390ed44a24bd304ee1dfe1 Mon Sep 17 00:00:00 2001 From: Murilo Elias Date: Sat, 5 Oct 2024 13:55:33 -0300 Subject: [PATCH] [update] - updated conditionals, added group and custom operations --- .env.example | 6 +-- README.md | 10 ++-- composer.json | 2 +- maestro.php | 4 +- src/Abstract/Query.php | 16 +++++-- src/Core/Manager.php | 2 +- src/Core/Synchronizer.php | 2 +- src/Database/Insert.php | 4 +- src/Database/PDOConnection.php | 46 +++++++++++++++---- src/Database/Select.php | 13 ++++-- src/Database/Transaction.php | 2 +- src/Database/Update.php | 20 ++++++-- .../{IntegrationTest.php => .gitkeep} | 0 tests/unit/ManagerTest.php | 2 +- 14 files changed, 88 insertions(+), 41 deletions(-) rename tests/integration/{IntegrationTest.php => .gitkeep} (100%) diff --git a/.env.example b/.env.example index bb88285..b457a13 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ DB_SQL=pgsql -DB_HOST=localhost +DB_HOST=127.0.0.3 DB_PORT=5432 -DB_NAME=maestrodb +DB_NAME=maestro_db DB_USER=postgres -DB_PASS=dbpass \ No newline at end of file +DB_PASS=password diff --git a/README.md b/README.md index 381961c..182a03e 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ The `Select` class allows you to build and execute SELECT queries. use Ilias\Maestro\Database\Select; use Ilias\Maestro\Database\PDOConnection; -$select = new Select(PDOConnection::getInstance()); +$select = new Select(PDOConnection::get()); $select->from(['u' => 'users'], ['u.id', 'u.name']) ->where(['u.active' => true]) ->order('u.name', 'ASC') @@ -111,7 +111,7 @@ use Maestro\Example\User; $user = new User('John Doe', 'john@example.com', md5('password'), true, new Timestamp('now')); -$insert = new Insert(PDOConnection::getInstance()); +$insert = new Insert(PDOConnection::get()); $insert->into(User::class) ->values($user) ->returning(['id']); @@ -128,7 +128,7 @@ The `Update` class allows you to build and execute UPDATE queries. use Ilias\Maestro\Database\Update; use Ilias\Maestro\Database\PDOConnection; -$update = new Update(PDOConnection::getInstance()); +$update = new Update(PDOConnection::get()); $update->table('users') ->set('name', 'Jane Doe') ->where(['id' => 1]); @@ -145,7 +145,7 @@ The `Delete` class allows you to build and execute DELETE queries. use Ilias\Maestro\Database\Delete; use Ilias\Maestro\Database\PDOConnection; -$delete = new Delete(PDOConnection::getInstance()); +$delete = new Delete(PDOConnection::get()); $delete->from('users') ->where(['id' => 1]); @@ -241,7 +241,7 @@ use Maestro\Example\Hr; use PDO; // Initialize PDO with environment variables -$pdo = PDOConnection::getInstance(); +$pdo = PDOConnection::get(); // Initialize DatabaseManager $dbManager = new DatabaseManager($pdo); diff --git a/composer.json b/composer.json index 2d16060..2558cc4 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ilias/maestro", "type": "library", - "version": "1.2.3", + "version": "1.2.4", "description": "A PHP object-oriented postgres database manager", "keywords": [ "postgres", diff --git a/maestro.php b/maestro.php index 0aede5c..fdef15e 100644 --- a/maestro.php +++ b/maestro.php @@ -20,11 +20,11 @@ // print implode("\n", $coreDatabase->createDatabase($agrofastDB, true)) . "\n"; -$insert = new Insert(Maestro::SQL_NO_PREDICT, PDOConnection::getInstance()); +$insert = new Insert(Maestro::SQL_NO_PREDICT, PDOConnection::get()); $user = new User("nickname'-- drop table", 'John', 'Doe', 'email@example.com', 'password', true, new Timestamp()); $result = $insert->into($user)->values($user)->returning(['id'])->execute(); var_dump($result); -$delete = new Delete(Maestro::SQL_NO_PREDICT, PDOConnection::getInstance()); +$delete = new Delete(Maestro::SQL_NO_PREDICT, PDOConnection::get()); $delete->from($user)->where(['id' => $result[0]['id']])->execute(); diff --git a/src/Abstract/Query.php b/src/Abstract/Query.php index 569623f..c7b428b 100644 --- a/src/Abstract/Query.php +++ b/src/Abstract/Query.php @@ -16,6 +16,8 @@ abstract class Query private ?PDOStatement $stmt = null; private bool $isBinded = false; protected string $query = ''; + public const AND = 'AND'; + public const OR = 'OR'; public function __construct( protected string $behavior = Maestro::SQL_STRICT, @@ -30,19 +32,23 @@ public function __construct( * @param array $conditions An associative array of conditions for the WHERE clause. * @return $this Returns the current instance for method chaining. */ - public function where(array $conditions): static + public function where(array $conditions, string $operation = Select::AND, bool $group = false): static { + $where = []; foreach ($conditions as $column => $value) { $columnWhere = Utils::sanitizeForPostgres($column); $paramName = str_replace('.', '_', ":where_{$columnWhere}"); $this->storeParameter($paramName, $value); - $this->where[] = "{$column} = {$paramName}"; + $where[] = "{$column} = {$paramName}"; } + $clauses = implode(" {$operation} ", $where); + $this->where[] = ($group ? "({$clauses})" : $clauses); return $this; } - public function in(array $conditions): static + public function in(array $conditions, string $operation = Select::AND, bool $group = false): static { + $where = []; foreach ($conditions as $column => $value) { $inParams = array_map(function ($v, $k) use ($column) { $columnIn = Utils::sanitizeForPostgres($column); @@ -51,8 +57,10 @@ public function in(array $conditions): static return $paramName; }, $value, array_keys($value)); $inList = implode(",", $inParams); - $this->where[] = "{$column} IN({$inList})"; + $where[] = "{$column} IN({$inList})"; } + $clauses = implode(" {$operation} ", $where); + $this->where[] = ($group ? "({$clauses})" : $clauses); return $this; } diff --git a/src/Core/Manager.php b/src/Core/Manager.php index f4a3765..943e8a7 100644 --- a/src/Core/Manager.php +++ b/src/Core/Manager.php @@ -34,7 +34,7 @@ class Manager public function __construct( private string $strictType = Maestro::SQL_STRICT ) { - $this->pdo = PDOConnection::getInstance(); + $this->pdo = PDOConnection::get(); } /** diff --git a/src/Core/Synchronizer.php b/src/Core/Synchronizer.php index 0786351..375ddf3 100644 --- a/src/Core/Synchronizer.php +++ b/src/Core/Synchronizer.php @@ -14,7 +14,7 @@ class Synchronizer public function __construct() { $this->manager = new Manager(); - $this->pdo = PDOConnection::getInstance(); + $this->pdo = PDOConnection::get(); } public function synchronize(Database $ormDb): void diff --git a/src/Database/Insert.php b/src/Database/Insert.php index c62375f..861d319 100644 --- a/src/Database/Insert.php +++ b/src/Database/Insert.php @@ -42,7 +42,7 @@ public function values(Table|array $data): Insert * @param array $conditions An associative array of conditions for the WHERE clause. * @return Insert Returns the current Insert instance. */ - public function where(array $conditions): static + public function where(array $conditions, string $operation = \Ilias\Maestro\Database\Select::AND, bool $group = false): static { return $this; } @@ -53,7 +53,7 @@ public function where(array $conditions): static * @param array $conditions An associative array of conditions for the WHERE clause. * @return Insert Returns the current Insert instance. */ - public function in(array $conditions): static + public function in(array $conditions, string $operation = \Ilias\Maestro\Database\Select::AND, bool $group = false): static { return $this; } diff --git a/src/Database/PDOConnection.php b/src/Database/PDOConnection.php index 151bcc9..e1e0f6e 100644 --- a/src/Database/PDOConnection.php +++ b/src/Database/PDOConnection.php @@ -20,25 +20,51 @@ public function __wakeup() { } - public static function getInstance(?\PDO $pdoMock = null): \PDO + /** + * Retrieves a PDO connection instance. + * + * This method returns a singleton instance of the PDO connection. If the connection has not been established yet, it will create a new one using the provided parameters or environment variables. If a PDO mock object is provided, it will be used instead. + * + * @param string|null $dbSql The SQL driver (e.g., mysql, pgsql). Defaults to environment variable DB_SQL. + * @param string|null $dbName The name of the database. Defaults to environment variable DB_NAME. + * @param string|null $dbHost The database host. Defaults to environment variable DB_HOST. + * @param string|null $dbPort The database port. Defaults to environment variable DB_PORT. + * @param string|null $dbUser The database user. Defaults to environment variable DB_USER. + * @param string|null $dbPass The database password. Defaults to environment variable DB_PASS. + * @param \PDO|null $pdoMock An optional PDO mock object for testing purposes. + * @return \PDO The PDO connection instance. + */ + public static function get(string $dbSql = null, string $dbName = null, string $dbHost = null, string $dbPort = null, string $dbUser = null, string $dbPass = null, ?\PDO $pdoMock = null): \PDO { if (self::$pdo === null) { if ($pdoMock) { self::$pdo = $pdoMock; } else { - $sqlDatabase = Helper::env("DB_SQL"); - $host = Helper::env("DB_HOST"); - $port = Helper::env("DB_PORT"); - $databaseName = Helper::env("DB_NAME"); - $username = Helper::env("DB_USER"); - $password = Helper::env("DB_PASS"); - - $dns = "{$sqlDatabase}:host={$host};port={$port};dbname={$databaseName}"; - self::$pdo = new \PDO($dns, $username, $password); + $dbSql = Helper::env("DB_SQL", $dbSql); + $dbName = Helper::env("DB_NAME", $dbName); + $dbHost = Helper::env("DB_HOST", $dbHost); + $dbPort = Helper::env("DB_PORT", $dbPort); + $dbUser = Helper::env("DB_USER", $dbUser); + $dbPass = Helper::env("DB_PASS", $dbPass); + + $dns = "{$dbSql}:host={$dbHost};port={$dbPort};dbname={$dbName}"; + self::$pdo = new \PDO($dns, $dbUser, $dbPass); self::$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } } return self::$pdo; } + + /** + * Get an instance of the PDO connection. + * + * @param \PDO|null $pdoMock Optional PDO mock object for testing. + * @return \PDO The PDO connection instance. + * @deprecated This method is deprecated and will be removed in a future version. Use self::get() directly instead. + */ + public static function getInstance(?\PDO $pdoMock = null): \PDO + { + return self::get(pdoMock: $pdoMock); + } } diff --git a/src/Database/Select.php b/src/Database/Select.php index 0841bda..17be8fc 100644 --- a/src/Database/Select.php +++ b/src/Database/Select.php @@ -12,8 +12,8 @@ class Select extends Query const INNER = 'INNER'; const LEFT = 'LEFT'; const RIGHT = 'RIGHT'; - const ORDER_ASC = 'ASC'; - const ORDER_DESC = 'DESC'; + const ASC = 'ASC'; + const DESC = 'DESC'; private string $from; private ?string $alias; @@ -93,18 +93,21 @@ public function group(array $columns): Select return $this; } - public function having(array $conditions): Select + public function having(array $conditions, string $operation = Select::AND, $group = false): Select { + $having = []; foreach ($conditions as $column => $value) { $columnHaving = Utils::sanitizeForPostgres($column); $paramName = ":having_{$columnHaving}"; $this->storeParameter($paramName, $value); - $this->having[] = "{$column} = {$paramName}"; + $having[] = "{$column} = {$paramName}"; } + $clauses = implode(" {$operation} ", $having); + $this->having[] = ($group ? "({$clauses})" : $clauses); return $this; } - public function order(string $column, string $direction = 'ASC'): Select + public function order(string $column, string $direction = Select::ASC): Select { $this->order[] = "{$column} {$direction}"; return $this; diff --git a/src/Database/Transaction.php b/src/Database/Transaction.php index 0b4f7b0..c9a32f9 100644 --- a/src/Database/Transaction.php +++ b/src/Database/Transaction.php @@ -12,7 +12,7 @@ class Transaction public function __construct(?PDO $pdo = null) { if (empty($pdo)) { - $pdo = PDOConnection::getInstance(); + $pdo = PDOConnection::get(); } $this->pdo = $pdo; } diff --git a/src/Database/Update.php b/src/Database/Update.php index 3682247..75f6550 100644 --- a/src/Database/Update.php +++ b/src/Database/Update.php @@ -16,12 +16,22 @@ public function table(string $table): Update return $this; } - public function set(string $column, $value): Update + public function set(string|array $column, $value = null): Update { - $column = Utils::sanitizeForPostgres($column); - $paramName = ":$column"; - $this->set[$column] = $paramName; - $this->parameters[$paramName] = $value; + if (is_array($column)) { + foreach ($column as $col => $val) { + $this->set($col, $val); + } + } + if (is_string($column)) { + if (is_int($column) || is_numeric($column)) { + throw new \InvalidArgumentException("Column name must be a string"); + } + $column = Utils::sanitizeForPostgres($column); + $paramName = ":$column"; + $this->set[$column] = $paramName; + $this->parameters[$paramName] = $value; + } return $this; } diff --git a/tests/integration/IntegrationTest.php b/tests/integration/.gitkeep similarity index 100% rename from tests/integration/IntegrationTest.php rename to tests/integration/.gitkeep diff --git a/tests/unit/ManagerTest.php b/tests/unit/ManagerTest.php index dbb7530..616b649 100644 --- a/tests/unit/ManagerTest.php +++ b/tests/unit/ManagerTest.php @@ -15,7 +15,7 @@ class ManagerTest extends TestCase protected function setUp(): void { $this->pdoMock = $this->createMock(\PDO::class); - PDOConnection::getInstance($this->pdoMock); + PDOConnection::get(pdoMock: $this->pdoMock); $this->manager = new Manager(); }