6 Commits
1.0.2 ... 1.0.8

Author SHA1 Message Date
ee3ea44a81 add phpdoc 2025-01-09 22:54:48 +01:00
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
7 changed files with 446 additions and 52 deletions

View File

@@ -12,33 +12,64 @@ class Collection implements IteratorAggregate, Countable, JsonSerializable
{ {
private array $elements; private array $elements;
/**
* Constructor
*
* @param array $elements
*/
public function __construct(array $elements = []) public function __construct(array $elements = [])
{ {
$this->elements = $elements; $this->elements = $elements;
} }
/**
* Return first element of the collection
*
* @return mixed
*/
public function first(): mixed public function first(): mixed
{ {
return reset($this->elements); return reset($this->elements);
} }
/**
* Return last element of the collection
*
* @return mixed
*/
public function last(): mixed public function last(): mixed
{ {
return end($this->elements); return end($this->elements);
} }
/**
* Return collection length
*
* @return integer
*/
public function count(): int public function count(): int
{ {
return count($this->elements); return count($this->elements);
} }
/**
* Clear all elements in collection
*
* @return boolean
*/
public function clear(): bool public function clear(): bool
{ {
$this->elements = []; $this->elements = [];
return count($this->elements) == 0; return count($this->elements) == 0;
} }
public function index($index): mixed /**
* Return element in nth position in the collection
*
* @param int $index
* @return mixed
*/
public function index(int $index): mixed
{ {
if (isset($this->elements[$index])) { if (isset($this->elements[$index])) {
return $this->elements[$index]; return $this->elements[$index];
@@ -46,31 +77,67 @@ class Collection implements IteratorAggregate, Countable, JsonSerializable
return null; return null;
} }
/**
* Return random element of the collection
*
* @return mixed
*/
public function random(): mixed public function random(): mixed
{ {
return array_rand($this->elements); return array_rand($this->elements);
} }
/**
* Reduce function on the collection
*
* @param callable $fn
* @param mixed $initial
* @return mixed
*/
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);
} }
/**
* Map function on the collection
*
* @param callable $fn
* @return Collection
*/
public function map(callable $fn): Collection public function map(callable $fn): Collection
{ {
return new Collection(array_map($fn, $this->elements)); return new Collection(array_map($fn, $this->elements));
} }
/**
* Apply callable function on each element of the function
*
* @param callable $fn
* @return void
*/
public function each(callable $fn): void public function each(callable $fn): void
{ {
array_walk($this->elements, $fn); array_walk($this->elements, $fn);
} }
/**
* Apply function to filter collection
*
* @param callable $fn
* @return Collection
*/
public function filter(callable $fn): Collection public function filter(callable $fn): Collection
{ {
return new Collection(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH)); return new Collection(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH));
} }
/**
* Return collection of the key related items in current collection
*
* @param string $key
* @return Collection
*/
public function pluck(string $key): Collection public function pluck(string $key): Collection
{ {
return new Collection(array_map(function ($item) use ($key) { return new Collection(array_map(function ($item) use ($key) {
@@ -83,36 +150,72 @@ class Collection implements IteratorAggregate, Countable, JsonSerializable
}, $this->elements)); }, $this->elements));
} }
/**
* Return actual collection items in an array
*
* @return array
*/
public function toArray(): array public function toArray(): array
{ {
return $this->elements; return $this->elements;
} }
/**
* Return if the collection are empty
*
* @return boolean
*/
public function empty(): bool public function empty(): bool
{ {
return empty($this->elements); return empty($this->elements);
} }
/**
* Add an element to the collection
*
* @param mixed $element
* @return void
*/
public function add(mixed $element): void public function add(mixed $element): void
{ {
$this->elements[] = $element; $this->elements[] = $element;
} }
/**
* Return all values of the collection
*
* @return array
*/
public function values(): array public function values(): array
{ {
return array_values($this->elements); return array_values($this->elements);
} }
/**
* Return all items of the collection
*
* @return array
*/
public function items(): array public function items(): array
{ {
return $this->elements; return $this->elements;
} }
/**
* Iterator
*
* @return Traversable
*/
public function getIterator(): Traversable public function getIterator(): Traversable
{ {
return new ArrayIterator($this->elements); return new ArrayIterator($this->elements);
} }
/**
* Return json serialization of the collection
*
* @return mixed
*/
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return $this->elements; return $this->elements;

View File

