Skip to content

Instantly share code, notes, and snippets.

@otengkwame
Forked from bkader/Filebase.php
Created June 20, 2020 00:59
Show Gist options
  • Save otengkwame/ed0a9b75f6f73dca3db4dea6d710f0ad to your computer and use it in GitHub Desktop.
Save otengkwame/ed0a9b75f6f73dca3db4dea6d710f0ad to your computer and use it in GitHub Desktop.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* CodeIgniter Filebase Class
*
* HOW TO USE - see at the bottom of the file.
*
* @package CodeIgniter
* @subpackage Third Party
* @category File-based database engine
* @author Kader Bouyakoub <[email protected]>
* @link https://github.com/bkader/filebase
* @link http://www.bkader.com/
*/
// Make sure the DIRECTORY_SEPARATOR is defined
defined('DS') OR define('DS', DIRECTORY_SEPARATOR);
class Filebase
{
/**
* @var string - path to data folder
*/
protected $path = NULL;
/**
* @var string - main database name
*/
protected $database = NULL;
/**
* @var string - table name set when class is called
*/
protected $table = NULL;
/**
* @var string - realpath to the data folder
*/
protected $data_dir = NULL;
/**
* @var string - realpath to table folder
*/
protected $table_dir = NULL;
/**
* @var array - array of ALL tables' indices
*/
protected $indices = array();
/**
* @var boolean - turn on/off safe delete
*/
protected $safe_delete = FALSE;
/**
* @var array - array of fields with unique values.
*/
protected $unique_fields = array();
/**
* Constructor:
* We start by settings our path, database, table, data_dir and table_dir
* @access public
* @param array $config array of class config.
* @param string $database the database name
*/
public function __construct($config = array(), $database = 'main')
{
// We start by setting real path to data folder.
$this->path = realpath(($config['path'] !== NULL) ? $config['path'] : './').DS;
// We set our database
$this->db = $database;
// Set path to data folder and create it if it does snot exist.
$this->data_dir = $this->path.$this->db.DS;
// Create all needed folder
$this->_create_datafolder();
$this->_create_database();
// Make sure our table is set and not NULL so we can create
// our table's folder where files will be stored.
$this->table = $config['table'];
if ($this->table !== NULL)
{
$this->table_dir = $this->data_dir.$this->table.DS;
}
if ($this->table_dir !== NULL)
{
$this->_create_folder($this->table_dir);
}
// Check if our safe delete is turned on.
if (isset($config['safe_delete']))
{
$this->safe_delete = (bool) $config['safe_delete'];
}
// Are there any unique fields?
if (isset($config['unique_fields']) && is_array($config['unique_fields']))
{
$this->unique_fields = array_merge(
$this->unique_fields,
$config['unique_fields']
);
}
// List all indices to be used to call files but also
// to keep track of records count.
$this->indices = $this->_list_indices();
}
/*
|======================================================================
| READERS
|======================================================================
*/
/**
* Get the row where the value matches that of the key and return the value of the other key
*
* @param string $col the column to get
* @param string $key the key whose value to match
* @param string $val the value to match
* @param array $table the array to get from
*
* @return array
*/
public function get($col, $key, $val, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
foreach ($rows as $row)
{
if ($row[$key] === $val && $row[$col])
{
return $row[$col];
break;
}
}
}
return FALSE;
}
/**
* Returns an array of selected columns for all rows
* @access public
* @param array $cols columns to get
* @param string $table table name
* @return array
*/
public function select($cols = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
$_result = array();
$_values = array();
if ($cols === array())
{
foreach ($rows as $row)
{
foreach (array_keys($row) as $c)
{
$_values[$c] = $row[$c];
}
if ($_values)
{
$_result[] = $_values;
}
$_values = array();
}
}
else
{
foreach ($rows as $row)
{
foreach ((array) $cols as $c)
{
if ($row[$c])
$_values[$c] = $row[$c];
}
if ($_values)
{
$_result[] = $_values;
}
$_values = array();
}
}
return $_result;
}
return FALSE;
}
/**
* Get columns from records in which key's value is part of an array of value
* @access public
* @param array $cols columns to get
* @param string $key key to look for the value
* @param array $values array of value to be compared to
* @return array
*/
public function where_in($cols = array(), $key, $values = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
$_result = array();
$_values = array();
if ($cols === array())
{
foreach ($rows as $row)
{
if (in_array($row[$key], $values))
{
foreach (array_keys($row) as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
else
{
foreach ($rows as $row)
{
if (in_array($row[$key], $values))
{
foreach ((array) $cols as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
return $_result;
}
return FALSE;
}
/**
* Get columns from records in which key's value is NOT part of an array of value
* @access public
* @param array $cols columns to get
* @param string $key key to look for the value
* @param array $values array of value to be compared to
* @return array
*/
public function where_not_in($cols = array(), $key, $values = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
$_result = array();
$_values = array();
if ($cols === array())
{
foreach ($rows as $row)
{
if ( ! in_array($row[$key], $values))
{
foreach (array_keys($row) as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
else
{
foreach ($rows as $row)
{
if ( ! in_array($row[$key], $values))
{
foreach ((array) $cols as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
return $_result;
}
return FALSE;
}
/**
* Matches keys and values bases on a regular expression
* @param array $cols the columns to return
* @param string $key the key to check
* @param string $regex the regular expression to match
* @param string $table table to execute the search on
* @return array
*/
public function like($cols, $key, $regex, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
$_result = array();
$_values = array();
if ($cols === array())
{
foreach ($rows as $row)
{
if (preg_match($regex, $row[$key]))
{
foreach (array_keys($row) as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
else
{
foreach ($rows as $row)
{
if (preg_match($regex, $row[$key]))
{
foreach ((array) $cols as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
return $_result;
}
return FALSE;
}
/**
* Matches keys and values bases on a regular expression
* @param array $cols the columns to return
* @param string $key the key to check
* @param string $regex the regular expression to match
* @param string $table table to execute the search on
* @return array
*/
public function not_like($cols, $key, $regex, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->all($table))
{
$_result = array();
$_values = array();
if ($cols === array())
{
foreach ($rows as $row)
{
if ( ! preg_match($regex, $row[$key]))
{
foreach (array_keys($row) as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
else
{
foreach ($rows as $row)
{
if ( ! preg_match($regex, $row[$key]))
{
foreach ((array) $cols as $c)
{
$_values[$c] = $row[$c];
}
$_result[] = $_values;
$_values = array();
}
}
}
return $_result;
}
return FALSE;
}
/**
* Retrieves a single row by its ID
* @access public
* @param string $id ID of the row to retrieve
* @param string $table
* @return array
*/
public function find($id, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and in_array(md5($id), $this->indices))
{
$content = file_get_contents($this->table_dir.md5($id).'.dat');
return $this->_unserialize($content);
}
return NULL;
}
/**
* Retrieves multiple rows by conditions
* @access public
* @param mixed $field field name or array of WHERE
* @param mixed
* @param string $table
* @return array
*/
public function find_one($field, $match = NULL, $table = NULL)
{
$table or $table = $this->table;
if($rows = $this->_read_files($table))
{
$where = (is_array($field) and $match === NULL) ? $field : array($field => $match);
if ($indices = $this->_search_array($rows, $where))
{
return $rows[$indices[0]];
}
return NULL;
}
return NULL;
}
/**
* Retrieves multiple or all rows inside a table
* @access public
* @param array $where array of WHERE statements
* @param string $table
* @return array
*/
// public function find_all($where = array(), $table = NULL)
public function find_all($field, $match = NULL, $table = NULL)
{
$table or $table = $this->table;
if ($rows = $this->_read_files($table))
{
$where = (is_array($field) AND $match === NULL)
? $field : array($field => $match);
$found = array();
if ($indices = $this->_search_array($rows, $where))
{
foreach ($indices as $index)
{
$found[] = $rows[$index];
}
}
return $found;
}
return NULL;
}
/**
* Alias of the method above but it takes only the table nam
* so it would retrieve ALL records.
* @access public
* @param array $where array of WHERE statements
* @param string $table table name
* @return array
*/
public function all($table = NULL)
{
return $this->find_all(array(), NULL, $table);
}
/*
|======================================================================
| CRUD INTERFACE
|======================================================================
*/
/**
* Insert new record into table
* @access public
* @param array $data array of key=>value
* @param string $table table name
* @return boolean
*/
public function insert($data, $table = NULL)
{
// Make sure to use a table, otherwise return FALSE
$table or $table = $this->table;
if ($this->table !== NULL)
{
// Prepare our default
$default = array();
// Loop through all our records and append to $default
foreach($data as $key => $val)
{
// Make sure there is no duplicate entry!
if (in_array($key, $this->unique_fields)
&& $this->get($key, $key, $val))
{
throw new Exception("Duplicate entry '{$val}' for key '{$key}'.");
break;
}
// Loop through and set our data
$default[$key] = $val;
}
// Generate new UUID and add it to the $data array
$guid = $this->_generate_guid();
// Add both id and sid to $default.
$default = array_merge(
array( 'id' => $guid,
'sid' => $this->_shorten_guid($guid)),
$default
);
// Make sure to insert our created_at and created_by in case it is not set
$default['created_at'] = @$default['created_at'] ?: time();
$default['created_by'] = @$default['created_by'] ?: NULL;
// This is the first time the file is created! So, we set our
// update fields and make them empty.
$default['updated_at'] = 0;
$default['updated_by'] = NULL;
// Create safe delete fields if safe delete is turned on
if ($this->safe_delete === TRUE)
{
$default['deleted'] = FALSE;
$default['deleted_at'] = 0;
$default['deleted_by'] = NULL;
}
// Prepare out file to store, put content into file and
// return a simple the newly inserted ID.
$file = $this->table_dir.md5($guid).'.dat';
file_put_contents($file, $this->_serialize($default));
return $guid;
}
return FALSE;
}
/**
* Updates an existing row by its ID
* @access public
* @param string $id ID of the row to update
* @param array $data array of SET statements
* @param string $table
* @return boolean
*/
public function update($id, array $data, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $row = $this->find($id))
{
foreach($data as $key => $val)
{
// Are there any duplicate entries?
if (in_array($key, $this->unique_fields)
&& $this->get($key, $key, $val))
{
throw new Exception("Duplicate entry '{$val}' for key '{$key}'.");
break;
}
$row[$key] = $val;
if (isset($data['updated_at']))
{
$row['updated_at'] = time();
}
else
{
$row['updated_at'] = time();
}
}
$file = $this->data_dir.$table.DS.md5($row['id']).'.dat';
return (file_put_contents($file, $this->_serialize($row))) ? TRUE : FALSE;
}
return FALSE;
}
/**
* Updates multiple or all rows
* @access public
* @param array $data array of SET statements
* @param array $where array of WHERE statements
* @param string $table
* @return boolean
*/
public function update_all(array $data, $where = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->find_all($where, NULL, $table))
{
foreach($rows as $row)
{
$this->update($row['id'], $data, $table);
}
return TRUE;
}
return FALSE;
}
/**
* Deletes a single existing row by its ID
* @access public
* @param string $id ID of the row to delete
* @param string $table
* @return boolean
*/
public function delete($id, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $row = $this->find($id, $table))
{
if ($this->safe_delete === TRUE or isset($row['deleted']))
{
return $this->update($id, array(
'deleted' => TRUE,
'deleted_at' => time(),
), $table);
}
else
{
return $this->remove($id, $table);
}
}
return FALSE;
}
/**
* Deletes multiple or all rows by conditions
* @access public
* @param array $where array of WHERE statements
* @param string $table
* @return boolean
*/
public function delete_all($where = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->find_all($where, NULL, $table))
{
foreach($rows as $row)
{
$this->delete($row['id']);
}
return TRUE;
}
return FALSE;
}
/**
* Completely removes a single row bypassing soft delete
* @access public
* @param string $id ID of the row to delete
* @param string $table
* @return boolean
*/
public function remove($id, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $row = $this->find($id, $table))
{
@unlink($this->table_dir.md5($row['id']).'.dat');
clearstatcache();
return TRUE;
}
return FALSE;
}
/**
* Completely remove multiple or all rows bypassing soft delete
* @access public
* @param array $where array of where statements
* @param string $table
* @return boolean
*/
public function remove_all($where = array(), $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $this->find_all($where, NULL, $table))
{
foreach($rows as $row)
{
$this->remove($row['id'], $table);
}
clearstatcache();
return TRUE;
}
return FALSE;
}
/**
* This empties the table folder
* NOTE: This is an alias of the method above
* @access public
* @param none
* @return boolean
*/
public function truncate($table = NULL)
{
$table or $table = $this->table;
return ($table !== NULL) ? $this->remove_all() : FALSE;
}
/*
|======================================================================
| UTILITIES
|======================================================================
*/
/**
* Checks whether the given key/value pair exists
*
* @param string $key the key
* @param string $val the value
* @param array $table the array to work on
*
* @return boolean whether the pair exists
*/
public function exists($key, $val, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL and $rows = $thi->all($table))
{
$_result = FALSE;
foreach ($rows as $row)
{
if ($row[$key] === $val)
{
$_result = TRUE;
}
}
return $_result;
}
return FALSE;
}
/**
* Counts the number of items per column or for all columns
*
* @param string $col the column name to count. No input counts all columns.
* @param string $table table to use
*
* @return int the number of rows containing that column.
*/
public function count($col, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL)
{
is_array($col) or $col = (array) $col;
return count($this->select($col, $table));
}
return 0;
}
/**
* Gets the first item of a column
*
* @param string $col the column to look at
* @param string $table the table name
*
* @return mixed the first item in the column
*/
public function first($col = array(), $table = NULL)
{
$table or $table = $this->table;
return ($table !== NULL)
? $this->select((array) $col, $table)[0]
: FALSE;
}
/**
* Gets the last item in a column
*
* @param string $col the name of the column to look at
* @param string $table the array to work on
*
* @return mixed the last item in the column
*/
public function last($col, $table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL)
{
$_values = $this->select((array) $col, $table);
return end($_values)[$col];
}
return FALSE;
}
/*
|======================================================================
| PRIVATE METHODS
|======================================================================
*/
/**
* Searches inside an array
* @access protected
* @param array $array
* @param array $where
*/
protected function _search_array(array $array, array $where)
{
$found = FALSE;
foreach ($array as $aKey => $aVal)
{
$coincidences = 0;
foreach ($where as $pKey => $pVal)
{
if (array_key_exists($pKey, $aVal) && $aVal[$pKey] == $pVal)
{
$coincidences++;
}
}
if ($coincidences == count($where))
{
$found[] = $aKey;
}
}
return $found;
}
/**
* Reads all files inside table folder
* @access protected
* @param string $table
* @return array
*/
protected function _read_files($table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL)
{
$rows = array();
foreach (glob($this->table_dir.'*.dat') as $row)
{
$content = file_get_contents($row);
$rows[] = $this->_unserialize($content);
}
return $rows;
}
return NULL;
}
// ------------------------------------------------------------------------
/**
* Creates a new folder in a specified path
* @access protected
* @param string $path folder to create
* @return boolean
*/
protected function _create_folder($path = NULL)
{
if ($path !== NULL)
{
// Create data folder if it does not already exist.
if ( ! is_dir($path))
{
// Create the data folder
if ( ! mkdir($path, 0777, TRUE))
{
throw new Exception("Could not create data folder. Permission Denied!");
}
}
// Write our protection index.php if does not exists
if ( ! file_exists($path.'index.php'))
{
$this->_create_index($path);
}
}
return FALSE;
}
/**
* Creates data folder if it does not exit.
* @access protected
* @param string $path path to data folder
* @return boolean
*/
protected function _create_datafolder($path = NULL)
{
$path or $path = $this->path;
return ($path !== NULL)
? $this->_create_folder($path)
: FALSE;
}
/**
* Creates database folder
* @access protected
* @param string $path path to database folder
* @return boolean
*/
protected function _create_database($database = NULL)
{
$database or $database = $this->database;
return ($this->database !== NULL) ?
$this->_create_folder($this->data_dir)
: FALSE;
}
/**
* Create the index.php protection file
* @access protected
* @param string $path path where the create the file
* @return void
*/
protected function _create_index($path)
{
$o = umask(0);
file_put_contents(
$path.'index.php',
'<?php'."\n".'die("No direct script access allowed.");'."\n"
);
umask($o);
}
// ------------------------------------------------------------------------
/**
* Returns an array of all IDs
* @access protected
* @param string $table table's name
* @return array
*/
protected function _list_indices($table = NULL)
{
$table or $table = $this->table;
if ($table !== NULL)
{
$indices = array();
foreach (glob($this->table_dir.'*.dat') as $row)
{
$indices[] = str_replace('.dat', '', basename($row));
}
return $indices;
}
return FALSE;
}
/**
* Generates a fresh new GUID
* @access protected
* @param none
* @return string
*/
protected function _generate_guid($striped = FALSE)
{
$guid = '%04x%04x-%04x-%04x-%04x-%04x%04x%04x';
if ($striped === TRUE)
$guid = '%04x%04x%04x%04x%04x%04x%04x%04x';
return vsprintf($guid, array(
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
));
}
/**
* Generates a new shortened URL for a specific GUID
* @access protected
* @param string $id id to shorten
* @return string $sid shortened id
*/
protected function _shorten_guid($guid = NULL)
{
if ($guid !== NULL and $this->table !== NULL)
{
do {
$sid = $this->random_string();
return $sid;
} while ( ! $this->find_one('sid', $sid));
}
return FALSE;
}
/**
* Generate random string
* @access public
* @param string string to use to generate a random one
* @return string
*/
public function random_string($str = NULL)
{
is_array($str) or $str = (array) $str;
! empty($str) or $str = array(uniqid(), session_id());
for ($i=0; $i < count(array($str)); $i++)
{
$str = hash('crc32b', $str[$i]."-".$i);
}
return $str;
}
// ------------------------------------------------------------------------
/**
* Serializes an array
* @access protected
* @param array $data array of data to serialize
* @return string serialized $data
*/
protected function _serialize(array $data = array())
{
// Now we serialize and prepare our data!
// 1. Try to use igbinary_serialize/igbinary_unserialize
// 2. Try to use bson_encode/bson_decode (MongoDB)
// 3. If none of the above found, we use json_encode/json_decode
// 4. If none of the functions are available
// we use php serialize/unserialize
if (function_exists('igbinary_serialize'))
return igbinary_serialize($data);
elseif (function_exists('bson_encode'))
return bson_encode($data);
elseif (function_exists('json_encode'))
return json_encode($data);
else
return serialize($data);
}
/**
* Unerializes a string into an array
* @access protected
* @param string $str the string to unserialize
* @return array serialized string
*/
protected function _unserialize($str = '')
{
if (function_exists('igbinary_unserialize'))
$array = igbinary_unserialize($str);
elseif (function_exists('bson_decode'))
$array = bson_decode($str);
elseif (function_exists('json_decode'))
$array = json_decode($str);
else
$array = unserialize($str);
// Because functions above, except php serialize, return
// objects instead of array, we convert them into arrays.
return (array) $array;
}
}
// What is Filebase.php?
// ---------------------
// This class allows you to use CodeIgniter without any database server. It brings a homemade file-based, serverless database mechanism.
// How it works?
// -------------
// Once use, "Filebase" will automatically create a folder with the database name you provide. If you do not provide any, the database name "main" will be used.
// Each library using this class will have (preferably) its own table, which means, its own folder.
// Each record will be stored as a serialized array using in-order:
// 1. igbinary_serialize() and igbinary_unserialize() if they exist (Look for php_igbinary extension).
// 2. bson_encode() and bson_decode() if they exist (php_mongo extension).
// 3. json_encode() and json_decode()
// How to install?
// ---------------
// 1. Put the php files "Filebase.php" anywhere you want as long as you will correctly include.
// 2. Create a library (CodeIgniter) and include Filebase like so:
// require APPPATH.'third_party/Filebase/Filebase.php';
// (assuming that it is inside third_party folder)
// 3. In library's constructor you can put:
// - already declared $table -
// $this->table = new Filebase(array(
// 'path' => $dbpath,
// 'table' => $tablename,
// 'safe_delete' => TRUE/FALSE
// ), $database);
// - $dbpath: path to your writable database folder.
// - $tablename: library's table name.
// - safe_delete: whether to delete OR hide records.
// - $database : your database name (default: main)
//
// Once done, you can use the $table object like so:
// 1. $this->table->insert($data) where $data is the array to insert
// 2. $this->table->update($id, $data) & $this->table->update_all($data, $where)
// 3. $this->table->delete($id), ...->delete_all($where)
// ... etc
/* End of file Filebase.php */
/* Location: ./application/third_party/Filebase/Filebase.php */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment