2
\$\begingroup\$

Regarding the use of the file system as a database to simply hold JSON files.

Any input on this matter from people who know will be appreciated...

class JDB{

    public $path;

    function JDB( $path = __DIR__.'/jdb/' ){
        $this->path = $path;
        if( !file_exists($this->path) ) mkdir($this->path);
    }

    function p($t){
        return $this->path.$t.'.json';
    }

    function get($t){
        return json_decode(file_get_contents( $this->p($t) ));
    }

    function set($t,$c){
        return file_put_contents( $this->p($t), json_encode($c,JSON_PRETTY_PRINT) );
    }

    function create( $t, $d = [] ){
        $s = file_put_contents( $this->p($t), json_encode($d) );
        return $s;
    }

    function destroy(){
        $files = glob($this->path.'*'); // get all file names present in folder
        foreach($files as $file){ // iterate files
        if(is_file($file))
            unlink($file); // delete the file
        }
    }

    function delete( $t ){
        $s = unlink( $this->p($t) );
        return $s;
    }

    function insert( $t, $d = null ){
        if($d) $d['__uid'] = $t.'_'.$this->uid();
        $c = $this->get($t);
        array_push($c,$d);
        $s = $this->set($t,$c);
        if($s) return $d['__uid'];
    }

    function update($t,$conditions,$u){
        $c = $this->get($t);
        $this->search($c,$conditions,function($object) use (&$c,$u){
            foreach ($u as $key => $value) {
                $object->$key = $value;
            }
        });
        $this->set($t,$c);
    }

    function remove($t,$conditions){
        $c = $this->get($t);
        $this->search($c,$conditions,function($object,$key) use (&$c){
            unset($c[$key]);
        });
        $this->set($t,$c);
    }

    function search( $c, $conditions = [], $fn ){
        $l = count($conditions);
        foreach ($c as $key => $object) {
            $f = 0;
            foreach ($conditions as $k => $v) {
                if( property_exists($object,$k) && ($object->$k == $v) ){
                    $f++;
                    if( $f==$l ) $fn($object,$key);
                }else break;
            }
        }
    }

    function select( $t, $conditions = [] ){
        $c = $this->get($t);
        $r = [];
        $this->search($c,$conditions,function($object) use (&$r){
            array_push($r,$object);
        });
        if (count($r) == 0) return false;
        if (count($r) == 1) return $r[0];
        return $r;
    }

    function count($t){
        $c = $this->get($t);
        return count($c);
    }

    function uid($length = 20) {
        $c = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $cl = strlen($c);
        $uid = '';
        for ($i = 0; $i < $length; $i++) {
            $uid .= $c[rand(0, $cl - 1)];
        }
        return $uid;
    }

}

/*

$db = new JDB();

$db->create('users');
$db->create('pages');

$user_uid = $db->insert('users',[
    'name' => 'a',
    'password' => 'hello world',
    'pages'  => []
]);

$user_uid = $db->insert('users',[
    'name' => 'b',
    'password' => 'hello world',
    'pages'  => []
]);

_log($user_uid,'1');

$page_uid = $db->insert('pages',[
    'name' => 'page 1',
    'content' => 'hello world',
    'users'  => [$user_uid]
]);

_log($page_uid);

$user = $db->select('users',['name'  => 'a']);
$page = $db->select('pages',['users'  => [$user_uid]]);

$db->update('users',['name'  => 'b'],['pages' => [$page->__uid]]);
$db->remove('users',['name'  => 'a']);

_log($user);
_log($page);

*/
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. \$\endgroup\$ – Toby Speight Mar 19 at 8:13
5
\$\begingroup\$

The only good thing about this code is your desire to invent. It is a very good desire, only ones possessing it make good programmers. You just made the very first step towards inventing your own database, just like every aspiring newbie programmer does. And that's a very good sign per se.

Everything else about this code - let me be frank - is bad. From the main idea to the minute syntax issues. To outline some misconceptions:

  • The premise. I know, I know, you have a million reasons not to use a relational database. All of them are wrong though. Beside an aspiration, a good programmer possesses a thing called common sense. Which is bound to tell them that generations of programmers created and polished a thing called a relational database for a reason.
  • the above leads to the number of consequences:
    • scalability. By no means it is good for any project you would call "large". All these manual loops in the memory will become unbearably slow. A good database never loads the entire file in the memory, it reads a file by chunks. And uses indexes to speed up all the operations dramatically.
    • race condition. With your database you will learn the hard way such things like file locking and a race condition. In concurrent environments a file must be locked when operated with, in order to prevent other files from accessing it. Or it will lead to data corruption and other unwanted consequences.
    • features. SQL is one of the greatest inventions of the last century. In a relatively short sentence that is close to natural English it lets us to do the data filtering, ordering, aggregation. How many lines of code you will have to write to get the overall salary for all employees? Grouped by departments? For the certain period? It's just a single line in SQL mind you.
  • naming. What do remove(), delete() and destroy() functions do? Can you tell from looking at them used in the code?
  • OOP. Not to mention the ancient constructor definition (it was discouraged from using for decades!), the overall class structure is flat. It's just a collection of functions. There is no delegation, no abstraction, no separation of concerns. At least there should be two classes, one operating at the filesystem level and one operating at the single json file level.
  • syntax in general. Again, it seems to be inevitable for many people to pass such a stage, trying to make a code easier to write, making names gibberish as a result. It takes time to realize that reading is much more important than writing, and such names as p(), $d and such is a blasphemy. Every variable and method should have a meaningful name.
  • if (count($r) == 1) return $r[0]; is my favorite. I learned the hard way that such a magic will lead to a situation when your code expects a 2-dimensional array of rows but gets just a single row. It's funny to see the result when you are trying to loop over it and then access distinct fields inside each element. there must be explicit methods to get you certain result types, you want to look at PDOStatement for inspiration
  • error reporting. at least for json_encode() you have to implement a wrapper that will produce a meaningful error message

    function jsonDecode($json, $assoc = false)
    {
        $ret = json_decode($json, $assoc);
        if ($error = json_last_error())
        {
            throw new Exception(json_last_error_msg(), $error);
        }
        return $ret;
    }
    
\$\endgroup\$
  • \$\begingroup\$ I would only contend that "All of them are wrong" is... well... wrong :) There are some valid resons not to use a relational database for certain purposes: 1) overhead : the "relational" part is sometimes just not needed 2) rigidity : you sometimes just want to dump data without worrying about schema 3) Lack of in-process alternatives: options are scarce in PHP for in-process RDBs, except SQLite, which doesn't provide hash indexes so it's inefficient for KV stores So there are valid reasons not to use a relational database. But there are luckily KV store alternatives (like DB4). \$\endgroup\$ – Dinu Mar 19 at 16:57
  • \$\begingroup\$ ... so the point is valid that one should use a mature tried-and-tested solution for any persisted data. But it doesn't have to be a RDB. It can be KV, noSQL, etc. \$\endgroup\$ – Dinu Mar 19 at 17:08
0
\$\begingroup\$

To add a constructive suggestion to this, it is apparent to me you are implementing a glorified KV (key-value) store; value being JSON is of no consequence. So why not use something that is already designed for this?

  1. http://php.net/manual/en/book.dba.php
  2. Any flavor of DB with hash-type storage

This would or at least should rid you of concurrency concerns (well, not entirely, you will still quite possibly get deadlocks but at least integrity is preserved by any of the above suggestions).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.