24 Commits

Author SHA1 Message Date
Gregory Letellier
293eb9e555 cast extended models when loading from database 2025-01-09 12:00:25 +01:00
Gregory Letellier
d961d2a80f ajout model de type readonly pour une requête de select uniquement 2024-06-18 13:02:18 +02:00
Gregory Letellier
0db845a116 add orWhere 2023-11-15 11:50:15 +01:00
Gregory Letellier
35188860ce gestion du charset en mysql 2023-11-14 16:41:09 +01:00
Gregory Letellier
c8d6e911a6 meilleure gestion des noms de tables sur les modèles hérités 2023-11-14 15:55:08 +01:00
Gregory Letellier
03d26d9167 fix bug in array host reading 2023-11-14 15:33:19 +01:00
Gregory Letellier
737090c6a8 suppression mode prepare emulated 2023-11-14 12:38:51 +01:00
Gregory Letellier
43729579fa Ajout jsonserializable 2023-11-13 16:16:12 +01:00
Gregory Letellier
bdb0a88e58 add static method to get inherited class querybuilder from model, add isset method in model 2023-11-13 09:01:51 +01:00
Gregory Letellier
523d307f81 beautify et ajout export type connection 2023-11-07 10:45:54 +01:00
Gregory Letellier
fd54281ce3 la méthode find de l'objet retourne l'objet lui même 2023-11-07 10:36:01 +01:00
Gregory Letellier
97c8a8a92c ajout possibilité de choisir le fetch mode 2023-11-07 10:17:45 +01:00
Gregory Letellier
80a611d5ac add getPdo method 2023-11-07 10:09:54 +01:00
Gregory Letellier
9db5e9b2bd ajout des transactions 2023-11-06 10:17:40 +01:00
39eb008118 ajout méthode find dans le model 2023-11-04 09:43:58 +01:00
aa551c697b beautify code, ajout méthode pluck et toArray à l'objet collection 2023-11-04 09:32:51 +01:00
Gregory Letellier
ff515202c9 ajout composant vardumper pour avoir le dd 2023-11-03 09:44:42 +01:00
Gregory Letellier
72ba193a1e code beautifier 2023-11-03 09:01:55 +01:00
Gregory Letellier
4d7f0d6d83 suppression de l'objet db dans l'objet QueryBuilder 2023-11-03 08:59:35 +01:00
Gregory Letellier
a6c51f5f17 renommage de l'objet DB en Connection, suppression de la variable privée db dans le model 2023-11-03 08:58:42 +01:00
d67916e2ff ajout mode de configuration mixte, tableau ou variable ENV 2023-11-02 23:04:35 +01:00
Gregory Letellier
84bf765b84 ajout méthodes random,index dans la collection, ajout clause distinct, whereNull , whereNotNull dans le querybuilder 2023-11-02 10:27:06 +01:00
Gregory Letellier
fe6d2f27b8 ajout retour en collection, modification des tests en conséquences 2023-10-31 10:47:11 +01:00
Gregory Letellier
42d3d38a0a ajout Countable interface 2023-10-31 10:46:49 +01:00
10 changed files with 587 additions and 156 deletions

View File

@@ -4,7 +4,8 @@
"type": "library", "type": "library",
"require": { "require": {
"vlucas/phpdotenv": "^5.5", "vlucas/phpdotenv": "^5.5",
"evenement/evenement": "^3.0" "evenement/evenement": "^3.0",
"symfony/var-dumper": "^6.3"
}, },
"require-dev": { "require-dev": {
"squizlabs/php_codesniffer": "^3.4", "squizlabs/php_codesniffer": "^3.4",

220
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7b31fc4374d54dc20d8f35625a8f74a7", "content-hash": "ef49a4d31601e74941ecce68ae0ff3ff",
"packages": [ "packages": [
{ {
"name": "evenement/evenement", "name": "evenement/evenement",
@@ -190,6 +190,73 @@
], ],
"time": "2023-02-25T19:38:58+00:00" "time": "2023-02-25T19:38:58+00:00"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.28.0", "version": "v1.28.0",
@@ -438,6 +505,90 @@
], ],
"time": "2023-01-26T09:26:14+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{
"name": "symfony/var-dumper",
"version": "v6.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "999ede244507c32b8e43aebaa10e9fce20de7c97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97",
"reference": "999ede244507c32b8e43aebaa10e9fce20de7c97",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/console": "<5.4"
},
"require-dev": {
"ext-iconv": "*",
"symfony/console": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/process": "^5.4|^6.0",
"symfony/uid": "^5.4|^6.0",
"twig/twig": "^2.13|^3.0.4"
},
"bin": [
"Resources/bin/var-dump-server"
],
"type": "library",
"autoload": {
"files": [
"Resources/functions/dump.php"
],
"psr-4": {
"Symfony\\Component\\VarDumper\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides mechanisms for walking through any arbitrary PHP variable",
"homepage": "https://symfony.com",
"keywords": [
"debug",
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v6.3.6"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-10-12T18:45:56+00:00"
},
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
"version": "v5.5.0", "version": "v5.5.0",
@@ -3653,73 +3804,6 @@
], ],
"time": "2023-08-16T10:10:12+00:00" "time": "2023-08-16T10:10:12+00:00"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
},
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.3.1", "version": "v6.3.1",

