Completed
Push — master ( 605cf4...2dcac2 )
by Richard
05:16
created

IndexDB   C

Complexity

Total Complexity 69

Size/Duplication

Total Lines 402
Duplicated Lines 2.99 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 94.74%

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 7
dl 12
loc 402
ccs 234
cts 247
cp 0.9474
rs 5.6445
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A _file() 0 4 1
A _namespace() 0 4 1
A _class() 0 4 1
A _interface() 0 4 1
A _trait() 0 4 1
A _method() 0 4 1
A _function() 0 4 1
A _global() 0 4 1
A _language_construct() 0 4 1
A _method_reference() 0 4 1
A _function_reference() 0 4 1
A _relation() 0 4 1
A insert_cache() 0 16 3
A append_and_maybe_flush() 0 17 3
A esc_str() 0 4 1
A esc_maybe_null() 0 7 2
A write_cached_inserts() 0 5 2
A to_graph_index() 0 6 1
A build_graph_index_db() 0 3 1
C get_inserts() 0 48 9
A select_all_from() 0 6 1
F write_inserts_to() 0 44 12
C init_table() 12 57 19
A init_database_schema() 0 20 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like IndexDB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IndexDB, and based on these observations, apply Extract Interface, too.

1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the license along with the code.
9
 */
10
11
namespace Lechimp\Dicto\App;
12
13
use Lechimp\Dicto\Graph;
14
use Lechimp\Dicto\Indexer\Insert;
15
use Doctrine\DBAL\Schema;
16
use Doctrine\DBAL\Types\Type;
17
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
18
use Doctrine\DBAL\Statement;
19
20
class IndexDB extends DB implements Insert {
21
    protected $nodes_per_insert = 200;
22
23
    /**
24
     * @var array
25
     */
26
    protected $tables =
27
        [ "files" => ["_file",
28
            ["id", "path", "source"]]
29
        , "namespaces" => ["_namespace",
30
            ["id", "name"]]
31
        , "classes" => ["_class",
32
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
33
        , "interfaces" => ["_interface",
34
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
35
        , "traits" => ["_trait",
36
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
37
        , "methods" => ["_method",
38
            ["id", "name", "class_id", "file_id", "start_line", "end_line"]]
39
        , "functions" => ["_function",
40
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
41
        , "globals" => ["_global",
42
            ["id", "name"]]
43
        , "language_constructs" => ["_language_construct",
44
            ["id", "name"]]
45
        , "method_references" => ["_method_reference",
46
            ["id", "name", "file_id", "line", "column"]]
47
        , "function_references" => ["_function_reference",
48
            ["id", "name", "file_id", "line", "column"]]
49
        , "relations" => ["_relation",
50
            ["left_id", "relation", "right_id", "file_id", "line"]]
51
        ];
52
53
    /**
54
     * @var array[]
55
     */
56
    protected $caches = [];
57
58
    /**
59
     * @var integer
60
     */
61
    protected $id_counter = 0;
62
63 1
    public function __construct($connection) {
64 1
        parent::__construct($connection);
65 1
        foreach ($this->tables as $table => $_) {
66 1
            $this->caches[$table] = [];
67 1
        }
68 1
    }
69
70
    /**
71
     * @inheritdocs
72
     */
73 1
    public function _file($path, $source) {
74 1
        return $this->append_and_maybe_flush("files",
75 1
            [null, $this->esc_str($path), $this->esc_str($source)]);
76
    }
77
78
    /**
79
     * @inheritdocs
80
     */
81 1
    public function _namespace($name) {
82 1
        return $this->append_and_maybe_flush("namespaces",
83 1
            [null, $this->esc_str($name)]);
84
    }
85
86
    /**
87
     * @inheritdocs
88
     */
89 1
    public function _class($name, $file, $start_line, $end_line, $namespace = null) {
90 1
        return $this->append_and_maybe_flush("classes",
91 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
92
    }
93
94
    /**
95
     * @inheritdocs
96
     */
97
    public function _interface($name, $file, $start_line, $end_line, $namespace = null) {
98
        return $this->append_and_maybe_flush("interfaces",
99
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
100
    }
101
102
    /**
103
     * @inheritdocs
104
     */
105
    public function _trait($name, $file, $start_line, $end_line, $namespace = null) {
106
        return $this->append_and_maybe_flush("traits",
107
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
108
    }
109
110
    /**
111
     * @inheritdocs
112
     */
113 1
    public function _method($name, $class, $file, $start_line, $end_line) {
114 1
        return $this->append_and_maybe_flush("methods",
115 1
            [null, $this->esc_str($name), $class, $file, $start_line, $end_line]);
116
    }
117
118
    /**
119
     * @inheritdocs
120
     */
121 1
    public function _function($name, $file, $start_line, $end_line, $namespace = null) {
122 1
        return $this->append_and_maybe_flush("functions",
123 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
124
    }
125
126
    /**
127
     * @inheritdocs
128
     */
129 1
    public function _global($name) {
130 1
        return $this->append_and_maybe_flush("globals",
131 1
            [null, $this->esc_str($name)]);
132
    }
133
134
    /**
135
     * @inheritdocs
136
     */
137 1
    public function _language_construct($name) {
138 1
        return $this->append_and_maybe_flush("language_constructs",
139 1
            [null, $this->esc_str($name)]);
140
    }
141
142
    /**
143
     * @inheritdocs
144
     */
145 1
    public function _method_reference($name, $file, $line, $column) {
146 1
        return $this->append_and_maybe_flush("method_references",
147 1
            [null, $this->esc_str($name), $file, $line, $column]);
148
    }
149
150
    /**
151
     * @inheritdocs
152
     */
153 1
    public function _function_reference($name, $file, $line, $column) {
154 1
        return $this->append_and_maybe_flush("function_references",
155 1
            [null, $this->esc_str($name), $file, $line, $column]);
156
    }
157
158
    /**
159
     * @inheritdocs
160
     */
161 1
    public function _relation($left_entity, $relation, $right_entity, $file, $line) {
162 1
        $this->append_and_maybe_flush("relations",
163 1
            [$left_entity, $this->esc_str($relation), $right_entity, $file, $line]);
164 1
    }
165
166 1
    protected function insert_cache($table) {
167 1
        assert('array_key_exists($table, $this->tables)');
168 1
        $fields = $this->tables[$table][1];
169 1
        $which = &$this->caches[$table];
170 1
        if (count($which) == 0) {
171 1
            return;
172
        }
173 1
        $stmt = "INSERT INTO $table (".implode(", ", $fields).") VALUES\n";
174 1
        $values = [];
175 1
        foreach ($which as $v) {
176 1
            $values[] = "(".implode(", ", $v).")";
177 1
        }
178 1
        $stmt .= implode(",\n", $values).";";
179 1
        $this->connection->exec($stmt);
180 1
        $which = [];
181 1
    }
182
183 1
    protected function append_and_maybe_flush($table, $values) {
184 1
        if ($values[0] === null) {
185 1
            $id = $this->id_counter++;
186 1
            $values[0] = $id;
187 1
        }
188
        else {
189 1
            $id = null;
190
        }
191
192 1
        $which = &$this->caches[$table];
193 1
        $which[] = $values;
194 1
        if (count($which) > $this->nodes_per_insert) {
195
            $this->insert_cache($table);
196
        }
197
198 1
        return $id;
199
    }
200 1
    protected function esc_str($str) {
201 1
        assert('is_string($str)');
202 1
        return '"'.str_replace('"', '""', $str).'"';
203
    }
204
205 1
    protected function esc_maybe_null($val) {
206 1
        assert('!is_string($val)');
207 1
        if ($val === null) {
208 1
            return "NULL";
209
        }
210 1
        return $val;
211
    }
212
213
    /**
214
     * Write everything that currently is in cache to the database.
215
     *
216
     * @return null
217
     */
218 1
    public function write_cached_inserts() {
219 1
        foreach ($this->tables as $table => $_) {
220 1
            $this->insert_cache($table);
221 1
        }
222 1
    }
223
224
    /**
225
     * Read the index from the database.
226
     *
227
     * @return  Graph\IndexDB   $index
228
     */
229 1
    public function to_graph_index() {
230 1
        $index = $this->build_graph_index_db();
231 1
        $inserts = $this->get_inserts();
232 1
        $this->write_inserts_to($index, $inserts);
233 1
        return $index;
234
    }
235
236
    protected function build_graph_index_db() {
237
        return new Graph\IndexDB();
238
    }
239
240 1
    protected function get_inserts() {
241 1
        $results = [];
242 1
        foreach ($this->tables as $key => $_) {
243 1
            if ($key == "relations") {
244 1
                continue;
245
            }
246 1
            $results[] = [$key, null, $this->select_all_from($key)];
247 1
        }
248
249 1
        $count = count($results);
250 1
        $i = 0;
251 1
        $expected_id = 0;
252
        // TODO: This will loop forever if there are (unexpected) holes
253
        // in the sequence of ids.
254 1
        while ($count > 0) {
255 1
            if ($i >= $count) {
256 1
                $i = 0;
257 1
            }
258
259 1
            $current_res = $results[$i][1];
260 1
            if ($current_res !== null) {
261 1
                if ($current_res["id"] != $expected_id) {
262 1
                    $i++;
263 1
                    continue;
264
                }
265
266 1
                $current_res["_which"] = $results[$i][0];
267 1
                yield $current_res;
268 1
                $results[$i][1] = null;
269 1
                $expected_id++;
270 1
            }
271
272 1
            $next_res = $results[$i][2]->fetch();
273 1
            if (!$next_res) {
274 1
                $count--;
275 1
                unset($results[$i]);
276 1
                $results = array_values($results);
277 1
                continue;
278
            }
279 1
            $results[$i][1] = $next_res;
280 1
        }
281
282 1
        $relations = $this->select_all_from("relations");
283 1
        while($res = $relations->fetch()) {
284 1
            $res["_which"] = "relations";
285 1
            yield $res;
286 1
        }
287 1
    }
288
289 1
    protected function select_all_from($table) {
290 1
        return $this->builder()
291 1
            ->select($this->tables[$table][1])
292 1
            ->from($table)
293 1
            ->execute();
294
    }
295
296 1
    protected function write_inserts_to(Graph\IndexDB $index, \Iterator $inserts) {
297 1
        $id_map = [];
298 1
        foreach ($inserts as $insert) {
299 1
            if (isset($insert["file_id"])) {
300 1
                $insert["file_id"] = $id_map[(int)$insert["file_id"]];
301 1
            }
302 1
            if (isset($insert["namespace_id"])) {
303 1
                $insert["namespace_id"] = $id_map[(int)$insert["namespace_id"]];
304 1
            }
305 1
            if (isset($insert["class_id"])) {
306 1
                $insert["class_id"] = $id_map[(int)$insert["class_id"]];
307 1
            }
308 1
            if (isset($insert["left_id"])) {
309 1
                $insert["left_id"] = $id_map[(int)$insert["left_id"]];
310 1
            }
311 1
            if (isset($insert["right_id"])) {
312 1
                $insert["right_id"] = $id_map[(int)$insert["right_id"]];
313 1
            }
314 1
            if (isset($insert["start_line"])) {
315 1
                $insert["start_line"] = (int)$insert["start_line"];
316 1
            }
317 1
            if (isset($insert["end_line"])) {
318 1
                $insert["end_line"] = (int)$insert["end_line"];
319 1
            }
320 1
            if (isset($insert["line"])) {
321 1
                $insert["line"] = (int)$insert["line"];
322 1
            }
323 1
            if (isset($insert["column"])) {
324 1
                $insert["column"] = (int)$insert["column"];
325 1
            }
326
327 1
            assert('array_key_exists($insert["_which"], $this->tables)');
328 1
            $method = $this->tables[$insert["_which"]][0];
329 1
            unset($insert["_which"]);
330 1
            if (isset($insert["id"])) {
331 1
                $id = $insert["id"];
332 1
                unset($insert["id"]);
333 1
                $id_map[$id] = call_user_func_array([$index, $method], $insert);
334 1
            }
335
            else {
336 1
                call_user_func_array([$index, $method], $insert);
337
            }
338 1
        }
339 1
    }
340
341
    // INIT DATABASE
342
343 1
    public function init_table($name, Schema\Schema $schema, Schema\Table $file_table = null, Schema\Table $namespace_table = null) {
344 1
        assert('array_key_exists($name, $this->tables)');
345 1
        $table = $schema->createTable($name);
346 1
        foreach ($this->tables[$name][1] as $field) {
347
            switch ($field) {
348 1
                case "id":
349 1
                case "file_id":
350 1
                case "class_id":
351 1
                case "left_id":
352 1
                case "right_id":
353 1
                case "start_line":
354 1
                case "end_line":
355 1
                case "line":
356 1 View Code Duplication
                case "column":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357 1
                    $table->addColumn
358 1
                        ($field, "integer"
359 1
                        , ["notnull" => true, "unsigned" => true]
360 1
                        );
361 1
                    break;
362 1 View Code Duplication
                case "namespace_id":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
363 1
                    $table->addColumn
364 1
                        ($field, "integer"
365 1
                        , ["notnull" => false, "unsigned" => true]
366 1
                        );
367 1
                   break;
368 1
                case "path":
369 1
                case "source":
370 1
                case "name":
371 1
                case "relation":
372 1
                    $table->addColumn
373 1
                        ($field, "string"
374 1
                        , ["notnull" => true]
375 1
                        );
376 1
                    break;
377
                default:
378
                    throw new \LogicException("Unknown field '$field'");
379
            }
380 1
            if ($field == "id") {
381 1
                $table->setPrimaryKey(["id"]);
382 1
            }
383 1
            if ($field == "file_id") {
384 1
                $table->addForeignKeyConstraint
385 1
                    ( $file_table
0 ignored issues
show
Bug introduced by
It seems like $file_table defined by parameter $file_table on line 343 can be null; however, Doctrine\DBAL\Schema\Tab...dForeignKeyConstraint() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
386 1
                    , array("file_id")
387 1
                    , array("id")
388 1
                    );
389 1
            }
390 1
            if ($field == "namespace_id") {
391 1
                $table->addForeignKeyConstraint
392 1
                    ( $namespace_table
0 ignored issues
show
Bug introduced by
It seems like $namespace_table defined by parameter $namespace_table on line 343 can be null; however, Doctrine\DBAL\Schema\Tab...dForeignKeyConstraint() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
393 1
                    , array("namespace_id")
394 1
                    , array("id")
395 1
                    );
396 1
            }
397 1
        }
398 1
        return $table;
399
    }
400
401 1
    public function init_database_schema() {
402 1
        $schema = new Schema\Schema();
403
404 1
        $file_table = $this->init_table("files", $schema);
405 1
        $namespace_table = $this->init_table("namespaces", $schema);
406 1
        $this->init_table("classes", $schema, $file_table, $namespace_table);
407 1
        $this->init_table("interfaces", $schema, $file_table, $namespace_table);
408 1
        $this->init_table("traits", $schema, $file_table, $namespace_table);
409 1
        $this->init_table("methods", $schema, $file_table);
410 1
        $this->init_table("functions", $schema, $file_table, $namespace_table);
411 1
        $this->init_table("globals", $schema);
412 1
        $this->init_table("language_constructs", $schema);
413 1
        $this->init_table("method_references", $schema, $file_table);
414 1
        $this->init_table("function_references", $schema, $file_table);
415 1
        $relation_table = $this->init_table("relations", $schema, $file_table);
416 1
        $relation_table->setPrimaryKey(["left_id", "relation", "right_id"]);
417
418 1
        $sync = new SingleDatabaseSynchronizer($this->connection);
419 1
        $sync->createSchema($schema);
420 1
    }
421
}
422