MySQLStore::find()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
namespace arc\store;
3
4
/*
5
TODO: implement links
6
*/
7
final class MySQLStore implements Store {
8
9
    private $db;
10
    private $queryParser;
11
    private $resultHandler;
12
    private $path;
13
14
    /**
15
     * MySQLStore constructor.
16
     * @param \PDO $db
17
     * @param callable $queryParser
18
     * @param callable $resultHandler
19
     * @param string $path
20
     */
21
    public function __construct($db = null, $queryParser = null, $resultHandler = null, $path = '/')
22
    {
23
        $this->db            = $db;
24
        if ($this->db) {
25
            $this->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
26
        }
27
        $this->queryParser   = $queryParser;
28
        $this->resultHandler = $resultHandler;
29
        $this->path          = \arc\path::collapse($path);
30
    }
31
32
    /**
33
     * change the current path, returns a new store instance for that path
34
     * @param string $path
35
     * @return MySQLStore
36
     */
37
    public function cd($path)
38
    {
39
        return new self( $this->db, $this->queryParser, $this->resultHandler, \arc\path::collapse($path, $this->path) );
40
    }
41
42
    /**
43
     * creates sql query for the search query and returns the resulthandler
44
     * @param string $query
45
     * @param string $path
46
     * @return mixed
47
     */
48
    public function find($query, $path='')
49
    {
50
        $path = \arc\path::collapse($path, $this->path);
51
        $sql  = $this->queryParser->parse($query, $path);
0 ignored issues
show
Bug introduced by
The method parse() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

51
        /** @scrutinizer ignore-call */ 
52
        $sql  = $this->queryParser->parse($query, $path);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
52
        return ($this->resultHandler)( $sql, [] );
53
    }
54
55
    /**
56
     * get a single object from the store by path
57
     * @param string $path
58
     * @return mixed
59
     */
60
    public function get($path='')
61
    {
62
        $path   = \arc\path::collapse($path, $this->path);
63
        $parent = ($path=='/' ? '' : \arc\path::parent($path));
64
        $name   = ($path=='/' ? '' : basename($path));
65
        $result = ($this->resultHandler)(
66
            'parent=:parent and name=:name', 
67
            [':parent' => $parent, ':name' => $name]
68
        );
69
        if (!is_array($result)) {
70
            $result = iterator_to_array($result);
71
        }
72
        return array_pop($result);
73
    }
74
75
    /**
76
     * list all parents, including self, by path, starting from the root
77
     * @param string $path
78
     * @param string $top
79
     * @return mixed
80
     */
81
    public function parents($path='', $top='/')
82
    {
83
        $path   = \arc\path::collapse($path, $this->path);
84
        return ($this->resultHandler)(
85
            /** @lang sql */
86
            'path=substring(:path,1,length(path)) '
87
            . ' and path LIKE :top order by path',
88
            [':path' => $path, ':top' => $top.'%']
89
        );
90
    }
91
92
    /**
93
     * list all child objects by path
94
     * @param string $path
95
     * @return mixed
96
     */
97
    public function ls($path='')
98
    {
99
        $path   = \arc\path::collapse($path, $this->path);
100
        return ($this->resultHandler)('parent=:path', [':path' => $path]);
101
    }
102
103
    /**
104
     * returns true if an object with the given path exists
105
     * @param string $path
106
     * @return bool
107
     */
108
    public function exists($path='')
109
    {
110
        $path   = \arc\path::collapse($path, $this->path);
111
        $query  = $this->db->prepare('select count(*) from nodes where path=:path');
0 ignored issues
show
Bug introduced by
The method prepare() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

111
        /** @scrutinizer ignore-call */ 
112
        $query  = $this->db->prepare('select count(*) from nodes where path=:path');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
112
        $result = $query->execute([':path' => $path]);
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
113
        return ($query->fetchColumn(0)>0);
114
    }
115
116
    /**
117
     * initialize the postgresql database, if it wasn't before
118
     * @return bool|mixed
119
     */
120
    public function initialize() {
121
        try {
122
            if ($result=$this->exists('/')) {
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
123
                return false;
124
            }
125
        } catch (\PDOException $e) {
126
            // expected exception
127
        }
128
129
        $queries = [];
130
        $queries[] = "begin;";
131
        $queries[] = <<<SQL
132
create table nodes (
133
    parent text not null ,
134
    name   text not null,
135
    path   text generated always as (concat(parent,name,'/')),
136
    data   json not null,
137
    ctime  timestamp default current_timestamp,
138
    mtime  timestamp default current_timestamp on update current_timestamp,
139
    KEY key_parent (parent(255)),
140
    KEY key_name (name(255)),
141
    UNIQUE(parent(255),name(255))
142
);
143
SQL;
144
//        $queries[] = "create trigger before_insert_node before insert on nodes for each row set new.id = UUID_TO_BIN(uuid());";
145
        $queries[] = "create unique index path on nodes ( path(255) );";
146
        foreach ( $queries as $query ) {
147
            $result = $this->db->exec($query);
148
            if ($result===false) {
149
                $this->db->exec('rollback;');
150
                return false;
151
            }
152
        }
153
        $this->db->exec('commit;');
154
155
        return $this->save(\arc\prototype::create([
156
            'name' => 'Root'
157
        ]),'/');
158
    }
159
160
    /**
161
     * save (insert or update) a single object on the given path
162
     * @param $data
163
     * @param string $path
164
     * @return mixed
165
     */
166
    public function save($data, $path='') {
167
        $path   = \arc\path::collapse($path, $this->path);
168
        $parent = ($path=='/' ? '' : \arc\path::parent($path));
169
        if ($path!='/' && !$this->exists($parent)) {
170
            throw new \arc\IllegalRequest("Parent $parent not found.", \arc\exceptions::OBJECT_NOT_FOUND);
171
        }
172
        $name = ($path=='/' ? '' : basename($path));
173
        $queryStr = <<<EOF
174
insert into nodes (parent, name, data) 
175
values (:parent, :name, :data) 
176
on duplicate key update  
177
  data = :data;
178
EOF;
179
        $query = $this->db->prepare($queryStr);
180
        return $query->execute([
181
            ':parent' => $parent,
182
            ':name'   => $name,
183
            ':data'   => json_encode($data)
184
        ]);
185
    }
186
187
    /**
188
     * remove the object with the given path and all its children
189
     * won't remove the root object ever
190
     * @param string $path
191
     * @return mixed
192
     */
193
    public function delete($path = '') {
194
        $path   = \arc\path::collapse($path, $this->path);
195
        $parent = \arc\path::parent($path);
196
        $name   = basename($path);
197
        $queryStr = <<<EOF
198
delete from nodes where (parent like :path or (parent = :parent and name = :name ))
199
EOF;
200
        $query = $this->db->prepare($queryStr);
201
        return $query->execute([
202
            ':path' => $path.'%',
203
            ':parent' => $parent,
204
            ':name' => $name
205
        ]);
206
    }
207
208
}
209