View File

@@ -16,7 +16,7 @@
</include> </include>
</coverage> </coverage>
<php> <php>
<env name="DB_TYPE" value="sqlite"/> <env name="DB_DEFAULT_TYPE" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/> <env name="DB_DEFAULT_DATABASE" value=":memory:"/>
</php> </php>
</phpunit> </phpunit>

View File

@@ -3,10 +3,12 @@
namespace Kletellier\PdoWrapper; namespace Kletellier\PdoWrapper;
use ArrayIterator; use ArrayIterator;
use Countable;
use IteratorAggregate; use IteratorAggregate;
use JsonSerializable;
use Traversable; use Traversable;
class Collection implements IteratorAggregate class Collection implements IteratorAggregate, Countable, JsonSerializable
{ {
private array $elements; private array $elements;
@@ -36,6 +38,19 @@ class Collection implements IteratorAggregate
return count($this->elements) == 0; return count($this->elements) == 0;
} }
public function index($index): mixed
{
if (isset($this->elements[$index])) {
return $this->elements[$index];
}
return null;
}
public function random(): mixed
{
return array_rand($this->elements);
}
public function reduce(callable $fn, mixed $initial): mixed public function reduce(callable $fn, mixed $initial): mixed
{ {
return array_reduce($this->elements, $fn, $initial); return array_reduce($this->elements, $fn, $initial);
@@ -43,8 +58,7 @@ class Collection implements IteratorAggregate
public function map(callable $fn): Collection public function map(callable $fn): Collection
{ {
$tmp = array_map($fn, $this->elements); return new Collection(array_map($fn, $this->elements));
return new Collection($tmp);
} }
public function each(callable $fn): void public function each(callable $fn): void
@@ -54,9 +68,24 @@ class Collection implements IteratorAggregate
public function filter(callable $fn): Collection public function filter(callable $fn): Collection
{ {
$tmp = array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH); return new Collection(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH));
$collect = new Collection($tmp); }
return $collect;
public function pluck(string $key): Collection
{
return new Collection(array_map(function ($item) use ($key) {
if (is_object($item)) {
return isset($item->$key) ? $item->$key : null;
}
if (is_array($item)) {
return isset($item[$key]) ? $item[$key] : null;
}
}, $this->elements));
}
public function toArray(): array
{
return $this->elements;
} }
public function empty(): bool public function empty(): bool
@@ -83,4 +112,9 @@ class Collection implements IteratorAggregate
{ {
return new ArrayIterator($this->elements); return new ArrayIterator($this->elements);
} }
public function jsonSerialize(): mixed
{
return $this->elements;
}
} }

View File

