Overview

Namespaces

  • Sleepy
  • Module
    • Authentication
    • CSV
    • DB
    • FormBuilder
    • FSDB
    • IP2Country
    • Mailer
    • MobiDetect
    • Navigation
    • StaticCache
  • PHP

Classes

  • Sleepy\Debug
  • Sleepy\Hook
  • Sleepy\Router
  • Sleepy\SM
  • Sleepy\Template

Exceptions

  • Sleepy\RouteNotFound
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: namespace Module\FSDB;
  3: 
  4: /**
  5:  * Implements a flat-file database that can be used when a real DB is overkill.
  6:  *
  7:  * A database that uses flat files for basic database functionality like select,
  8:  * insert, update, and delete.
  9:  *
 10:  * ### Usage
 11:  *
 12:  * <code>
 13:  *   $fruit = new stdClass();
 14:  *   $fruit->name = "Apple";
 15:  *   $fruit->color = "Red";
 16:  *   $fruit->texture = "Crispy";
 17:  *   $fruit->price = 0.50;
 18:  *
 19:  *   $db = new \Module\FSDB\Connection();
 20:  *   $db->insert('fruit', $fruit);
 21:  *   $data = $db->select('fruit', 'name', 'Banana');
 22:  * </code>
 23:  *
 24:  * ### Changelog
 25:  *
 26:  * ## Version 1.0
 27:  * * Added namespacing
 28:  *
 29:  * ## Version 0.8
 30:  * * Added the date and changelog sections to documentation
 31:  *
 32:  * @todo select with =, >, <, !=, >=, <=
 33:  *
 34:  * @date August 13, 2014
 35:  * @author Jaime A. Rodriguez <hi.i.am.jaime@gmail.com>
 36:  * @version 0.8
 37:  * @license  http://opensource.org/licenses/MIT
 38:  */
 39: class Connection {
 40:     /**
 41:      * string The directory where the data is stored
 42:      * @private
 43:      */
 44:     private $directory;
 45: 
 46: 
 47:     /**
 48:      * array Tables currently loaded in memory
 49:      * @private
 50:      */
 51:     private $tables = array();
 52: 
 53:     /**
 54:      * __construct
 55:      *
 56:      * @param string $directory Directory where data is stored (optional)
 57:      */
 58:     public function __construct($directory = '') {
 59:         if ($directory == '') {
 60:             $directory = getcwd() . "/data/";
 61:         }
 62:         if (is_dir($directory)) {
 63:             $this->directory = $directory;
 64:         } else {
 65:             $oldumask = umask(0);
 66:             if (@mkdir($directory, 0777)) {
 67:                 $this->directory = $directory;
 68:             } else {
 69:                 throw new \Exception("FSDB: Cannot create data directory at:" . $directory);
 70:             }
 71:             umask($oldumask);
 72:         }
 73:     }
 74: 
 75:     /**
 76:      * __call
 77:      *
 78:      * Handles method calls, if the method exists in \Module\FSDB\Table, use that one.
 79:      *
 80:      * @param string $method Method called
 81:      * @param array $args   Arguments passed
 82:      * @return mixed Value.
 83:      */
 84:     public function __call($method, $args) {
 85:         if (method_exists("\Module\FSDB\_Table", $method)) {
 86:             $tableName = $args[0];
 87:             $filename = $args[0] . ".json";
 88: 
 89:             if (!isset($this->tables[$tableName])) {
 90:                 $this->tables[$tableName] = new _Table($this->directory . $filename);
 91:             }
 92: 
 93:             array_shift($args);
 94: 
 95:             return call_user_func_array(array($this->tables[$tableName], $method), $args);
 96: 
 97:         } else {
 98:             throw new \Exception("FSDB: Method does not exist: $method");
 99:         }
100:     }
101: }
102: 
103: /**
104:  * Private class used by \Module\FSDB\Connection
105:  *
106:  * @date August 13, 2014
107:  * @author Jaime A. Rodriguez <hi.i.am.jaime@gmail.com>
108:  * @version 0.8
109:  * @license  http://opensource.org/licenses/MIT
110:  * @internal
111:  */
112: class _Table {
113:     /**
114:      * bool This flag gets set when the Database needs to be saved
115:      * @private
116:      */
117:     private $edited = false;
118: 
119: 
120:     /**
121:      * bool This flag is set if the file is locked.
122:      * @private
123:      */
124:     private $locked = false;
125: 
126:     /**
127:      * string The filename for this table
128:      * @private
129:      */
130:     private $file;
131: 
132: 
133:     /**
134:      * int The handle for the opened file
135:      * @private
136:      */
137:     private $handle;
138: 
139: 
140:     /**
141:      * array The rows for this table
142:      * @private
143:      */
144:     private $data = array();
145: 
146:     /**
147:      * __construct
148:      *
149:      * @param mixed $file The file to open for this table.
150:      */
151:     public function __construct($file) {
152:         if (!file_exists($file)) {
153:             $newFile = fopen($file, 'w');
154:             fclose($newFile);
155:         }
156: 
157:         $this->file = $file;
158: 
159:         $this->handle = fopen($this->file, "a+");
160: 
161:         if ($this->handle) {
162:             fseek($this->handle, 0);
163:             $this->getLock($file);
164:         } else {
165:             throw new \Exception('\FSDB\Table: Could not open file');
166:         }
167:     }
168: 
169:     /**
170:      * __destruct
171:      *
172:      */
173:     public function __destruct() {
174:         if ($this->edited) {
175:             $this->save();
176:         }
177:         if ($this->locked) {
178:             flock($this->handle, LOCK_UN);
179:         }
180:         if ($this->handle) {
181:             fclose($this->handle);
182:         }
183:     }
184: 
185:     private function uuid($prefix = '') {
186:         $chars = md5(uniqid(mt_rand(), true));
187:         $uuid  = substr($chars,0,8) . '-';
188:         $uuid .= substr($chars,8,4) . '-';
189:         $uuid .= substr($chars,12,4) . '-';
190:         $uuid .= substr($chars,16,4) . '-';
191:         $uuid .= substr($chars,20,12);
192:         return $prefix . $uuid;
193:     }
194: 
195:     /**
196:      * Gets a file lock for $this->handle, retries if it fails
197:      *
198:      * @private
199:      */
200:     private function getLock($timeout) {
201:         if ($this->locked == false) {
202:             if (flock($this->handle, LOCK_EX)) {
203:                 $this->locked = true;
204:                 $this->load();
205:                 return true;
206:             } else {
207:                 if ($timeout < 10) {
208:                     $this->getLock($timeout++);
209:                 } else {
210:                     throw new \Exception('\FSDB\Table: Could not lock file');
211:                 }
212:             }
213:         }
214: 
215:     }
216: 
217:     /**
218:      * Loads the table into $this->data, needs a file lock.
219:      *
220:      * @private
221:      */
222:     private function load() {
223:         if ($this->locked == false) {
224:             throw new \Exception('\FSDB\Table: File in not locked yet');
225:         }
226: 
227:         $data = "";
228: 
229:         while (($buffer = fgets($this->handle)) !== false) {
230:             $data .= $buffer;
231:         }
232: 
233:         if (!feof($this->handle)) {
234:             throw new \Exception('\FSDB\Table: unexpected fgets() fail');
235:         }
236: 
237:         $this->data = json_decode($data, false);
238:     }
239: 
240:     /**
241:      * save
242:      *
243:      * @private
244:      * @return mixed Value.
245:      */
246:     private function save() {
247:         // Clear the old file
248:         if ($this->handle) {
249:             fseek($this->handle, 0);
250:             ftruncate($this->handle, 0);
251: 
252:             // Write the new one
253:             if (fwrite($this->handle, json_encode($this->data))) {
254:                 return true;
255:             } else {
256:                 throw new \Exception("\FSDB\Connection: Cannot write data to:" . $this->file);
257:             }
258:         } else {
259:             throw new \Exception('\FSDB\Connection\: File is not opened, can not save.');
260:         }
261:     }
262: 
263:     /**
264:      * Selects data from the table
265:      *
266:      * @param string $column The column to match
267:      * @param mixed $search What to search for
268:      * @return array An array of rows in the form of objects.
269:      */
270:     public function select($column, $search = 0) {
271:         $results = array();
272: 
273:         if ($column == "*") {
274:             return $this->data;
275:         }
276: 
277:         if (count($this->data) == 0) {
278:             return array();
279:         }
280: 
281:         foreach($this->data as $row) {
282:             if ($row->$column == $search) {
283:                 $results[] = $row;
284:             }
285:         }
286: 
287:         return $results;
288:     }
289: 
290:     /**
291:      * Selects data from the table within a range
292:      *
293:      * @param string $column The column to match
294:      * @param int $lower The lower range
295:      * @param int $upper The upper range
296:      * @return array An array of rows in the form of objects.
297:      */
298:     public function selectRange($column, $lower, $upper) {
299:         $results = array();
300: 
301:         foreach($this->data as $row) {
302:             if ($row->$column > $lower && $row->column < $upper) {
303:                 $results[] = $row;
304:             }
305:         }
306: 
307:         return $results;
308:     }
309: 
310:     /**
311:      * Updates columns in the table.
312:      *
313:      * Currently, it overwrites the whole row instead of merging the row.
314:      *
315:      * @param string $column The column to match
316:      * @param mixed $search What to search for
317:      * @param object $data An object containing the row data.
318:      * @return int How many rows were updated?
319:      */
320:     public function update($column, $search = 0, $data = array()) {
321:         $updated = 0;
322: 
323:         foreach($this->data as $key => $value) {
324:             if ($this->data[$key]->$column == $search) {
325:                 if (is_object($data)) {
326:                     $this->data[$key] = (object) array_merge((array) $this->data[$key], (array) $data);
327:                 } else {
328:                     $this->data[$key]->$column = $data;
329:                 }
330:                 $updated++;
331:             }
332:         }
333: 
334:         if ($updated) {
335:             $this->edited = true;
336:         }
337: 
338:         return $updated;
339:     }
340: 
341:     /**
342:      * Inserts a new row into the table
343:      *
344:      * @param object $data An object containing the row data.
345:      * @return bool Was the operation successful?
346:      */
347:     public function insert($data) {
348:         $data->id = $this->uuid();
349:         $this->data[] = $data;
350:         $this->edited = true;
351:         return true;
352:     }
353: 
354:     /**
355:      * Deletes rows from the table
356:      *
357:      * @param string $column The column to match
358:      * @param mixed $search What to search for
359:      * @return int How many rows were deleted?
360:      */
361:     public function delete($column, $search = 0) {
362:         $updated = 0;
363: 
364:         foreach($this->data as $key => $value) {
365:             if ($value->$column == $search) {
366:                 unset($this->data[$key]);
367:                 $updated++;
368:             }
369:         }
370: 
371:         if ($updated > 0) {
372:             $this->edited = true;
373:         }
374: 
375:         return $updated;
376:     }
377: }
sleepyMUSTACHE v.0.8 API documentation generated by ApiGen