yf_model classORM model.
| File | plugins/sys/classes/yf_model.class.php |
|---|---|
| Use | _class('model') |
A site sets them in its config with $PROJECT_CONF['model']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$_params |
null |
|
$_with |
null |
CREATED_AT | 'created_at' | |
UPDATED_AT | 'updated_at' |
public static function all()
Get all matching rows.
public static function all()
{
$obj = new static();
return $obj->new_query(['where' => func_get_args()])->get_all();
}
public function associate($model_instance)
Associate here means to auotmatically create foreign key on child model.
public function associate($model_instance)
{
// TODO
}
public function belongs_to($related, $foreign_key = null, $other_key = null, $relation = null)
Relation one-to-one inversed.
public function belongs_to($related, $foreign_key = null, $other_key = null, $relation = null)
{
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
$instance = $this->_db->model($related);
return $this->new_relation([
'type' => __FUNCTION__,
'related' => $related,
'relation' => $relation,
'foreign_key' => $foreign_key ?: strtolower($relation) . '_id',
'other_key' => $other_key ?: $instance->get_key_name(),
'query' => $instance->new_query(),
]);
}
public function belongs_to_many($related, $pivot_table = null, $foreign_key = null, $other_key = null, $relation = null)
Relation many-to-many.
public function belongs_to_many($related, $pivot_table = null, $foreign_key = null, $other_key = null, $relation = null)
{
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
$instance = $this->_db->model($related);
return $this->new_relation([
'type' => __FUNCTION__,
'related' => $related,
'relation' => $relation,
'pivot_table' => $pivot_table ?: $this->joining_table($related),
'foreign_key' => $foreign_key ?: $this->get_foreign_key(),
'other_key' => $other_key ?: $instance->get_foreign_key(),
'query' => $instance->new_query(),
]);
}
public static function count()
Count number of matching records, according to condition.
public static function count()
{
$obj = new static();
return (int) $obj->new_query(['where' => func_get_args()])->count();
}
public static function create(array $data)
Create new model record inside database.
$data array public static function create(array $data)
{
$obj = new static();
$insert_id = $obj->new_query()->insert($data);
if ( ! $insert_id) {
return null;
}
$obj->set_key($insert_id);
return $obj->find($insert_id);
}
public function delete()
Delete matching record(s) from database.
public function delete()
{
$where = func_get_args() ?: $this->_key_where(__FUNCTION__);
if ( ! $where) {
return false;
}
return $this->new_query(['where' => $where])->limit(1)->delete();
}
public static function destroy()
Delete matching record(s) from database, quicker method than delete().
public static function destroy()
{
// without ids the builder gets no condition at all, which would delete the whole table
if ( ! func_get_args()) {
trigger_error(static::class . '::destroy(): no ids given, nothing done', E_USER_WARNING);
return false;
}
$obj = new static();
return $obj->new_query(['where' => func_get_args()])->delete();
}
public function filter_form($data = [], $params = [])
Linking with the form builder.
public function filter_form($data = [], $params = [])
{
$filter_name = ($params['filter_name'] ?? null) ?: ($_GET['object'] ?? null) . '__' . ($_GET['action'] ?? null);
$a = [
'form_action' => url_admin('/@object/filter_save/' . $filter_name),
'clear_url' => url_admin('/@object/filter_save/' . $filter_name . '/clear'),
];
$params['selected'] = ($params['selected'] ?? null) ?: ($_SESSION[$filter_name] ?? null);
return form($a + (array) $data, $params);
}
public static function find()
Search for model data, according to args array, returning first record Usuallly get by primary key, but possible to use complex conditions.
public static function find()
{
$obj = new static();
$pk = $obj->get_key_name();
$result = $obj->new_query(['where' => func_get_args()])->get();
if ( ! $result || ! $result->$pk) {
return null;
}
$obj->set_key($result->$pk);
$obj->set_data($result);
return $result;
}
public static function first_or_create()
Return first matched row or create such one, if not existed.
public static function first_or_create()
{
$obj = new static();
$args = func_get_args();
$first = $obj->new_query([
'where' => $args,
'order_by' => $obj->get_key_name() . ' asc',
'limit' => 1,
])->get();
if (is_object($first)) {
$obj->set_data($first);
return $first;
}
return call_user_func_array([$obj, 'create'], $args);
}
public static function first_or_new()
Return first matched row or create empty model object.
public static function first_or_new()
{
$obj = new static();
$args = func_get_args();
$first = $obj->new_query([
'where' => $args,
'order_by' => $obj->get_key_name() . ' asc',
'limit' => 1,
])->get();
if (is_object($first)) {
$obj->set_data($first);
return $first;
}
return call_user_func_array([$obj, 'new_instance'], $args);
}
public function form($whereid, $data = [], $params = [])
Linking with the form builder.
public function form($whereid, $data = [], $params = [])
{
$a = (array) $this->new_query((array) $params['query_builder'])->whereid($whereid)->get();
return form($a + (array) $data, $params);
}
public function get_data()
Return current model data.
public function get_data()
{
$data = [];
foreach (get_object_vars($this) as $var => $value) {
if (substr($var, 0, 1) === '_') {
continue;
}
$data[$var] = $value;
}
return $data;
}
public function get_foreign_key()
Default model foreign key.
public function get_foreign_key()
{
return strtolower(class_basename($this, '', '_model')) . '_id';
}
public function get_key()
Primary key value.
public function get_key()
{
return $this->_primary_id;
}
public function get_key_name()
Primary key name.
public function get_key_name()
{
if (isset($this->_primary_key)) {
return $this->_primary_key;
}
$table = $this->get_table();
$utils = $this->_db->utils();
if ($table && $utils->table_exists($table)) {
$primary_index = $utils->index_info($table, 'PRIMARY');
}
if ( ! isset($primary_index['columns'])) {
return null;
}
$name = current($primary_index['columns']);
return $this->set_key_name($name);
}
public function get_table()
public function get_table()
{
$name = $this->_table;
if ( ! $name) {
$name = strtolower(class_basename($this, '', '_model'));
if ($name === 'model') {
return false;
}
$this->set_table($name);
}
return $this->_db->_fix_table_name($name);
}
public function has($relation, $where = [])
Relation querying method $posts = model('post')->has('comments', '>=', 3)->get();.
public function has($relation, $where = [])
{
// TODO
return $this;
}
public function has_many($related, $foreign_key = null, $local_key = null, $relation = null)
Relation one-to-many.
public function has_many($related, $foreign_key = null, $local_key = null, $relation = null)
{
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
$instance = $this->_db->model($related);
return $this->new_relation([
'type' => __FUNCTION__,
'related' => $related,
'relation' => $relation,
'foreign_key' => $foreign_key ?: $this->get_foreign_key(),
'local_key' => $local_key ?: $instance->get_key_name(),
'query' => $instance->new_query(),
]);
}
public function has_many_through($related, $through_model, $foreign_key = null, $local_key = null, $relation = null)
Relation distant through other model.
public function has_many_through($related, $through_model, $foreign_key = null, $local_key = null, $relation = null)
{
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
$instance = $this->_db->model($related);
return $this->new_relation([
'type' => __FUNCTION__,
'related' => $related,
'relation' => $relation,
'through_model' => $through_model,
'foreign_key' => $instance->get_table() . '.' . ($foreign_key ?: $this->get_foreign_key()),
'local_key' => $local_key ?: $instance->get_key_name(),
'query' => $instance->new_query(),
]);
}
public function has_one($related, $foreign_key = null, $local_key = null, $relation = null)
Relation one-to-one.
public function has_one($related, $foreign_key = null, $local_key = null, $relation = null)
{
if ($relation === null) {
list(, $caller) = debug_backtrace(false);
$relation = $caller['function'];
}
$instance = $this->_db->model($related);
return $this->new_relation([
'type' => __FUNCTION__,
'related' => $related,
'relation' => $relation,
'foreign_key' => $foreign_key ?: $this->get_foreign_key(),
'local_key' => $local_key ?: $instance->get_key_name(),
'query' => $instance->new_query(),
]);
}
public function html($name, $params = [])
Html widget connetion.
public function html($name, $params = [])
{
// TODO
}
public function joining_table($related)
Get the joining table name for a many-to-many relation.
public function joining_table($related)
{
$base = class_basename($this, '', '_model');
$related = class_basename($related, '', '_model');
$models = [$related, $base];
// Now that we have the model names in an array we can just sort them and
// use the implode function to join them together with an underscores,
// which is typically used by convention within the database system.
sort($models);
$table = strtolower(implode('_', $models));
return $this->_db->_fix_table_name($table);
}
public function morph_many()
Relation polymorphic one-to-many. Not implemented (polymorphic relations); returns null. Removal or implementation is a 4.0 decision.
public function morph_many()
{
return null;
}
public function morph_one()
Relation polymorphic one-to-one. Not implemented (polymorphic relations); returns null. Removal or implementation is a 4.0 decision.
public function morph_one()
{
return null;
}
public function morph_to()
Relation polymorphic one-to-many. Not implemented (polymorphic relations); returns null. Removal or implementation is a 4.0 decision.
public function morph_to()
{
return null;
}
public function morph_to_many()
Relation polymorphic many-to-many. Not implemented (polymorphic relations); returns null. Removal or implementation is a 4.0 decision.
public function morph_to_many()
{
return null;
}
public function morphed_by_many()
Relation polymorphic many-to-many. Not implemented (polymorphic relations); returns null. Removal or implementation is a 4.0 decision.
public function morphed_by_many()
{
return null;
}
public function new_instance($args = [])
Return new instance of the given model.
public function new_instance($args = [])
{
$model = new static($args);
return $model;
}
public function new_query($params = [])
Return new query builder instance.
public function new_query($params = [])
{
if (($params['where'] ?? null) === null) {
unset($params['where']);
}
$table = ($params['table'] ?? null) ?: $this->get_table();
if ( ! $table) {
throw new Exception('MODEL: ' . get_called_class() . ': requires table name to make queries');
}
$builder = $this->_db->query_builder();
$builder->_model = $this;
$builder->_with = $this->_with;
$builder->_result_wrapper = [$this, 'new_result'];
$builder->_remove_as_from_delete = true;
$builder->from($table . ' AS t0');
foreach (['select', 'where', 'where_or', 'whereid', 'order_by', 'having', 'group_by'] as $part) {
if ($params[$part] ?? null) {
call_user_func_array([$builder, $part], is_array($params[$part]) ? $params[$part] : [$params[$part]]);
}
}
// limit => [10,30] or limit => 5
if ($params['limit'] ?? null) {
$count = is_numeric($params['limit']) ? $params['limit'] : $params['limit'][0];
$offset = is_numeric($params['limit']) ? null : $params['limit'][1];
$builder->limit($count, $offset);
}
foreach (['join', 'left_join', 'inner_join', 'right_join'] as $func) {
if ($params[$func] ?? null) {
$join = $params[$func];
$builder->$func($join['table'], $join['on']);
}
}
return $builder;
}
public function new_relation($relation)
Return new instance of model relation.
public function new_relation($relation)
{
return new yf_model_relation($relation, $this);
}
public function new_result($result = [])
Return new instance of model result.
public function new_result($result = [])
{
return new yf_model_result($result, $this);
}
public static function query()
Query builder custom constructor.
public static function query()
{
$obj = new static();
return $obj->new_query(func_get_args());
}
public function save()
Save model back into database.
public function save()
{
$data = $this->get_data();
$pk = $this->get_key_name();
if ( ! ($data[$pk] ?? null)) {
$insert_id = $this->new_query()->insert($data);
if ( ! $insert_id) {
return null;
}
$data[$pk] = $insert_id;
$this->set_data($data);
$this->set_key($insert_id);
return $insert_id;
}
$this->set_key($data[$pk]);
if (isset($data[self::UPDATED_AT])) {
$data[self::UPDATED_AT] = date('Y-m-d H:i:s');
}
return $this->new_query(['whereid' => $this->get_key()])->update($data);
}
public static function select()
Query builder custom constructor.
public static function select()
{
$obj = new static();
return $obj->new_query([__FUNCTION__ => func_get_args()]);
}
public function set($name, $value = null)
Set model attribute, example: (new bear)->set('name', 'Joe').
public function set($name, $value = null)
{
if (is_array($name)) {
$func = __FUNCTION__;
foreach ($name as $k => $v) {
$this->$func($k, $v);
}
} else {
$mutator = 'set_attr_' . $name;
if (method_exists($this, $mutator)) {
$this->$mutator($value);
} else {
$this->$name = $value;
}
}
return $this;
}
public function set_data($data = [])
Set current model data.
public function set_data($data = [])
{
if ($data instanceof yf_model_result) {
$data = $data->get_data();
}
foreach ((array) $data as $k => $v) {
if (substr($k, 0, 1) === '_') {
continue;
}
$this->$k = $v;
}
$pk = $this->get_key_name();
if ($pk !== null && isset($data[$pk])) {
$this->set_key($data[$pk]);
}
}
public function set_db_object($db = null)
public function set_db_object($db = null)
{
$this->_db = $db ?: db();
return $this;
}
public function set_key($id)
Primary key value set.
public function set_key($id)
{
$this->_primary_id = $id;
return $this->_primary_id;
}
public function set_key_name($name)
Primary key name set.
public function set_key_name($name)
{
$this->_primary_key = $name;
return $this->_primary_key;
}
public function set_table($name)
public function set_table($name)
{
$this->_table = $name;
return $this->_table;
}
public function table($params = [])
Linking with the table builder.
public function table($params = [])
{
$sql = $this->new_query((array) $params['query_builder'])->sql();
$filter_name = ($params['filter_name'] ?? null) ?: (($this->_params['filter_name'] ?? null) ?: ($_GET['object'] ?? null) . '__' . ($_GET['action'] ?? null));
$params['filter'] = ($params['filter'] ?? null) ?: (($this->_params['filter'] ?? null) ?: ($_SESSION[$filter_name] ?? null));
return table($sql, $params);
}
public function touch()
Update only model's timestamps.
public function touch()
{
$where = func_get_args() ?: $this->_key_where(__FUNCTION__);
if ( ! $where) {
return false;
}
return $this->new_query(['where' => $where])->update([self::UPDATED_AT => date('Y-m-d H:i:s')]);
}
public function validate($rules = [], $params = [])
Model validation will be here.
public function validate($rules = [], $params = [])
{
// TODO
}
public static function where()
Query builder custom constructor.
public static function where()
{
$obj = new static();
return $obj->new_query([__FUNCTION__ => func_get_args()]);
}
public function with($model)
Eager loading with relations. Examples:.
model('post')->with('comments')->whereid($id)->first();
foreach (model('book')->with('author')->get_all() as $book) { // select * from authors where id in (1, 2, 3, 4, 5, ...)
echo $book->author->name;
}
public function with($model)
{
// TODO
return $this;
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
$where_prefix = 'where_';
$scope_prefix = 'scope_';
$get_prefix = 'get_attr_';
$set_prefix = 'set_attr_';
if (strpos($name, $where_prefix) !== false) {
$name = substr($name, strlen($where_prefix));
array_unshift($args, 't0.' . $name);
return call_user_func_array([$this, 'where'], $args);
} elseif (strpos($name, $scope_prefix) !== false) {
if (method_exists($this, $name)) {
return call_user_func_array([$this, $name], $args);
}
} elseif (strpos($name, $get_prefix) !== false) {
$accessor = $get_prefix . $name;
if (method_exists($this, $accessor)) {
return $this->$accessor($args);
}
} elseif (strpos($name, $set_prefix) !== false) {
$mutator = $set_prefix . $name;
if (method_exists($this, $mutator)) {
return $this->$mutator($args);
}
}
return main()->extend_call($this, $name, $args);
}
public static function __callStatic($method, $args)
Catch static calls.
public static function __callStatic($method, $args)
{
$where_prefix = 'where_';
$scope_prefix = 'scope_';
$obj = new static();
$name = $args[0] ?? '';
if (strpos($name, $where_prefix) !== false) {
$name = substr($name, strlen($where_prefix));
array_unshift($args, 't0.' . $name);
return call_user_func_array([$obj, 'where'], $args);
} elseif (strpos($name, $scope_prefix) !== false) {
if (method_exists($obj, $name)) {
return call_user_func_array([$obj, $name], $args);
}
}
return call_user_func_array([$obj, $method], $args);
}
public function __clone()
We cleanup object properties when cloning.
public function __clone()
{
$persist_properties = [
'_table',
'_fillable',
];
foreach ((array) get_object_vars($this) as $k => $v) {
if ( ! in_array($k, $persist_properties)) {
$this->$k = null;
}
}
}
public function __construct($args = [], $params = [])
public function __construct($args = [], $params = [])
{
$this->set_db_object($params['db'] ?? null);
$this->set_data($args);
}
public function __get($name)
public function __get($name)
{
if (method_exists($this, $name)) {
return $this->$name()?->get_data();
}
if (isset($this->$name)) {
return $this->$name;
}
return null;
}
public function __toString(): string
public function __toString()
{
return json_encode($this->get_data());
}
public function _key_where($caller = '')
Where-clause for the loaded record. Without arguments delete() / touch() act on this record only: an empty where-clause is skipped by the builder, which turned them into "DELETE ... LIMIT 1" / an update of every row.
$caller string public function _key_where($caller = '')
{
$pk = $this->get_key_name();
$id = $pk ? ($this->get_data()[$pk] ?? $this->get_key()) : null;
$table = $this->get_table();
$primary = $pk && $table ? $this->_db->utils()->index_info($table, 'PRIMARY') : [];
// the builder also drops "pk = 0" (empty value), and a composite key would match several rows
if ( ! $pk || empty($id) || ! is_scalar($id) || count((array) ($primary['columns'] ?? [])) > 1) {
trigger_error(static::class . '::' . $caller . '(): no single primary key value, nothing done', E_USER_WARNING);
return [];
}
return [$pk, '=', is_string($id) ? $this->_db->es_for_builder($id) : $id];
}