@@ -6,7 +6,7 @@ use Dotenv\Dotenv;
use PDO; use PDO;
use PDOStatement; use PDOStatement;
class Db class Connection
{ {
protected PDO $pdo; protected PDO $pdo;
protected string $name; protected string $name;
@@ -18,16 +18,39 @@ class Db
private string $database; private string $database;
private ?string $port; private ?string $port;
private ?string $host; private ?string $host;
private ?string $charset;
private string $error; private string $error;
private function __construct($name, $dir) private function __construct($name, $config)
{ {
$this->error = ""; $this->error = "";
$this->name = $name; $this->name = $name;
$this->loadEnv($dir); if (is_array($config)) {
$this->parseArray($config);
} else {
if (is_dir($config)) {
$this->loadEnv($config);
}
}
$this->initPdo(); $this->initPdo();
} }
private function parseArray($config)
{
try {
$key = (strtolower(trim($this->name)));
$this->type = isset($config[$key]["TYPE"]) ? $config[$key]["TYPE"] : null;
$this->user = isset($config[$key]["USER"]) ? $config[$key]["USER"] : null;
$this->host = isset($config[$key]["HOST"]) ? $config[$key]["HOST"] : null;
$this->password = isset($config[$key]["PASSWORD"]) ? $config[$key]["PASSWORD"] : null;
$this->database = isset($config[$key]["DATABASE"]) ? $config[$key]["DATABASE"] : null;
$this->port = isset($config[$key]["PORT"]) ? $config[$key]["PORT"] : null;
$this->charset = isset($config[$key]["CHARSET"]) ? $config[$key]["CHARSET"] : null;
} catch (\Throwable $th) {
$this->error = $th->getMessage();
}
}
private function loadEnv($dir) private function loadEnv($dir)
{ {
try { try {
@@ -49,6 +72,7 @@ class Db
$this->password = isset($_ENV[$this->getKey("PASSWORD", $key)]) ? $_ENV[$this->getKey("PASSWORD", $key)] : null; $this->password = isset($_ENV[$this->getKey("PASSWORD", $key)]) ? $_ENV[$this->getKey("PASSWORD", $key)] : null;
$this->database = isset($_ENV[$this->getKey("DATABASE", $key)]) ? $_ENV[$this->getKey("DATABASE", $key)] : null; $this->database = isset($_ENV[$this->getKey("DATABASE", $key)]) ? $_ENV[$this->getKey("DATABASE", $key)] : null;
$this->port = isset($_ENV[$this->getKey("PORT", $key)]) ? $_ENV[$this->getKey("PORT", $key)] : null; $this->port = isset($_ENV[$this->getKey("PORT", $key)]) ? $_ENV[$this->getKey("PORT", $key)] : null;
$this->charset = isset($_ENV[$this->getKey("CHARSET", $key)]) ? $_ENV[$this->getKey("CHARSET", $key)] : null;
} catch (\Throwable $th) { } catch (\Throwable $th) {
$this->error = $th->getMessage(); $this->error = $th->getMessage();
} }
@@ -63,6 +87,7 @@ class Db
{ {
$this->getConnString(); $this->getConnString();
$this->pdo = new PDO($this->connstring, $this->user, $this->password); $this->pdo = new PDO($this->connstring, $this->user, $this->password);
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} }
@@ -95,10 +120,19 @@ class Db
return $stmt; return $stmt;
} }
private function getMySQLOptions()
{
$ret = "";
if ($this->charset) {
$ret .= ";charset=" . $this->charset;
}
return $ret;
}
private function getConnString() private function getConnString()
{ {
$this->connstring = match ($this->type) { $this->connstring = match ($this->type) {
"mysql" => "mysql:host=" . $this->host . ":" . $this->port . ";dbname=" . $this->database, "mysql" => "mysql:host=" . $this->host . ":" . $this->port . ";dbname=" . $this->database . $this->getMySQLOptions() ,
"sqlite" => "sqlite:" . $this->database, "sqlite" => "sqlite:" . $this->database,
}; };
} }
@@ -109,13 +143,13 @@ class Db
return $stmt->execute(); return $stmt->execute();
} }
public function getSelectQuery(Query $query): mixed public function getSelectQuery(Query $query, $fetchmode = PDO::FETCH_ASSOC): mixed
{ {
$stmt = $this->pdo->prepare($query->getQuery()); $stmt = $this->pdo->prepare($query->getQuery());
$stmt = $this->bindParams($stmt, $query->getData()); $stmt = $this->bindParams($stmt, $query->getData());
EventDispatcher::emit("on.query", $query); EventDispatcher::emit("on.query", $query);
$stmt->execute(); $stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll($fetchmode);
} }
public function updateQuery(Query $query): bool public function updateQuery(Query $query): bool
@@ -139,26 +173,53 @@ class Db
} }
} }
public static function init($dir, array $connections = ["default"]) public function beginTransaction(): bool
{ {
self::fillInstances($dir, $connections); if ($this->pdo) {
return $this->pdo->beginTransaction();
}
return false;
}
public function commit(): bool
{
if ($this->pdo) {
return $this->pdo->commit();
}
return false;
}
public function rollback(): bool
{
if ($this->pdo) {
return $this->pdo->rollBack();
}
return false;
}
public static function init($config, array $connections = ["default"])
{
self::fillInstances($config, $connections);
return self::getInstance(); return self::getInstance();
} }
private static function fillInstances($dir, array $connections) private static function fillInstances($config, array $connections)
{ {
foreach ($connections as $connection) { foreach ($connections as $connection) {
if (!isset(self::$instances[$connection])) { if (!isset(self::$instances[$connection])) {
self::$instances[$connection] = new Db($connection, $dir); self::$instances[$connection] = new Connection($connection, $config);
} }
} }
} }
public static function getInstance($connection = "default") public static function getInstance($connection = "")
{ {
if ($connection == "" && count(self::$instances) > 0) {
$connection = array_keys(self::$instances)[0];
}
if (isset(self::$instances[$connection])) { if (isset(self::$instances[$connection])) {
$instance = self::$instances[$connection]; $instance = self::$instances[$connection];
if ($instance instanceof Db) { if ($instance instanceof Connection) {
return self::$instances[$connection]; return self::$instances[$connection];
} }
} }
@@ -169,4 +230,14 @@ class Db
{ {
return $this->error; return $this->error;
} }
public function getPdo()
{
return $this->pdo;
}
public function getDriverType()
{
return $this->type;
}
} }

View File

@@ -25,9 +25,21 @@ class Expression
if ($operator == "between" && count($this->value) == 2) { if ($operator == "between" && count($this->value) == 2) {
return "(" . $this->condition . " " . $this->operator . " ? AND ? )"; return "(" . $this->condition . " " . $this->operator . " ? AND ? )";
} }
if ($operator == "notnull") {
return "(" . $this->condition . " IS NOT NULL )";
}
if ($operator == "isnull") {
return "(" . $this->condition . " IS NULL )";
}
return "(" . $this->condition . " " . $this->operator . " ? )"; return "(" . $this->condition . " " . $this->operator . " ? )";
} }
public function hasData()
{
$operator = trim(strtolower($this->operator));
return (in_array($operator, ["isnull","notnull"])) ? false : true;
}
public function getCondition() public function getCondition()
{ {
return $this->condition; return $this->condition;

View File

@@ -2,25 +2,39 @@
namespace Kletellier\PdoWrapper; namespace Kletellier\PdoWrapper;
class Model use JsonSerializable;
class Model implements JsonSerializable
{ {
protected mixed $values; protected mixed $values;
protected array $updated; protected array $updated;
protected string $table; protected string $table = "";
protected string $connection = "default"; protected string $connection = "";
protected string $pk; protected string $pk;
private ?Db $db;
private bool $new; private bool $new;
private bool $ro;
public function __construct() public function __construct($ro = false)
{ {
$this->db = null;
$this->new = true; $this->new = true;
$this->values = array(); $this->values = array();
$this->updated = array(); $this->updated = array();
$this->pk = "id"; $this->pk = "id";
$this->ro = $ro;
$this->initTable();
}
private function initTable(): void
{
if (!$this->ro) {
if ($this->table == "") {
$this->table = $this->getDefaultTableName(); $this->table = $this->getDefaultTableName();
} }
} else {
$this->table = "ReadOnlyModel";
$this->pk = "__idpk";
}
}
public function getTable(): string public function getTable(): string
{ {
@@ -33,6 +47,11 @@ class Model
return $this; return $this;
} }
public function getValues(): array
{
return $this->values;
}
public function getPk(): string public function getPk(): string
{ {
return $this->pk; return $this->pk;
@@ -63,14 +82,25 @@ class Model
return $this; return $this;
} }
public function find(mixed $key): mixed
{
$qb = $this->newQueryBuilder(get_class($this));
$find = $qb->find($key);
if ($find !== null) {
$this->fillData($find->getValues());
return $this;
}
return null;
}
public function save(): bool public function save(): bool
{ {
$this->db = Db::getInstance($this->connection);
$ret = false; $ret = false;
$qb = $this->newQueryBuilder(); if (!$this->ro) {
$qb = $this->newQueryBuilder("");
if ($this->new) { if ($this->new) {
$query = $qb->getPreparedQuery($this->values); $query = $qb->getPreparedQuery($this->values);
$id = $this->db->insertQuery($query); $id = Connection::getInstance($this->connection)->insertQuery($query);
if ($id !== false) { if ($id !== false) {
$this->values[$this->pk] = $id; $this->values[$this->pk] = $id;
$this->new = false; $this->new = false;
@@ -85,19 +115,20 @@ class Model
$data[$updatecol] = $this->values[$updatecol]; $data[$updatecol] = $this->values[$updatecol];
} }
$query = $qb->getPreparedQuery($data, false); $query = $qb->getPreparedQuery($data, false);
$ret = $this->db->updateQuery($query); $ret = Connection::getInstance($this->connection)->updateQuery($query);
if ($ret) { if ($ret) {
$this->updated = []; $this->updated = [];
} }
} }
} }
} }
}
return $ret; return $ret;
} }
public function newQueryBuilder(): QueryBuilder public function newQueryBuilder($className): QueryBuilder
{ {
return new QueryBuilder($this->table, $this->pk, $this->connection); return new QueryBuilder($this->table, $this->pk, $this->connection, $className);
} }
public function __set($name, $value): void public function __set($name, $value): void
@@ -108,6 +139,27 @@ class Model
} }
} }
public function __isset($name): bool
{
if (method_exists($this, $name)) {
return true;
} else {
return isset($this->values[$name]);
}
}
public static function getClassName()
{
return static::class;
}
public static function q(): QueryBuilder
{
$cls = self::getClassName();
$ist = new static();
return $ist->newQueryBuilder($cls);
}
public function __get($name): mixed public function __get($name): mixed
{ {
if (isset($this->values[$name])) { if (isset($this->values[$name])) {
@@ -125,4 +177,9 @@ class Model
return $table; return $table;
} }
public function jsonSerialize(): mixed
{
return $this->values;
}
} }

