<?php
namespace Tlf;
use PDO;
/**
* Minimal ORM implementation.
*
*
* Declare `$protected ClassName $_article;` and `public int $article_id;` to automagically make `$obj->article` load the related article as a BigOrm object.
* @tagline (Alpha version) A minimalist ORM for mapping arrays of data to objects with magic getters & some convenience methods
*/
#[\AllowDynamicProperties]
class BigOrm {
protected array $_cache = [];
protected \Tlf\BigDb $bdb;
protected string $table;
public function __construct(\Tlf\BigDb $bdb, array $row = []){
if (!isset($this->table)){
$class = get_class($this);
$parts = explode('\\', $class);
$this->table = strtolower(array_pop($parts));
}
$this->bdb = $bdb;
$row = $this->sanitize($row);
foreach ($row as $k=>$v){
$this->$k = $v;
}
}
/**
* A hook for sanitizing row data passed to `__construct()`. The built-in version of this method does nothing.
*
* @param $row a row of data that has not been sanitized
* @return array of safe data
* @override Builtin version does nothing
*/
public function sanitize(array $row): array {
return $row;
}
/**
* Called at end of `save()`, after the update/insert is finished AND id is set (in case of an INSERT)
* @override
*/
public function didSave(){}
/**
* Store current object in database.
* UPDATE if `get_saveable_row()` returns an `id`. INSERT otherwise
*/
public function save(){
$row = [];
$row = $this->get_saveable_row();
if (isset($row['id'])){
$this->bdb->ldb->update($this->table, $row, 'id');
} else {
$id = $this->bdb->ldb->insert($this->table, $row, 'id');
$this->id = $id;
}
$this->didSave();
}
/**
* Returns array of all dynamic properties and properties explicitly defined on the BigOrm subclass.
* @override for custom saveable row
* @return array of key/value pairs to store in the database
*/
public function get_saveable_row(): array{
$row = [];
$obj_vars = array_keys(get_object_vars($this));
$defined_properties = array_keys(get_class_vars(get_class($this)));
$all_props = array_merge($obj_vars, $defined_properties);
$bigorm_props = array_keys(get_class_vars(self::class));
$storable = array_diff($all_props, $bigorm_props);
foreach ($storable as $prop){
if (substr($prop,0,1)=='_')continue;
$row[$prop] = $this->$prop ?? null;
}
return $row;
}
/**
* Create an object
*/
public function object($name, array $row){
$class = get_class($this);
$parts = explode("\\",$class);
array_pop($parts);
$namespace = implode("\\", $parts);
$class = $namespace.'\\'.ucfirst($name);
if (class_exists($class)){
return new $class($row);
}
return (object)$row;
}
/**
* Get an array of objects from rows
* @param $rows an array of rows
* @return array of objects
*/
static public function from_rows(array $rows){
$list = [];
foreach ($rows as $r){
$class = static::class;
$list[] = new $class($r);
}
return $list;
}
/**
* Call `getPropName()` or magically load a related prop if property `_propName` is declared AND property `propName_id` is set
*/
public function __get(string $prop){
// return 'nothing';
$value = $this->property_getter($prop);
if ($value!==null)return $value;
$value = $this->property_related($prop);
if ($value!==null)return $value;
// @todo probably throw an exception here
return $value;
}
/**
* Call `setPropName()` if the setter method exists. Otherwise set dynamic property.
* If the property is defined on the class & no setter is available, then an exception is thrown.
*/
public function __set(string $prop, $value){
$method = 'set'.ucfirst($prop);
if (method_exists($this,$method)){
$this->$method($value);
return;
}
$class = get_class($this);
if (property_exists($class,$prop)){
throw new \Exception("Property '$prop' is not accessible on $class, and property setter '$method' does not exist.");
}
$this->$prop = $value;
}
public function property_related(string $prop): ?\Tlf\BigOrm {
$magic_prop = "_".$prop;
if (!property_exists(get_class($this),$magic_prop))return null;
if ($this->$magic_prop!=null)return $this->$magic_prop;
$id_prop = $prop.'_id';
// $use_magic_loader = !isset($this->$magic_prop) && isset($this->$id_prop);
//@todo maybe throw exception if the properties don't exist
if (!isset($this->$id_prop))return null;
$item = $this->bdb->one($prop, $this->$id_prop);
$this->$magic_prop = $item;
return $item;
}
public function property_getter($prop){
if (substr($prop,0,1)=='_')return false;
if (method_exists($this,$mthd='get'.ucfirst($prop))){
return $this->$mthd();
}
return null;
}
}