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

IndexDB::init_table()   C

Complexity

Conditions 19
Paths 114

Size

Total Lines 57
Code Lines 46

Duplication

Lines 12
Ratio 21.05 %

Code Coverage

Tests 52
CRAP Score 19.0584

Importance

Changes 0
Metric Value
dl 12
loc 57
ccs 52
cts 55
cp 0.9455
rs 6.0221
c 0
b 0
f 0
cc 19
eloc 46
nc 114
nop 4
crap 19.0584

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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