View File

@@ -4,24 +4,29 @@ namespace Kletellier\PdoWrapper;
class QueryBuilder class QueryBuilder
{ {
protected ?Db $db;
protected array $columns; protected array $columns;
protected array $wheres; protected array $wheres;
protected array $orwheres;
protected array $orders; protected array $orders;
protected array $groupby; protected array $groupby;
protected string $table; protected string $table;
protected string $classname;
protected string $connection; protected string $connection;
protected string $pk; protected string $pk;
protected ?int $take; protected ?int $take;
protected ?int $offset; protected ?int $offset;
protected string $raw_query; protected string $raw_query;
protected bool $distinct;
public function __construct($table, $pk = "id", $connection = "default") public const MODE_AND = 0;
public const MODE_OR = 1;
public function __construct($table, $pk = "id", $connection = "", $classname = "")
{ {
$this->classname = $classname;
$this->table = $table; $this->table = $table;
$this->pk = $pk; $this->pk = $pk;
$this->connection = $connection; $this->connection = $connection;
$this->db = null;
$this->reset(); $this->reset();
} }
@@ -29,10 +34,12 @@ class QueryBuilder
{ {
$this->columns = []; $this->columns = [];
$this->wheres = []; $this->wheres = [];
$this->orwheres = [];
$this->orders = []; $this->orders = [];
$this->groupby = []; $this->groupby = [];
$this->offset = null; $this->offset = null;
$this->take = null; $this->take = null;
$this->distinct = false;
$this->raw_query = ""; $this->raw_query = "";
} }
@@ -54,6 +61,12 @@ class QueryBuilder
return $this; return $this;
} }
public function distinct()
{
$this->distinct = true;
return $this;
}
public function orderBy($columns, $sort = "ASC") public function orderBy($columns, $sort = "ASC")
{ {
if (is_array($columns)) { if (is_array($columns)) {
@@ -100,6 +113,18 @@ class QueryBuilder
return $this; return $this;
} }
public function whereNotNull($condition)
{
$this->wheres[] = new Expression($condition, "notnull", "");
return $this;
}
public function whereNull($condition)
{
$this->wheres[] = new Expression($condition, "isnull", "");
return $this;
}
public function where($condition, $operator, $value = null) public function where($condition, $operator, $value = null)
{ {
if ($value === null) { if ($value === null) {
@@ -110,12 +135,28 @@ class QueryBuilder
return $this; return $this;
} }
public function orWhere($condition, $operator, $value = null)
{
if ($value === null) {
$this->orwheres[] = new Expression($condition, "=", $operator);
} else {
$this->orwheres[] = new Expression($condition, $operator, $value);
}
return $this;
}
public function whereRaw(string $raw) public function whereRaw(string $raw)
{ {
$this->wheres[] = $raw; $this->wheres[] = $raw;
return $this; return $this;
} }
public function orWhereRaw(string $raw)
{
$this->orwheres[] = $raw;
return $this;
}
public function find($id): mixed public function find($id): mixed
{ {
$this->reset(); $this->reset();
@@ -124,7 +165,7 @@ class QueryBuilder
$this->columns = ["*"]; $this->columns = ["*"];
$data = $this->getData(); $data = $this->getData();
if (count($data) > 0) { if (count($data) > 0) {
return $this->prepareModels($data)[0]; return $this->prepareModels($data)->first();
} }
return null; return null;
} }
@@ -169,16 +210,20 @@ class QueryBuilder
{ {
$ret = array(); $ret = array();
foreach ($data as $row) { foreach ($data as $row) {
if ($this->classname != "") {
$model = new $this->classname();
} else {
$model = new Model(); $model = new Model();
}
$ret[] = $model->setPk($this->pk)->setTable($this->table)->fillData($row); $ret[] = $model->setPk($this->pk)->setTable($this->table)->fillData($row);
} }
return $ret; return new Collection($ret);
} }
private function getData(): mixed private function getData(): mixed
{ {
$this->db = Db::getInstance($this->connection); return Connection::getInstance($this->connection)->getSelectQuery($this->getQuery());
return $this->db->getSelectQuery($this->getQuery());
} }
private function getConstructedQuery(): Query private function getConstructedQuery(): Query
@@ -192,13 +237,20 @@ class QueryBuilder
} }
$columns = implode(",", $this->columns); $columns = implode(",", $this->columns);
$query = "SELECT " . $columns . " FROM " . $this->table ; $distinct = ($this->distinct) ? " DISTINCT " : "";
$query = "SELECT " . $distinct . " " . $columns . " FROM " . $this->table ;
$where = $this->getWhere(); $where = $this->getWhere();
$Orwhere = $this->getWhere(QueryBuilder::MODE_OR);
$this->fillData($ret); $this->fillData($ret);
if ($where != "" && $Orwhere != "") {
$Orwhere = " OR " . $Orwhere;
}
if ($where != "") { if ($where != "") {
$query .= " WHERE " . $where; $query .= " WHERE " . $where . $Orwhere;
} }
$query .= $this->getClause("GROUP By", $this->groupby); $query .= $this->getClause("GROUP By", $this->groupby);
@@ -211,17 +263,27 @@ class QueryBuilder
private function fillData(Query &$query): void private function fillData(Query &$query): void
{ {
foreach ($this->wheres as $expression) { foreach (array_merge($this->wheres, $this->orwheres) as $expression) {
if ($expression instanceof Expression) { if ($expression instanceof Expression) {
if ($expression->hasData()) {
$query->addData($expression->getValue()); $query->addData($expression->getValue());
} }
} }
} }
}
private function getWhere(): string private function getWhere(int $mode = QueryBuilder::MODE_AND): string
{ {
$where = ""; $where = "";
foreach ($this->wheres as $expression) { $collection = match ($mode) {
QueryBuilder::MODE_AND => $this->wheres,
QueryBuilder::MODE_OR => $this->orwheres,
};
$keyword = match ($mode) {
QueryBuilder::MODE_AND => " AND ",
QueryBuilder::MODE_OR => " OR ",
};
foreach ($collection as $expression) {
$sw = ""; $sw = "";
if ($expression instanceof Expression) { if ($expression instanceof Expression) {
$sw = $expression->raw(); $sw = $expression->raw();
@@ -232,7 +294,7 @@ class QueryBuilder
} }
} }
if ($where != "" && $sw != "") { if ($where != "" && $sw != "") {
$where .= " AND "; $where .= " $keyword ";
} }
$where .= $sw; $where .= $sw;
} }

View File

@@ -14,6 +14,8 @@ test("collection", function () {
expect($coll->count())->toBe(11); expect($coll->count())->toBe(11);
expect($coll->first())->toBe(0); expect($coll->first())->toBe(0);
expect($coll->last())->toBe(10); expect($coll->last())->toBe(10);
expect($coll->index(1))->toBe(1);
expect($coll->random())->toBeGreaterThanOrEqual(0)->toBeLessThanOrEqual(11);
$filt = $coll->filter(function($item){ return $item<5;}); $filt = $coll->filter(function($item){ return $item<5;});
expect($filt)->toBeInstanceOf(Collection::class); expect($filt)->toBeInstanceOf(Collection::class);
@@ -48,5 +50,29 @@ test("collection", function () {
return $item + $carry; return $item + $carry;
},$init); },$init);
expect($reduce)->toBe(55); expect($reduce)->toBe(55);
$array = $coll->toArray();
expect($array)->toBeArray();
expect(count($array))->toBe(11);
$obj = new \stdClass();
$obj->key = "test";
$obj->val = 1;
$obj2 = new \stdClass();
$obj2->key = "test2";
$obj2->val = 2;
$collo = new Collection([$obj,$obj2]);
expect($collo)->toBeInstanceOf(Collection::class);
expect(count($collo))->toBe(2);
$json = json_encode($collo);
expect($json)->toBeJson();
$pluck = $collo->pluck("key");
expect($pluck)->toBeInstanceOf(Collection::class);
expect(count($pluck))->toBe(2);
expect($pluck->first())->toBe("test");
}); });

