exists(); $self = new Database; $self->name = $name; $self->setFields(); $self->setPending(); return $self; } /** * Get rows from table * @uses Lazer\Classes\Helpers\Data::get() to get data from file * @return array */ protected function getData() { return Helpers\Data::table($this->name)->get(); } /** * Setting data to Database::$data */ protected function setData() { $this->data = $this->getData(); } /** * Returns array key of row with specified ID * @param integer $id Row ID * @return integer Row key * @throws LazerException If there's no data with that ID */ protected function getRowKey($id) { foreach ($this->getData() as $key => $data) { if ($data->id == $id) { return $key; break; } } throw new LazerException('No data found with ID: ' . $id); } /** * Set NULL for currentId and currentKey */ protected function clearKeyInfo() { $this->currentId = $this->currentKey = NULL; } /** * Setting fields with default values * @uses Lazer\Classes\Helpers\Validate::isNumeric() to check if type of field is numeric */ protected function setFields() { $this->set = new \stdClass(); $schema = $this->schema(); foreach ($schema as $field => $type) { if (Helpers\Validate::isNumeric($type) AND $field != 'id') { $this->set->{$field} = 0; } else { $this->set->{$field} = null; } } } /** * Set pending functions in right order with default values (Empty). */ protected function setPending() { $this->pending = array( 'where' => array(), 'orderBy' => array(), 'limit' => array(), 'with' => array(), 'groupBy' => array(), ); } /** * Clear info about previous queries */ protected function clearQuery() { $this->setPending(); $this->clearKeyInfo(); } /** * Validating fields and setting variables to current operations * @uses Lazer\Classes\Helpers\Validate::field() to check that field exist * @uses Lazer\Classes\Helpers\Validate::type() to check that field type is correct * @param string $name Field name * @param mixed $value Field value */ public function __set($name, $value) { if (Helpers\Validate::table($this->name)->field($name) && Helpers\Validate::table($this->name)->type($name, $value)) { $this->set->{$name} = $value; } } /** * Returning variable from Object * @param string $name Field name * @return mixed Field value * @throws LazerException */ public function __get($name) { if (isset($this->set->{$name})) return $this->set->{$name}; throw new LazerException('There is no data'); } /** * Check if the given field exists * @param string $name Field name * @return boolean True if the field exists, false otherwise */ public function __isset($name) { return isset($this->set->{$name}); } /** * Execute pending functions */ protected function pending() { $this->setData(); foreach ($this->pending as $func => $args) { if (!empty($args)) { call_user_func(array($this, $func . 'Pending')); } } //clear pending values after executed query $this->clearQuery(); } /** * Creating new table * * For example few fields: * * Database::create('news', array( * 'title' => 'string', * 'content' => 'string', * 'rating' => 'double', * 'author' => 'integer' * )); * * Types of field: * - boolean * - integer * - string * - double (also for float type) * * ID field isn't required (it will be created automatically) but you can specify it at first place. * * @uses Lazer\Classes\Helpers\Data::arrToLower() to lower case keys and values of array * @uses Lazer\Classes\Helpers\Data::exists() to check if data file exists * @uses Lazer\Classes\Helpers\Config::exists() to check if config file exists * @uses Lazer\Classes\Helpers\Validate::types() to check if type of fields are correct * @uses Lazer\Classes\Helpers\Data::put() to save data file * @uses Lazer\Classes\Helpers\Config::put() to save config file * @param string $name Table name * @param array $fields Field configuration * @throws LazerException If table exist */ public static function create($name, array $fields) { $fields = Helpers\Validate::arrToLower($fields); if (Helpers\Data::table($name)->exists() && Helpers\Config::table($name)->exists()) { throw new LazerException('helper\Table "' . $name . '" already exists'); } $types = array_values($fields); Helpers\Validate::types($types); if (!array_key_exists('id', $fields)) { $fields = array('id' => 'integer') + $fields; } $data = new \stdClass(); $data->last_id = 0; $data->schema = $fields; $data->relations = new \stdClass(); Helpers\Data::table($name)->put(array()); Helpers\Config::table($name)->put($data); } /** * Removing table with config * @uses Lazer\Classes\Helpers\Data::remove() to remove data file * @uses Lazer\Classes\Helpers\Config::remove() to remove config file * @param string $name Table name * @return boolean|LazerException */ public static function remove($name) { if (Helpers\Data::table($name)->remove() && Helpers\Config::table($name)->remove()) { return TRUE; } return FALSE; } /** * Grouping results by one field * @param string $column * @return \Lazer\Classes\Core_Database */ public function groupBy($column) { if (Helpers\Validate::table($this->name)->field($column)) { $this->resetKeys = 0; $this->pending[__FUNCTION__] = $column; } return $this; } /** * Grouping array pending method */ protected function groupByPending() { $column = $this->pending['groupBy']; $grouped = array(); foreach ($this->data as $object) { $grouped[$object->{$column}][] = $object; } $this->data = $grouped; } /** * JOIN other tables * @param string $table relations separated by : * @return \Lazer\Classes\Core_Database */ public function with($table) { $this->pending['with'][] = explode(':', $table); return $this; } /** * Pending function for with(), joining other tables to current */ protected function withPending() { $joins = $this->pending['with']; foreach ($joins as $join) { $local = (count($join) > 1) ? array_slice($join, -2, 1)[0] : $this->name; $foreign = end($join); $relation = Relation::table($local)->with($foreign); $data = $this->data; foreach ($join as $part) { $data = $relation->build($data, $part); } } } /** * Sorting data by field * @param string $key Field name * @param string $direction ASC|DESC * @return \Lazer\Classes\Core_Database */ public function orderBy($key, $direction = 'ASC') { if (Helpers\Validate::table($this->name)->field($key)) { $directions = array( 'ASC' => SORT_ASC, 'DESC' => SORT_DESC ); $this->pending[__FUNCTION__][$key] = isset($directions[$direction]) ? $directions[$direction] : 'ASC'; } return $this; } /** * Sort an array of objects by more than one field. * @ * @link http://blog.amnuts.com/2011/04/08/sorting-an-array-of-objects-by-one-or-more-object-property/ It's not mine algorithm */ protected function orderByPending() { $properties = $this->pending['orderBy']; uasort($this->data, function($a, $b) use ($properties) { foreach ($properties as $column => $direction) { if (is_int($column)) { $column = $direction; $direction = SORT_ASC; } $collapse = function($node, $props) { if (is_array($props)) { foreach ($props as $prop) { $node = (!isset($node->$prop)) ? null : $node->$prop; } return $node; } else { return (!isset($node->$props)) ? null : $node->$props; } }; $aProp = $collapse($a, $column); $bProp = $collapse($b, $column); if ($aProp != $bProp) { return ($direction == SORT_ASC) ? strnatcasecmp($aProp, $bProp) : strnatcasecmp($bProp, $aProp); } } return FALSE; }); } /** * Where function, like SQL * * Operators: * - Standard operators (=, !=, >, <, >=, <=) * - IN (only for array value) * - NOT IN (only for array value) * * @param string $field Field name * @param string $op Operator * @param mixed $value Field value * @return \Lazer\Classes\Core_Database */ public function where($field, $op, $value) { $this->pending['where'][] = array( 'type' => 'and', 'field' => $field, 'op' => $op, 'value' => $value, ); return $this; } /** * Alias for where() * @param string $field Field name * @param string $op Operator * @param mixed $value Field value * @return \Lazer\Classes\Core_Database */ public function andWhere($field, $op, $value) { $this->where($field, $op, $value); return $this; } /** * Alias for where(), setting OR for searching * @param string $field Field name * @param string $op Operator * @param mixed $value Field value * @return \Lazer\Classes\Core_Database */ public function orWhere($field, $op, $value) { $this->pending['where'][] = array( 'type' => 'or', 'field' => $field, 'op' => $op, 'value' => $value, ); return $this; } /** * Filter function for array_filter() in where() * @return boolean */ protected function wherePending() { $operator = array( '=' => '==', '!=' => '!=', '>' => '>', '<' => '<', '>=' => '>=', '<=' => '<=', 'and' => '&&', 'or' => '||' ); $this->data = array_filter($this->data, function($row) use ($operator) { $clause = ''; $result = true; foreach ($this->pending['where'] as $key => $condition) { extract($condition); if (is_array($value) && $op == 'IN') { $value = (in_array($row->{$field}, $value)) ? 1 : 0; $op = '=='; $field = 1; } elseif (!is_array($value) && in_array($op, array('LIKE', 'like'))) { $regex = "/^" . str_replace('%', '(.*?)', preg_quote($value)) . "$/si"; $value = preg_match($regex, $row->{$field}); $op = '=='; $field = 1; } elseif (!is_array($value) && $op != 'IN') { $value = is_string($value) ? '\'' . mb_strtolower($value) . '\'' : $value; $op = $operator[$op]; $field = is_string($row->{$field}) ? 'mb_strtolower($row->' . $field .')' : '$row->' . $field; } $type = (!$key) ? null : $operator[$type]; $query = array($type, $field, $op, $value); $clause .= implode(' ', $query) . ' '; eval('$result = ' . $clause . ';'); } return $result; }); } /** * Returning data as indexed or assoc array. * @param string $key Field that will be the key, NULL for Indexed * @param string $value Field that will be the value * @return array */ public function asArray($key = null, $value = null) { if (!is_null($key)) { Helpers\Validate::table($this->name)->field($key); } if (!is_null($value)) { Helpers\Validate::table($this->name)->field($value); } $datas = array(); if (!$this->resetKeys) { if (is_null($key) && is_null($value)) { return $this->data; } else { foreach ($this->data as $rowKey => $data) { $datas[$rowKey] = array(); foreach ($data as $row) { if (is_null($key)) { $datas[$rowKey][] = $row->{$value}; } elseif (is_null($value)) { $datas[$rowKey][$row->{$key}] = $row; } else { $datas[$rowKey][$row->{$key}] = $row->{$value}; } } } } } else { if (is_null($key) && is_null($value)) { foreach ($this->data as $data) { $datas[] = get_object_vars($data); } } else { foreach ($this->data as $data) { if (is_null($key)) { $datas[] = $data->{$value}; } elseif (is_null($value)) { $datas[$data->{$key}] = $data; } else { $datas[$data->{$key}] = $data->{$value}; } } } } return $datas; } /** * Limit returned data * * Should be used at the end of chain, before end method * @param integer $number Limit number * @param integer $offset Offset number * @return \Lazer\Classes\Core_Database */ public function limit($number, $offset = 0) { $this->pending['limit'] = array( 'offset' => $offset, 'number' => $number ); return $this; } /** * Pending function for limit() */ protected function limitPending() { $offset = $this->pending['limit']['offset']; $num = $this->pending['limit']['number']; $this->data = array_slice($this->data, $offset, $num); } /** * Add new fields to table, array schema like in create() function * @param array $fields Associative array */ public function addFields(array $fields) { $fields = Helpers\Validate::arrToLower($fields); Helpers\Validate::types(array_values($fields)); $schema = $this->schema(); $fields = array_diff_assoc($fields, $schema); if (!empty($fields)) { $config = $this->config(); $config->schema = array_merge($schema, $fields); $data = $this->getData(); foreach ($data as $key => $object) { foreach ($fields as $name => $type) { if (Helpers\Validate::isNumeric($type)) $data[$key]->{$name} = 0; else $data[$key]->{$name} = null; } } Helpers\Data::table($this->name)->put($data); Helpers\Config::table($this->name)->put($config); } } /** * Delete fields from array * @param array $fields Indexed array */ public function deleteFields(array $fields) { $fields = Helpers\Validate::arrToLower($fields); Helpers\Validate::table($this->name)->fields($fields); $config = $this->config(); $config->schema = array_diff_key($this->schema(), array_flip($fields)); $data = $this->getData(); foreach ($data as $key => $object) { foreach ($fields as $name) { unset($data[$key]->{$name}); } } Helpers\Data::table($this->name)->put($data); Helpers\Config::table($this->name)->put($config); } /** * Returns table name * @return string table name */ public function name() { return $this->name; } /** * Returning object with config for table * @return object Config */ public function config() { return Helpers\Config::table($this->name)->get(); } /** * Return array with names of fields * @return array Fields */ public function fields() { return Helpers\Config::table($this->name)->fields(); } /** * Returning assoc array with types of fields * @return array Fields type */ public function schema() { return Helpers\Config::table($this->name)->schema(); } /** * Returning assoc array with relationed tables * @param string|null $tableName * @return array Fields type */ public function relations($tableName = null) { return Helpers\Config::table($this->name)->relations($tableName, true); } /** * Returning last ID from table * @return integer Last ID */ public function lastId() { return Helpers\Config::table($this->name)->lastId(); } /** * Saving inserted or updated data */ public function save() { $data = $this->getData(); if (!$this->currentId) { $config = $this->config(); $config->last_id++; $this->set->id = $config->last_id; array_push($data, $this->set); Helpers\Config::table($this->name)->put($config); } else { $this->set->id = $this->currentId; $data[$this->currentKey] = $this->set; } Helpers\Data::table($this->name)->put($data); // $this->setFields(); } /** * Deleting loaded data * @return boolean */ public function delete() { $data = $this->getData(); if (isset($this->currentId)) { unset($data[$this->currentKey]); } else { $this->pending(); $old = $data; $data = array_diff_key($old, $this->data); } $this->data = array_values($data); return Helpers\Data::table($this->name)->put($this->data) ? true : false; } /** * Return count in integer or array of integers (if grouped) * @return mixed */ public function count() { if (!$this->resetKeys) { $count = array(); foreach ($this->data as $group => $data) { $count[$group] = count($data); } } else { $count = count($this->data); } return $count; } /** * Returns one row with specified ID * @param integer $id Row ID * @return \Lazer\Classes\Core_Database */ public function find($id = NULL) { if ($id !== NULL) { $data = $this->getData(); $this->currentId = $id; $this->currentKey = $this->getRowKey($id); foreach ($data[$this->currentKey] as $field => $value) { $this->set->{$field} = $value; } } else { $this->limit(1)->findAll(); $data = $this->data; if (count($data)) { foreach ($data[0] as $field => $value) { $this->set->{$field} = $value; } $this->currentId = $this->set->id; $this->currentKey = $this->getRowKey($this->currentId); } } return clone $this; } /** * Make data ready to read */ public function findAll() { $this->pending(); $this->data = $this->resetKeys ? array_values($this->data) : $this->data; return clone $this; } /** * Iterator for Data * @return \ArrayIterator */ public function getIterator() { return new \ArrayIterator($this->data); } /** * Debug functions, prints whole query with values */ public function debug() { $print = "Lazer::table(" . $this->name . ")\n"; foreach ($this->pending as $function => $values) { if (!empty($values)) { if (is_array($values)) { if (is_array(reset($values))) { foreach ($values as $value) { if ($function == 'where') { array_shift($value); } if ($function == 'with') { $params = implode(':', $value); } else { $params = implode(', ', $value); } $print .= "\t" . '->' . $function . '(' . $params . ')' . "\n"; } } else { $params = implode(', ', $values); $print .= "\t" . '->' . $function . '(' . $params . ')' . "\n"; } } else { $print .= "\t" . '->' . $function . '(' . $values . ')' . "\n"; } } } echo '
' . print_r($print, true) . '
'; $this->clearQuery(); } }