function find($filter = NULL, array $options = NULL, $ttl = 0, $log = TRUE)
{
if (!$options) {
$options = [];
}
$options += ['order' => NULL, 'limit' => 0, 'offset' => 0];
$fw = \Base::instance();
$cache = \Cache::instance();
$db = $this->db;
$now = microtime(TRUE);
$data = [];
if (!$fw->get('CACHE') || !$ttl || !($cached = $cache->exists($hash = $fw->hash($this->db->dir() . $fw->stringify([$filter, $options])) . '.jig', $data)) || $cached[0] + $ttl < microtime(TRUE)) {
$data = $db->read($this->file);
if (is_null($data)) {
return FALSE;
}
foreach ($data as $id => &$doc) {
$doc['_id'] = $id;
unset($doc);
}
if ($filter) {
if (!is_array($filter)) {
return FALSE;
}
// Normalize equality operator
$expr = preg_replace('/(?<=[^<>!=])=(?!=)/', '==', $filter[0]);
// Prepare query arguments
$args = isset($filter[1]) && is_array($filter[1]) ? $filter[1] : array_slice($filter, 1, NULL, TRUE);
$args = is_array($args) ? $args : [1 => $args];
$keys = $vals = [];
$tokens = array_slice(token_get_all('<?php ' . $this->token($expr)), 1);
$data = array_filter($data, function ($_row) use($fw, $args, $tokens) {
$_expr = '';
$ctr = 0;
$named = FALSE;
foreach ($tokens as $token) {
if (is_string($token)) {
if ($token == '?') {
// Positional
$ctr++;
$key = $ctr;
} else {
if ($token == ':') {
$named = TRUE;
} else {
$_expr .= $token;
}
continue;
}
} elseif ($named && token_name($token[0]) == 'T_STRING') {
$key = ':' . $token[1];
$named = FALSE;
} else {
$_expr .= $token[1];
continue;
}
$_expr .= $fw->stringify(is_string($args[$key]) ? addcslashes($args[$key], '\'') : $args[$key]);
}
// Avoid conflict with user code
unset($fw, $tokens, $args, $ctr, $token, $key, $named);
extract($_row);
// Evaluate pseudo-SQL expression
return eval('return ' . $_expr . ';');
});
}
if (isset($options['order'])) {
$cols = $fw->split($options['order']);
uasort($data, function ($val1, $val2) use($cols) {
foreach ($cols as $col) {
$parts = explode(' ', $col, 2);
$order = empty($parts[1]) ? SORT_ASC : constant($parts[1]);
$col = $parts[0];
if (!array_key_exists($col, $val1)) {
$val1[$col] = NULL;
}
if (!array_key_exists($col, $val2)) {
$val2[$col] = NULL;
}
list($v1, $v2) = [$val1[$col], $val2[$col]];
if ($out = strnatcmp($v1, $v2) * (($order == SORT_ASC) * 2 - 1)) {
return $out;
}
}
return 0;
});
}
$data = array_slice($data, $options['offset'], $options['limit'] ?: NULL, TRUE);
if ($fw->get('CACHE') && $ttl) {
// Save to cache backend
$cache->set($hash, $data, $ttl);
}
}
$out = [];
foreach ($data as $id => &$doc) {
unset($doc['_id']);
$out[] = $this->factory($id, $doc);
unset($doc);
}
if ($log && isset($args)) {
if ($filter) {
foreach ($args as $key => $val) {
$vals[] = $fw->stringify(is_array($val) ? $val[0] : $val);
$keys[] = '/' . (is_numeric($key) ? '\\?' : preg_quote($key)) . '/';
}
}
$db->jot('(' . sprintf('%.1f', 1000.0 * (microtime(TRUE) - $now)) . 'ms) ' . $this->file . ' [find] ' . ($filter ? preg_replace($keys, $vals, $filter[0], 1) : ''));
}
return $out;
}