View File

@@ -1,17 +1,18 @@
<?php <?php
use Kletellier\PdoWrapper\Db; use Kletellier\PdoWrapper\Collection;
use Kletellier\PdoWrapper\Connection;
use Kletellier\PdoWrapper\EventDispatcher; use Kletellier\PdoWrapper\EventDispatcher;
use Kletellier\PdoWrapper\Model; use Kletellier\PdoWrapper\Model;
use Kletellier\PdoWrapper\QueryBuilder; use Kletellier\PdoWrapper\QueryBuilder;
test("create db",function () { test("create db",function () {
$dir = getcwd(); $config = ["conone" => ["TYPE"=>"sqlite", "DATABASE" => ":memory:" ], "contwo"=>["TYPE"=>"sqlite", "DATABASE" => ":memory:" ]];
$db = Db::init($dir,["default","contwo"]); $db = Connection::init($config,["conone","contwo"]);
$ret = $db->executeRawQuery("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, col1 VARCHAR, date1 DATETIME);"); $ret = $db->executeRawQuery("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, col1 VARCHAR, date1 DATETIME);");
expect($ret)->toBe(true); expect($ret)->toBe(true);
$db2 = Db::getInstance("contwo"); $db2 = Connection::getInstance("contwo");
$ret = $db2->executeRawQuery("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, col1 VARCHAR, date1 DATETIME);"); $ret = $db2->executeRawQuery("CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, col1 VARCHAR, date1 DATETIME);");
expect($ret)->toBe(true); expect($ret)->toBe(true);
}); });
@@ -34,6 +35,32 @@ test("insert row",function () {
expect($ret2)->toBe(true); expect($ret2)->toBe(true);
}); });
test("model find", function() {
$row = new Model();
$row->setTable("test");
$res = $row->find(1);
expect($res->id)->toBe(1);
expect($row->col1)->toBe("test");
$res = $row->find(15);
expect($res)->toBe(null);
$row->col1 = "test2";
$res = $row->save();
expect($res)->toBe(true);
$row->col1 = "test";
$res = $row->save();
expect($res)->toBe(true);
$json = json_encode($row);
expect($json)->toBeJson();
$obj = json_decode($json);
expect($obj->col1)->toBe("test");
});
test("querybuilder", function() { test("querybuilder", function() {
$qb = new QueryBuilder("test"); $qb = new QueryBuilder("test");
$row = $qb->find(1); $row = $qb->find(1);
@@ -45,49 +72,90 @@ test("querybuilder", function() {
$qb->reset(); $qb->reset();
$rows = $qb->where("id",1)->columns("*")->get(); $rows = $qb->where("id",1)->columns("*")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1); expect(count($rows))->toBe(1);
$qb->reset(); $qb->reset();
$rows = $qb->where("id",">",2)->columns("*")->get(); $rows = $qb->where("id",">",2)->columns("*")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(0); expect(count($rows))->toBe(0);
$qb->reset(); $qb->reset();
$rows = $qb->whereRaw("id = 1")->columns("*")->get(); $rows = $qb->whereRaw("id = 1")->columns("*")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1); expect(count($rows))->toBe(1);
$qb->reset(); $qb->reset();
$rows = $qb->whereRaw("id = 1")->get(); $rows = $qb->whereRaw("id = 1")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1); expect(count($rows))->toBe(1);
$qb->reset();
$rows = $qb->distinct()->get();
expect(count($rows))->toBe(2);
$qb->reset();
$rows = $qb->whereNotNull("col1")->get();
expect(count($rows))->toBe(2);
$qb->reset();
$rows = $qb->where("id",1)->orWhere("id",2)->get();
expect(count($rows))->toBe(2);
});
test("extending model",function()
{
$class = "Test";
$code = "class $class extends \Kletellier\PdoWrapper\Model {}";
eval($code);
$qbs = Test::q();
expect($qbs)->toBeInstanceOf(QueryBuilder::class);
$row = $qbs->find(1);
expect($row->id)->toBe(1);
expect($row->col1)->toBe("test");
expect($row)->toBeInstanceOf(Test::class);
}); });
test("order by", function(){ test("order by", function(){
$qb = new QueryBuilder("test"); $qb = new QueryBuilder("test");
$rows = $qb->orderBy("id","DESC")->get(); $rows = $qb->orderBy("id","DESC")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(2); expect(count($rows))->toBe(2);
expect($rows[0]->col1)->toBe("test2"); expect($rows->first()->col1)->toBe("test2");
}); });
test("group by", function(){ test("group by", function(){
$qb = new QueryBuilder("test"); $qb = new QueryBuilder("test");
$rows = $qb->columns("COUNT(*) as Nb")->get(); $rows = $qb->columns("COUNT(*) as Nb")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1); expect(count($rows))->toBe(1);
expect($rows[0]->Nb)->toBe(2); expect($rows->first()->Nb)->toBe(2);
$qb->reset(); $qb->reset();
$rows = $qb->columns("COUNT(*) as Nb")->groupBy("id")->get(); $rows = $qb->columns("COUNT(*) as Nb")->groupBy("id")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(2); expect(count($rows))->toBe(2);
expect($rows[0]->Nb)->toBe(1); expect($rows->first()->Nb)->toBe(1);
});
test("isset",function()
{
$q = new QueryBuilder("test");
$row = $q->where("id",1)->get()->first();
expect($row->id)->toBe(1);
expect($row->col1)->toBe("test");
expect(isset($row->col1))->toBe(true);
expect(isset($row->col2))->toBe(false);
expect(isset($row->find))->toBe(true);
}); });
test("update row", function() { test("update row", function() {
@@ -99,9 +167,9 @@ test("update row", function() {
$qb->reset(); $qb->reset();
$rows = $qb->where("id",1)->columns("*")->get(); $rows = $qb->where("id",1)->columns("*")->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1); expect(count($rows))->toBe(1);
expect($rows[0]->col1)->toBe("testupdate"); expect($rows->first()->col1)->toBe("testupdate");
}); });
test("event", function() { test("event", function() {
@@ -134,15 +202,31 @@ test("where array", function() {
$between = $qb->whereBetween("id",2,3)->get(); $between = $qb->whereBetween("id",2,3)->get();
expect(count($between))->toBe(2); expect(count($between))->toBe(2);
expect($between[0]->id)->toBe(2); expect($between->first()->id)->toBe(2);
$qb->reset(); $qb->reset();
$in = $qb->whereIn("id",[2,3])->get(); $in = $qb->whereIn("id",[2,3])->get();
expect(count($in))->toBe(2); expect(count($in))->toBe(2);
expect($in[0]->id)->toBe(2); expect($in->first()->id)->toBe(2);
}); });
test("is null", function(){
$row4 = new Model();
$row4->setTable("test");
$row4->col1 = null;
$row4->date1 = new \DateTime();
$ret4 = $row4->save();
expect($ret4)->toBe(true);
$qb = new QueryBuilder("test");
$rows = $qb->whereNull("col1")->get();
expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(1);
expect($rows->first()->col1)->toBe(null);
});
test("second connection", function() { test("second connection", function() {
$row = new Model(); $row = new Model();
$row->setConnection("contwo"); $row->setConnection("contwo");
@@ -164,6 +248,6 @@ test("second connection", function() {
$qb = new QueryBuilder("test","id","contwo"); $qb = new QueryBuilder("test","id","contwo");
$rows = $qb->get(); $rows = $qb->get();
expect($rows)->toBeArray(); expect($rows)->toBeInstanceOf(Collection::class);
expect(count($rows))->toBe(2); expect(count($rows))->toBe(2);
}); });