Add own ORM without any dependencies, yay

This commit is contained in:
ohartl 2016-02-19 13:13:36 +01:00
parent 3b8466ab7f
commit a1e13a9919
2 changed files with 948 additions and 0 deletions

View file

@ -0,0 +1,691 @@
<?php
abstract class AbstractModel
{
/**
* Db table for find methods
*
* @var string
*/
public static $table;
/**
* Db id attribute for find methods
*
* @var string
*/
public static $idAttribute;
/**
* Mapping model attributes and database attributes for saving
*
* @var array
*/
protected $attributeDbAttributeMapping = array();
/**
* Setup db attribute mapping
*
* @param array $childMapping
*
* @return array
*
* @throws Exception
*/
protected function setupDbMapping($childMapping = array())
{
return array_replace(
array(
'id' => static::$idAttribute,
),
$childMapping
);
}
/**
* Format or do other things before saving
*
* @param array $data
*
* @return array
*/
protected function preSave($data)
{
return $data;
}
/**
* Hold all data from a model
*
* @var mixed
*/
protected $data = array();
/**
* Constructor.
*
* @param array $data
*/
protected function __construct($data)
{
$this->attributeDbAttributeMapping = $this->setupDbMapping();
if(isset($data[static::$idAttribute])){
$id = is_numeric($data[static::$idAttribute]) && strpos($data[static::$idAttribute], ',') === false
? intval($data[static::$idAttribute])
: $data[static::$idAttribute];
$this->setId($id);
}
}
/**
* Create a model from data
*
* @param array $data
*
* @return static|null The Model
*/
public static function create($data)
{
if(count($data) > 0){
return new static($data);
}
return null;
}
/**
* Create a model collection from data
*
* @param array $multiData
*
* @return ModelCollection|static[]
*/
public static function createMultiple($multiData = array())
{
$collection = new ModelCollection();
foreach($multiData as $data){
$model = static::create($data);
if(!is_null($model)){
if(is_null($model->getId())){
$collection->add($model);
}
else{
$collection->add($model, $model->getId());
}
}
}
return $collection;
}
/**
* @see create
*
* @param array $data
*
* @return AbstractModel|null
*/
public static function createAndSave($data)
{
$model = static::create($data);
if(!is_null($model) && $model->save()){
return $model;
}
return null;
}
/**
* @see createMultiple
*
* @param array $multiData
*
* @return ModelCollection|static[]
*/
public static function createMultipleAndSave($multiData = array())
{
$collection = new ModelCollection();
foreach($multiData as $data){
$model = static::createAndSave($data);
if(!is_null($model)){
$collection->add($model);
}
}
return $collection;
}
/**
* Create a model from mysqli result
*
* @param mysqli_result $result
*
* @return static|null
*/
public static function createFromDbResult($result)
{
if($result->num_rows === 0){
return null;
}
return static::create($result->fetch_assoc());
}
/**
* Create a model collection from mysqli result
*
* @param mysqli_result $result
*
* @return ModelCollection|static[]
*/
public static function createMultipleFromDbResult($result)
{
return static::createMultiple($result->fetch_all(MYSQLI_ASSOC));
}
/**
* @param string $attribute
* @param mixed $value
*/
public function setAttribute($attribute, $value)
{
$this->data[$attribute] = $value;
}
/**
* @param string $attribute
*
* @return mixed|null
*/
public function getAttribute($attribute)
{
if(isset($this->data[$attribute])){
if(is_array($this->data[$attribute])){
return array_map('strip_tags', $this->data[$attribute]);
}
else{
return strip_tags($this->data[$attribute]);
}
}
return null;
}
/**
* @return mixed
*/
public function getId()
{
return $this->getAttribute('id');
}
/**
* @param mixed $value
*/
protected function setId($value)
{
$this->setAttribute('id', $value);
}
/**
* @param array $attributes
*
* @return string
*/
protected static function sqlHelperAttributeList($attributes)
{
$sql = "%s";
$values = array();
$keywords = array('AS', 'ASC', 'DESC');
foreach($attributes as $val){
if(!is_array($val)){
// raw
$values[] = $val;
continue;
}
switch(count($val)){
case 1:
$values[] = "`{$val[0]}`";
break;
case 2:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]}";
}
else{
$values[] = "`{$val[0]}`.`{$val[1]}`";
}
break;
case 3:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]} `{$val[2]}`";
}
elseif(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]}";
}
break;
case 4:
if(in_array(strtoupper($val[1]), $keywords)){
$values[] = "`{$val[0]}` {$val[1]} `{$val[2]}`.`{$val[3]}`";
}
elseif(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]} `{$val[3]}`";
}
else{
$values[] = "`{$val[0]}`.`{$val[1]}` `{$val[2]}`.`{$val[3]}`";
}
break;
case 5:
if(in_array(strtoupper($val[2]), $keywords)){
$values[] = "`{$val[0]}`.`{$val[1]}` {$val[2]} `{$val[3]}`.`{$val[4]}`";
}
break;
}
}
return sprintf($sql, implode(', ', $values));
}
/**
* @param mixed $value
*
* @return string
*/
protected static function sqlHelperValue($value)
{
global $db;
if(is_null($value) || (is_string($value) && strtoupper($value) === 'NULL')){
return "NULL";
}
elseif(is_array($value)){
return static::sqlHelperValueList($value);
}
return "'{$db->escape_string($value)}'";
}
/**
* @param array $values
*
* @return string
*/
protected static function sqlHelperValueList($values)
{
$sql = "(%s)";
$sqlValues = array();
foreach($values as $val){
$sqlValues[] = static::sqlHelperValue($val);
}
return sprintf($sql, implode(', ', $sqlValues));
}
/**
* @param array $conditions
* array('attr', '=', '3') => "`attr` = '3'"
* array(
* array('`attr` = '3') (raw SQL) => `attr` = '3'
* array('attr', 3) => `attr` = '3'
* array('attr', '=', '3') => `attr` = '3'
* array('attr', '<=', 3) => `attr` <= '3'
* array('attr', 'LIKE', '%asd') => `attr` LIKE '%asd'
* array('attr', 'IS', null) => `attr` IS NULL
* array('attr', 'IS NOT', null) => `attr` IS NOT NULL
* )
* @param string $conditionConnector AND, OR
*
* @return string
*/
protected static function sqlHelperConditionList($conditions, $conditionConnector = 'AND')
{
$values = array();
// detect non nested array
if(count($conditions) > 0 && !is_array($conditions[0])){
$conditions = array($conditions);
}
$conditionConnector = strtoupper($conditionConnector);
if(in_array($conditionConnector, array('AND', 'OR'))){
$conditionConnector = " ".$conditionConnector;
}
$sql = "`%s` %s %s";
foreach($conditions as $val){
switch(count($val)){
case 1:
// raw
$values[] = $val;
break;
case 2:
$v = static::sqlHelperValue($val[1]);
$values[] = sprintf($sql, $val[0], "=", $v);
break;
case 3:
$v = static::sqlHelperValue($val[2]);
$values[] = sprintf($sql, $val[0], strtoupper($val[1]), $v);
break;
}
}
return implode($conditionConnector." ", $values);
}
/**
* @param array $conditions
* @param string $conditionConnector AND, OR
*
* @return string
*/
protected static function sqlHelperWhere($conditions, $conditionConnector = 'AND')
{
if(count($conditions) > 0){
$sql = " WHERE %s";
return sprintf($sql, static::sqlHelperConditionList($conditions, $conditionConnector));
}
return "";
}
/**
* @param array|null $orderBy Examples below:
* null => ""
* array() => ""
* array('attr1' => 'asc', 'attr2' => 'desc') => " ORDER BY `attr1` ASC, `attr2` DESC "
* array('attr1') => " ORDER BY `attr1` ASC "
*
* @return string
*/
protected static function sqlHelperOrderBy($orderBy = null)
{
if(!is_null($orderBy) && count($orderBy) > 0){
$sql = " ORDER BY %s";
$values = array();
foreach($orderBy as $key => $val){
if(is_int($key)){
$values[] = array($val);
}
else{
$values[] = array($key, strtoupper($val));
}
}
return sprintf($sql, static::sqlHelperAttributeList($values));
}
return "";
}
/**
* @param int|array $limit
* 0 => ""
* 3 => " LIMIT 3 "
* array(3, 4) => " LIMIT 3,4 "
*
* @return string
*/
protected static function sqlHelperLimit($limit = 0)
{
$sql = " LIMIT %s";
if(is_string($limit) || (is_int($limit) && $limit > 0)){
return sprintf($sql, $limit);
}
elseif(is_array($limit) && count($limit) == 2){
return sprintf($sql, $limit[0].",".$limit[1]);
}
return "";
}
/**
* Find all models by raw sql
*
* @param $sql
* @param null|string $useSpecificModel
*
* @return ModelCollection|static[]
*/
public static function findAllRaw($sql, $useSpecificModel = null)
{
global $db;
if(!$result = $db->query($sql)){
dbError($db->error, $sql);
}
if(is_null($useSpecificModel)){
return static::createMultipleFromDbResult($result);
}
elseif(class_exists($useSpecificModel)){
return call_user_func_array(array($useSpecificModel, 'createMultipleFromDbResult'), array($result));
}
return new ModelCollection();
}
/**
* Find a model by raw sql
*
* @param $sql
* @param null|string $useSpecificModel
*
* @return AbstractModel
*/
public static function findRaw($sql, $useSpecificModel = null)
{
global $db;
if(!$result = $db->query($sql)){
dbError($db->error, $sql);
}
if(is_null($useSpecificModel)){
return static::createFromDbResult($result);
}
elseif(class_exists($useSpecificModel)){
return call_user_func_array(array($useSpecificModel, 'createFromDbResult'), array($result));
}
return null;
}
/**
* Find all models
*
* @param array|null $orderBy see sqlHelperOrderBy
*
* @return ModelCollection|static[]
*/
public static function findAll($orderBy = null)
{
$sql = "SELECT * FROM `".static::$table."`"
.static::sqlHelperOrderBy($orderBy);
return static::findAllRaw($sql);
}
/**
* Find models by a condition
*
* @param array $conditions see sqlHelperConditionArray
* @param string $conditionConnector see sqlHelperConditionArray
* @param array|null $orderBy
* @param int $limit see sqlHelperLimit
*
* @return ModelCollection|static[]|AbstractModel|null
*/
public static function findWhere($conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
$sql = "SELECT * FROM `".static::$table."`"
.static::sqlHelperWhere($conditions, $conditionConnector)
.static::sqlHelperOrderBy($orderBy)
.static::sqlHelperLimit($limit);
if($limit === 1){
return static::findRaw($sql);
}
return static::findAllRaw($sql);
}
/**
* Find first model matching a condition
*
* @param array $conditions see sqlHelperConditionArray
* @param string $conditionConnector see sqlHelperConditionArray
* @param array|null $orderBy
*
* @return AbstractModel|null
*/
public static function findWhereFirst($conditions = array(), $conditionConnector = 'AND', $orderBy = null)
{
return static::findWhere($conditions, $conditionConnector, $orderBy, 1);
}
/**
* Find a model by id
*
* @param mixed $id
*
* @return AbstractModel|null
*/
public static function find($id)
{
return static::findWhereFirst(array(static::$idAttribute, $id));
}
/**
* Save model data to database
*
* @return bool
*/
public function save()
{
global $db;
$data = $this->preSave($this->data);
if(is_null($this->getId())){
// insert
$attributes = array();
$values = array();
foreach($this->attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$attributes[] = array($sqlAttribute);
$values[] = $data[$attribute];
}
$sql = "INSERT INTO `".static::$table."`"
." (".static::sqlHelperAttributeList($attributes).")"
." VALUES ".static::sqlHelperValueList($values);
}
else{
// update
$values = array();
foreach($this->attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$values[] = array($sqlAttribute, '=', $data[$attribute]);
}
$sql = "UPDATE `".static::$table."`"
." SET ".static::sqlHelperConditionList($values, ',')
.static::sqlHelperWhere(array(static::$idAttribute, $this->getId()));
}
if($stmt = $db->prepare($sql)){
if($stmt->execute()){
if(is_null($this->getId())){
$this->setId(intval($db->insert_id));
}
return true;
}
else{
dbError($db->error, $sql);
}
}
return false;
}
/**
* Delete model from database
*
* @return bool
*/
public function delete()
{
global $db;
if(!is_null($this->getId())){
$sql = "DELETE FROM `".static::$table."`"
.static::sqlHelperWhere(array(static::$idAttribute, $this->getId()));
if($stmt = $db->prepare($sql)){
if($stmt->execute()){
return true;
}
else{
dbError($db->error, $sql);
}
}
}
return false;
}
}

View file

@ -0,0 +1,257 @@
<?php
class ModelCollection implements Iterator, ArrayAccess, Countable
{
/**
* @var array|AbstractModel[]
*/
private $models = array();
/**
* Constructor.
*
* @param array|AbstractModel[] $array
*/
public function __construct($array = array())
{
if($this->isNumericArray($array)){
foreach($array as $model){
$this->add($model);
}
}
else{
foreach($array as $key => $model){
$this->add($model, $key);
}
}
}
/**
* @param array $array
*
* @return bool
*/
protected function isNumericArray($array)
{
return array_keys($array) === range(0, count($array) - 1)
&& count(array_filter($array, 'is_string')) === 0;
}
/**
* Adds a model to the collection,
* but will not replace if it exists with that key
*
* @param AbstractModel $model
* @param mixed|null $key
*/
public function add($model, $key = null)
{
if(is_null($model) || !($model instanceof AbstractModel)){
return;
}
if(is_null($key)){
$this->models[] = $model;
}
elseif(!$this->has($key)){
$this->models[$key] = $model;
}
}
/**
* Replace a model with given key
*
* @param AbstractModel $model
* @param mixed $key
*/
public function replace($model, $key)
{
if(is_null($model) || !($model instanceof AbstractModel)){
return;
}
$model[$key] = $model;
}
/**
* Delete a model by key
*
* @param mixed $key
*/
public function delete($key)
{
if($this->has($key)){
unset($this->models[$key]);
}
}
/**
* Check if collection has a model by key
*
* @param mixed $key
*
* @return bool
*/
public function has($key)
{
return isset($this->models[$key]);
}
/**
* Get a model from the collection by key
*
* @param mixed $key
*
* @return AbstractModel|null
*/
public function get($key)
{
if($this->has($key)){
return $this->models[$key];
}
return null;
}
/**
* Search a model in collection with a condition
*
* @param callable $callable Gives back if the search matches
*
* @return AbstractModel|null
*/
public function search($callable)
{
if(is_callable($callable)){
foreach($this->models as $model){
if($callable($model)){
return $model;
}
}
}
return null;
}
/**
* Search all models in collection with a condition
*
* @param callable $callable Gives back if the search matches
*
* @return static
*/
public function searchAll($callable)
{
$collection = new static;
if(is_callable($callable)){
foreach($this->models as $model){
if($callable($model)){
$collection->add($model);
}
}
}
return $collection;
}
/**
* @inheritdoc
*/
public function current()
{
return current($this->models);
}
/**
* @inheritdoc
*/
public function next()
{
return next($this->models);
}
/**
* @inheritdoc
*/
public function key()
{
return key($this->models);
}
/**
* @inheritdoc
*/
public function valid()
{
return $this->current() !== false;
}
/**
* @inheritdoc
*/
public function rewind()
{
reset($this->models);
}
/**
* @inheritdoc
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
/**
* @inheritdoc
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* @inheritdoc
*/
public function offsetSet($offset, $value)
{
$this->add($value, $offset);
}
/**
* @inheritdoc
*/
public function offsetUnset($offset)
{
$this->delete($offset);
}
/**
* @inheritdoc
*/
public function count()
{
return count($this->models);
}
}