<?php
namespace Tlf;
/**
* Extend this class to create an interface for your BigDb library. Provides many convenience methods for various features. Uses 4 traits.
*/
class BigDb {
/** for convenience sql commands like select, insert, update, delete */
use BigDb\SqlVerbs;
/** To easily load sql from .sql files */
use BigDb\SqlFiles;
/** Integrate with LilDb's LilMigrations class, to handle database versioning */
use BigDb\Migrations;
/** For loading of ORMs */
use BigDb\GetOrms;
public readonly \PDO $pdo;
public \Tlf\LilDb $ldb;
/** Sql queries, typically from .sql files on disk */
protected array $sql;
/** The namespace from which Orm classes should be loaded */
protected string $orm_namespace;
/** The name used by BigDbServer to identify this BigDb instance. Does NOT correspond to a mysql database. Also, {}@see get_db_name()} */
protected string $db_name;
public string $root_dir;
/**
* The application's timezone as a DateTimeZone-compatible string, like 'America/Chicago' or 'UTC'
*
* All DateTimes are converted to UTC for database storage bc MySql does not store timezone information.
*/
public string $timezone = 'UTC';
/**
* Construct a BigDb instance and initialize it.
*
* @param $pdo \PDO a pdo instance with a valid database connection
* @param $root_dir ?string directory of the database app to load. If null, then uses `$this->get_root_dir()`
*/
public function __construct(\PDO $pdo, ?string $root_dir = null){
$this->pdo = $pdo;
$this->ldb = new \Tlf\LilDb($pdo);
$this->root_dir = $root_dir ?? $this->get_root_dir();
$this->init_sql();
}
/**
* Initialize the sql statements from the compiled sql file, only if `$this->sql` is not set
*
* @return void
*
* @override to use a different sql storage system than LilDb's LilSql
*/
protected function init_sql(){
if (isset($this->sql))return;
$this->load_queries($this->get_root_dir().'/sql/');
}
/**
* Get the stored sql queries
*/
public function getSql(): array{
return $this->sql;
}
/**
* Get path to the root of a BigDb library.
*
* @return directory name where your BigDb subclass is defined, or null if not a subclass
* @override if your BigDb subclass is not in the root directory of your bigdb library.
*/
public function get_root_dir(): ?string {
if (isset($this->root_dir))return $this->root_dir;
if (!is_subclass_of($this, self::class)){
throw new \Exception("You must set root_dir on your db instance");
return null;
}
$refClass = new \ReflectionClass($this);
$file = $refClass->getFileName();
$dir = dirname($file);
return $dir;
}
/**
* Get a name for the BigDbServer to reference.
* @return string a string name, typically snake_case. Default implementation returns `$this->db_name` if set, or a snake_case version of the class's basename if `protected $db_name` is not set.
*
* @override if setting `$this->db_name` will not work for you AND you don't want a snake_case version of the class's basename.
*/
public function get_db_name(): string {
if (isset($this->db_name))return $this->db_name;
$class_name = get_class($this);
$parts = explode('\\', $class_name);
$base = array_pop($parts);
$snake = preg_replace('/([a-z])([A-Z])([a-z])/', '$1_$2$3', $base);
$snake = strtolower($snake);
return $snake;
}
/**
* Convert an array of orms to an array of arrays
* @param $orms_array `array<int index, \Tlf\BigOrm>` items to convert to arrays
*
* @return array<int index, array db_row>
*/
public function to_arrays(array $orms_array): array{
return array_map(
function(\Tlf\BigOrm $v){
return $v->get_db_row();
}, $orms_array
);
}
/**
* Coerce a property value to a database value
*
* @param $type string type, from ReflectionProperty
* @param $property_value mixed value of the given $type
* @param $property_name string property name for some automatic-conversions like uuid, or empty string
*
* @return a database-friendly value
* @throw \Exception if value cannot be coerced
*/
public function coerce_to_db(string $type, mixed $property_value, string $property_name=''): mixed {
if ($type[0]=='?')$type = substr($type,1);
switch ($type) {
case "bool":
return $property_value ? 1 : 0;
case "DateTime":
$property_value->setTimezone(new \DateTimeZone("UTC"));
return $property_value->format('Y-m-d H:i:s');
}
$combo = $type.'_'.$property_name;
switch ($combo){
case "string_uuid":
if ($property_value==null)return null;
return $this->uuid_to_bin($property_value);
}
if ($property_value instanceof \BackedEnum){
return $property_value->value;
}
throw new \Exception("Cannot coerce property '$property_name' of type '$type' to db value.");
}
/**
* Coerce a database value into a property value
*
* @param $type string type, from ReflectionProperty
* @param $db_value mixed value as retrieved from database
* @param $property_name string property name for some automatic-conversions like uuid, or empty string
*
* @return mixed value of the given $type
* @throw \Exception if value cannot be coerced
*/
public function coerce_from_db(string $type, mixed $db_value, string $property_name): mixed {
if ($type[0]=='?')$type = substr($type,1);
switch ($type) {
case "bool":
return (bool)$db_value;
case "DateTime":
$dt = \DateTime::createFromFormat('Y-m-d H:i:s', $db_value);
if ($dt === false)throw new \Exception("Datetime string '$db_value' cannot be automatically instantiated as a DateTime object. Implement a manual datetime conversion, or fix the format.\n");
$dt->setTimezone(new \DateTimeZone($this->timezone));
return $dt;
}
$combo = $type.'_'.$property_name;
switch ($combo){
case "string_uuid":
if ($db_value==null)return null;
return $this->bin_to_uuid($db_value);
}
if (is_a($type, 'BackedEnum',true)){
return $type::from($db_value);
}
throw new \Exception("Cannot coerce property '$property_name' of type '$type' to db value.");
}
/**
* Convert binary uuid to a string uuid (mysql compatible).
* @param $uuid a binary(16) uuid from MYSQL created via `UUID_TO_BIN( UUID() )`
* @return string a VARCHAR(36) compatible $uuid identical to `BIN_TO_UUID( binary_16_representation_of_uuid )`
*/
public function bin_to_uuid(string $uuid): string{
$hex = str_split(bin2hex($uuid), 4);
return
$hex[0].$hex[1].'-'
.$hex[2].'-'
.$hex[3].'-'
.$hex[4].'-'
.$hex[5].$hex[6].$hex[7]
;
}
/**
* Convert a string uuid to a binary uuid (mysql compatible).
* @param $uuid a VARCHAR(36) representation of a UUID, generated in MySql with `UUID()`
* @return string a BINARY(16) representation of a UUID, generated in MySql with `BIN_TO_UUID( UUID() )`
*/
public function uuid_to_bin(string $uuid): string {
return hex2bin(
str_replace('-','', $uuid)
);
}
}