@@ -18,6 +18,7 @@ class Connection
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, $config) private function __construct($name, $config)
@@ -44,6 +45,7 @@ class Connection
$this->password = isset($config[$key]["PASSWORD"]) ? $config[$key]["PASSWORD"] : null; $this->password = isset($config[$key]["PASSWORD"]) ? $config[$key]["PASSWORD"] : null;
$this->database = isset($config[$key]["DATABASE"]) ? $config[$key]["DATABASE"] : null; $this->database = isset($config[$key]["DATABASE"]) ? $config[$key]["DATABASE"] : null;
$this->port = isset($config[$key]["PORT"]) ? $config[$key]["PORT"] : null; $this->port = isset($config[$key]["PORT"]) ? $config[$key]["PORT"] : null;
$this->charset = isset($config[$key]["CHARSET"]) ? $config[$key]["CHARSET"] : null;
} catch (\Throwable $th) { } catch (\Throwable $th) {
$this->error = $th->getMessage(); $this->error = $th->getMessage();
} }
@@ -70,6 +72,7 @@ class Connection
$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();
} }
@@ -117,20 +120,42 @@ class Connection
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,
}; };
} }
/**
* Execute raw query on the current pdo connection
*
* @param string $rawquery
* @return boolean
*/
public function executeRawQuery(string $rawquery): bool public function executeRawQuery(string $rawquery): bool
{ {
$stmt = $this->pdo->prepare($rawquery); $stmt = $this->pdo->prepare($rawquery);
return $stmt->execute(); return $stmt->execute();
} }
/**
* Run prepared query in the current pdo connection
*
* @param Query $query
* @param $fetchmode
* @return mixed
*/
public function getSelectQuery(Query $query, $fetchmode = PDO::FETCH_ASSOC): mixed public function getSelectQuery(Query $query, $fetchmode = PDO::FETCH_ASSOC): mixed
{ {
$stmt = $this->pdo->prepare($query->getQuery()); $stmt = $this->pdo->prepare($query->getQuery());
@@ -140,6 +165,12 @@ class Connection
return $stmt->fetchAll($fetchmode); return $stmt->fetchAll($fetchmode);
} }
/**
* Run prepared update query on the current pdo connection
*
* @param Query $query
* @return boolean
*/
public function updateQuery(Query $query): bool public function updateQuery(Query $query): bool
{ {
$stmt = $this->pdo->prepare($query->getQuery()); $stmt = $this->pdo->prepare($query->getQuery());
@@ -148,6 +179,12 @@ class Connection
return $stmt->execute(); return $stmt->execute();
} }
/**
* Run prepared insert query on the current pdo connection
*
* @param Query $query
* @return mixed
*/
public function insertQuery(Query $query): mixed public function insertQuery(Query $query): mixed
{ {
$stmt = $this->pdo->prepare($query->getQuery()); $stmt = $this->pdo->prepare($query->getQuery());
@@ -161,6 +198,11 @@ class Connection
} }
} }
/**
* Begin transaction on the current pdo connection
*
* @return boolean
*/
public function beginTransaction(): bool public function beginTransaction(): bool
{ {
if ($this->pdo) { if ($this->pdo) {
@@ -169,6 +211,11 @@ class Connection
return false; return false;
} }
/**
* Commit transaction on the current pdo connection
*
* @return boolean
*/
public function commit(): bool public function commit(): bool
{ {
if ($this->pdo) { if ($this->pdo) {
@@ -177,6 +224,11 @@ class Connection
return false; return false;
} }
/**
* Rollback transaction on the current pdo connection
*
* @return boolean
*/
public function rollback(): bool public function rollback(): bool
{ {
if ($this->pdo) { if ($this->pdo) {
@@ -185,6 +237,13 @@ class Connection
return false; return false;
} }
/**
* Init the connections
*
* @param [type] $config
* @param array $connections
* @return void
*/
public static function init($config, array $connections = ["default"]) public static function init($config, array $connections = ["default"])
{ {
self::fillInstances($config, $connections); self::fillInstances($config, $connections);
@@ -214,17 +273,32 @@ class Connection
throw new \Exception("Unknown connection"); throw new \Exception("Unknown connection");
} }
public function getError() /**
* Return last error
*
* @return string
*/
public function getError(): string
{ {
return $this->error; return $this->error;
} }
public function getPdo() /**
* Return current PDO object
*
* @return PDO
*/
public function getPdo(): PDO
{ {
return $this->pdo; return $this->pdo;
} }
public function getDriverType() /**
* Return current database driver type currently used
*
* @return string
*/
public function getDriverType(): string
{ {
return $this->type; return $this->type;
} }

View File

@@ -4,9 +4,9 @@ namespace Kletellier\PdoWrapper;
class Expression class Expression
{ {
private $condition; private string $condition;
private $operator; private string $operator;
private $value; private mixed $value;
public function __construct($condition, $operator, $value) public function __construct($condition, $operator, $value)
{ {
@@ -15,6 +15,11 @@ class Expression
$this->value = $value; $this->value = $value;
} }
/**
* Return raw value of the expression
*
* @return string
*/
public function raw(): string public function raw(): string
{ {
$operator = trim(strtolower($this->operator)); $operator = trim(strtolower($this->operator));
@@ -34,40 +39,78 @@ class Expression
return "(" . $this->condition . " " . $this->operator . " ? )"; return "(" . $this->condition . " " . $this->operator . " ? )";
} }
/**
* Return if the condition need to evaluate data
*
* @return boolean
*/
public function hasData() public function hasData()
{ {
$operator = trim(strtolower($this->operator)); $operator = trim(strtolower($this->operator));
return (in_array($operator, ["isnull","notnull"])) ? false : true; return (in_array($operator, ["isnull","notnull"])) ? false : true;
} }
public function getCondition() /**
* Return actual condition
*
* @return string
*/
public function getCondition(): string
{ {
return $this->condition; return $this->condition;
} }
public function setCondition($condition) /**
* Set condition
*
* @param string $condition
* @return self
*/
public function setCondition(string $condition): self
{ {
$this->condition = $condition; $this->condition = $condition;
return $this; return $this;
} }
public function getOperator() /**
* Get actual operator
*
* @return string
*/
public function getOperator(): string
{ {
return $this->operator; return $this->operator;
} }
public function setOperator($operator) /**
* Set operator
*
* @param string $operator
* @return self
*/
public function setOperator(string $operator): self
{ {
$this->operator = $operator; $this->operator = $operator;
return $this; return $this;
} }
public function getValue() /**
* Get the value of the expression
*
* @return mixed
*/
public function getValue(): mixed
{ {
return $this->value; return $this->value;
} }
public function setValue($value) /**
* Set the value of the expression
*
* @param mixed $value
* @return void
*/
public function setValue(mixed $value)
{ {
$this->value = $value; $this->value = $value;
return $this; return $this;

View File

@@ -8,58 +8,121 @@ 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 = ""; protected string $connection = "";
protected string $pk; protected string $pk;
private bool $new; private bool $new;
private bool $ro;
public function __construct() /**
* Constructor
*
* @param boolean $ro readonly model
*/
public function __construct($ro = false)
{ {
$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->table = $this->getDefaultTableName(); $this->ro = $ro;
$this->initTable();
} }
private function initTable(): void
{
if (!$this->ro) {
if ($this->table == "") {
$this->table = $this->getDefaultTableName();
}
} else {
$this->table = "ReadOnlyModel";
$this->pk = "__idpk";
}
}
/**
* Return table
*
* @return string
*/
public function getTable(): string public function getTable(): string
{ {
return $this->table; return $this->table;
} }
/**
* Define table
*
* @param string $table
* @return self
*/
public function setTable(string $table): self public function setTable(string $table): self
{ {
$this->table = $table; $this->table = $table;
return $this; return $this;
} }
/**
* Return current key value attributes
*
* @return array
*/
public function getValues(): array public function getValues(): array
{ {
return $this->values; return $this->values;
} }
/**
* Return current primary key
*
* @return string
*/
public function getPk(): string public function getPk(): string
{ {
return $this->pk; return $this->pk;
} }
/**
* Set primary key
*
* @param string $pk
* @return self
*/
public function setPk(string $pk): self public function setPk(string $pk): self
{ {
$this->pk = $pk; $this->pk = $pk;
return $this; return $this;
} }
public function getConnection() /**
* Return actual connection
*
* @return string
*/
public function getConnection(): string
{ {
return $this->connection; return $this->connection;
} }
public function setConnection($connection) /**
* Set connection
*
* @param string $connection
* @return void
*/
public function setConnection(string $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
return $this; return $this;
} }
/**
* Fill values from key value array
*
* @param mixed $data
* @return self
*/
public function fillData(mixed $data): self public function fillData(mixed $data): self
{ {
$this->values = $data; $this->values = $data;
@@ -68,9 +131,15 @@ class Model implements JsonSerializable
return $this; return $this;
} }
/**
* Find specific element in the table on the primary key
*
* @param mixed $key
* @return mixed
*/
public function find(mixed $key): mixed public function find(mixed $key): mixed
{ {
$qb = $this->newQueryBuilder(); $qb = $this->newQueryBuilder(get_class($this));
$find = $qb->find($key); $find = $qb->find($key);
if ($find !== null) { if ($find !== null) {
$this->fillData($find->getValues()); $this->fillData($find->getValues());
@@ -79,10 +148,16 @@ class Model implements JsonSerializable
return null; return null;
} }
/**
* Save current model
*
* @return boolean
*/
public function save(): bool public function save(): bool
{ {
$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 = Connection::getInstance($this->connection)->insertQuery($query); $id = Connection::getInstance($this->connection)->insertQuery($query);
@@ -107,12 +182,19 @@ class Model implements JsonSerializable
} }
} }
} }
}
return $ret; return $ret;
} }
public function newQueryBuilder(): QueryBuilder /**
* Return new QueryBuilder on the model
*
* @param string $className
* @return QueryBuilder
*/
public function newQueryBuilder(string $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
@@ -132,10 +214,21 @@ class Model implements JsonSerializable
} }
} }
public static function getClassName()
{
return static::class;
}
/**
* Static method to return new QueryBuilder
*
* @return QueryBuilder
*/
public static function q(): QueryBuilder public static function q(): QueryBuilder
{ {
$cls = self::getClassName();
$ist = new static(); $ist = new static();
return $ist->newQueryBuilder(); return $ist->newQueryBuilder($cls);
} }
public function __get($name): mixed public function __get($name): mixed
@@ -156,6 +249,11 @@ class Model implements JsonSerializable
return $table; return $table;
} }
/**
* Serialize the model in Json
*
* @return mixed
*/
public function jsonSerialize(): mixed public function jsonSerialize(): mixed
{ {
return $this->values; return $this->values;

View File

@@ -13,7 +13,13 @@ class Query
$this->data = []; $this->data = [];
} }
public function addData($value) /**
* Add data to the query
*
* @param array|mixed $value
* @return void
*/
public function addData(mixed $value)
{ {
if (is_array($value)) { if (is_array($value)) {
$this->data = array_merge($this->data, $value); $this->data = array_merge($this->data, $value);
@@ -22,22 +28,44 @@ class Query
} }
} }
/**
* Return statement
*
* @return string
*/
public function getQuery(): string public function getQuery(): string
{ {
return $this->query; return $this->query;
} }
/**
* Set Statement
*
* @param string $query
* @return void
*/
public function setQuery(string $query) public function setQuery(string $query)
{ {
$this->query = $query; $this->query = $query;
return $this; return $this;
} }
/**
* Return current data of the query
*
* @return array
*/
public function getData(): array public function getData(): array
{ {
return $this->data; return $this->data;
} }
/**
* Set data of the query
*
* @param array $data
* @return void
*/
public function setData(array $data) public function setData(array $data)
{ {
$this->data = $data; $this->data = $data;

View File

@@ -6,9 +6,11 @@ class QueryBuilder
{ {
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;
@@ -16,8 +18,12 @@ class QueryBuilder
protected string $raw_query; protected string $raw_query;
protected bool $distinct; protected bool $distinct;
public function __construct($table, $pk = "id", $connection = "") 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;
@@ -28,6 +34,7 @@ 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;
@@ -128,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();
@@ -187,7 +210,12 @@ 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 new Collection($ret); return new Collection($ret);
@@ -213,10 +241,16 @@ class QueryBuilder
$query = "SELECT " . $distinct . " " . $columns . " FROM " . $this->table ; $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);
@@ -229,7 +263,7 @@ 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()) { if ($expression->hasData()) {
$query->addData($expression->getValue()); $query->addData($expression->getValue());
@@ -238,10 +272,18 @@ class QueryBuilder
} }
} }
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();
@@ -252,7 +294,7 @@ class QueryBuilder
} }
} }
if ($where != "" && $sw != "") { if ($where != "" && $sw != "") {
$where .= " AND "; $where .= " $keyword ";
} }
$where .= $sw; $where .= $sw;
} }

View File

@@ -98,6 +98,10 @@ test("querybuilder", function() {
$qb->reset(); $qb->reset();
$rows = $qb->whereNotNull("col1")->get(); $rows = $qb->whereNotNull("col1")->get();
expect(count($rows))->toBe(2); 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() test("extending model",function()
@@ -112,6 +116,8 @@ test("extending model",function()
$row = $qbs->find(1); $row = $qbs->find(1);
expect($row->id)->toBe(1); expect($row->id)->toBe(1);
expect($row->col1)->toBe("test"); expect($row->col1)->toBe("test");
expect($row)->toBeInstanceOf(Test::class);
}); });
test("order by", function(){ test("order by", function(){