Completed
Push — master ( 2a510c...5bfa5e )
by Richard
03:56
created

IndexDB   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 344
Duplicated Lines 3.49 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 90.29%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 7
dl 12
loc 344
ccs 158
cts 175
cp 0.9029
rs 8.5454
c 0
b 0
f 0

22 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 12 1
A build_graph_index_db() 0 3 1
C init_table() 12 69 21
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\DB;
12
13
use Lechimp\Dicto\Graph;
14
use Lechimp\Dicto\Indexer\Insert;
15
use Doctrine\DBAL\Schema;
16
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
17
use Doctrine\DBAL\Statement;
18
19
class IndexDB extends DB implements Insert {
20
    protected $nodes_per_insert = 200;
21
22
    /**
23
     * @var array
24
     */
25
    protected $tables =
26
        [ "files" => ["_file",
27
            ["id", "path", "source"]]
28
        , "namespaces" => ["_namespace",
29
            ["id", "name"]]
30
        , "classes" => ["_class",
31
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
32
        , "interfaces" => ["_interface",
33
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
34
        , "traits" => ["_trait",
35
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
36
        , "methods" => ["_method",
37
            ["id", "name", "class_id", "file_id", "start_line", "end_line"]]
38
        , "functions" => ["_function",
39
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
40
        , "globals" => ["_global",
41
            ["id", "name"]]
42
        , "language_constructs" => ["_language_construct",
43
            ["id", "name"]]
44
        , "method_references" => ["_method_reference",
45
            ["id", "name", "file_id", "line", "column"]]
46
        , "function_references" => ["_function_reference",
47
            ["id", "name", "file_id", "line", "column"]]
48
        , "relations" => ["_relation",
49
            ["left_id", "relation", "right_id", "file_id", "line"]]
50
        ];
51
52
    /**
53
     * Lists of fields that contain an id.
54
     *
55
     * @var array<string,int>
56
     */
57
    protected $id_fields =
58
        [ "file_id" => 0
59
        , "namespace_id" => 0
60
        , "class_id" => 0
61
        , "left_id" => 0
62
        , "right_id" => 0
63
        ];
64
65
    /**
66
     * List of all fields that contain an integer.
67
     *
68
     * @var array<string,0>
69
     */
70
    protected $int_fields =
71
        [ "start_line" => 0
72
        , "end_line" => 0
73
        , "line" => 0
74
        , "column" => 0
75
        ];
76
77
    /**
78
     * @var array[]
79
     */
80
    protected $caches = [];
81
82
    /**
83
     * @var integer
84
     */
85
    protected $id_counter = 0;
86
87 2
    public function __construct($connection) {
88 2
        parent::__construct($connection);
89 2
        foreach ($this->tables as $table => $_) {
90 2
            $this->caches[$table] = [];
91 2
        }
92 2
    }
93
94
    /**
95
     * @inheritdocs
96
     */
97 1
    public function _file($path, $source) {
98 1
        return $this->append_and_maybe_flush("files",
99 1
            [null, $this->esc_str($path), $this->esc_str($source)]);
100
    }
101
102
    /**
103
     * @inheritdocs
104
     */
105 1
    public function _namespace($name) {
106 1
        return $this->append_and_maybe_flush("namespaces",
107 1
            [null, $this->esc_str($name)]);
108
    }
109
110
    /**
111
     * @inheritdocs
112
     */
113 1
    public function _class($name, $file, $start_line, $end_line, $namespace = null) {
114 1
        return $this->append_and_maybe_flush("classes",
115 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
116
    }
117
118
    /**
119
     * @inheritdocs
120
     */
121
    public function _interface($name, $file, $start_line, $end_line, $namespace = null) {
122
        return $this->append_and_maybe_flush("interfaces",
123
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
124
    }
125
126
    /**
127
     * @inheritdocs
128
     */
129
    public function _trait($name, $file, $start_line, $end_line, $namespace = null) {
130
        return $this->append_and_maybe_flush("traits",
131
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
132
    }
133
134
    /**
135
     * @inheritdocs
136
     */
137 1
    public function _method($name, $class, $file, $start_line, $end_line) {
138 1
        return $this->append_and_maybe_flush("methods",
139 1
            [null, $this->esc_str($name), $class, $file, $start_line, $end_line]);
140
    }
141
142
    /**
143
     * @inheritdocs
144
     */
145 1
    public function _function($name, $file, $start_line, $end_line, $namespace = null) {
146 1
        return $this->append_and_maybe_flush("functions",
147 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
148
    }
149
150
    /**
151
     * @inheritdocs
152
     */
153 1
    public function _global($name) {
154 1
        return $this->append_and_maybe_flush("globals",
155 1
            [null, $this->esc_str($name)]);
156
    }
157
158
    /**
159
     * @inheritdocs
160
     */
161 1
    public function _language_construct($name) {
162 1
        return $this->append_and_maybe_flush("language_constructs",
163 1
            [null, $this->esc_str($name)]);
164
    }
165
166
    /**
167
     * @inheritdocs
168
     */
169 1
    public function _method_reference($name, $file, $line, $column) {
170 1
        return $this->append_and_maybe_flush("method_references",
171 1
            [null, $this->esc_str($name), $file, $line, $column]);
172
    }
173
174
    /**
175
     * @inheritdocs
176
     */
177 1
    public function _function_reference($name, $file, $line, $column) {
178 1
        return $this->append_and_maybe_flush("function_references",
179 1
            [null, $this->esc_str($name), $file, $line, $column]);
180
    }
181
182
    /**
183
     * @inheritdocs
184
     */
185 1
    public function _relation($left_entity, $relation, $right_entity, $file, $line) {
186 1
        $this->append_and_maybe_flush("relations",
187 1
            [$left_entity, $this->esc_str($relation), $right_entity, $file, $line]);
188 1
    }
189
190 1
    protected function insert_cache($table) {
191 1
        assert('array_key_exists($table, $this->tables)');
192 1
        $fields = $this->tables[$table][1];
193 1
        $which = &$this->caches[$table];
194 1
        if (count($which) == 0) {
195 1
            return;
196
        }
197 1
        $stmt = "INSERT INTO $table (".implode(", ", $fields).") VALUES\n";
198 1
        $values = [];
199 1
        foreach ($which as $v) {
200 1
            $values[] = "(".implode(", ", $v).")";
201 1
        }
202 1
        $stmt .= implode(",\n", $values).";";
203 1
        $this->connection->exec($stmt);
204 1
        $which = [];
205 1
    }
206
207 1
    protected function append_and_maybe_flush($table, $values) {
208 1
        if ($values[0] === null) {
209 1
            $id = $this->id_counter++;
210 1
            $values[0] = $id;
211 1
        }
212
        else {
213 1
            $id = null;
214
        }
215
216 1
        $which = &$this->caches[$table];
217 1
        $which[] = $values;
218 1
        if (count($which) > $this->nodes_per_insert) {
219
            $this->insert_cache($table);
220
        }
221
222 1
        return $id;
223
    }
224 1
    protected function esc_str($str) {
225 1
        assert('is_string($str)');
226 1
        return '"'.str_replace('"', '""', $str).'"';
227
    }
228
229 1
    protected function esc_maybe_null($val) {
230 1
        assert('!is_string($val)');
231 1
        if ($val === null) {
232 1
            return "NULL";
233
        }
234 1
        return $val;
235
    }
236
237
    /**
238
     * Write everything that currently is in cache to the database.
239
     *
240
     * @return null
241
     */
242 1
    public function write_cached_inserts() {
243 1
        foreach ($this->tables as $table => $_) {
244 1
            $this->insert_cache($table);
245 1
        }
246 1
    }
247
248
    /**
249
     * Read the index from the database.
250
     *
251
     * @return  Graph\IndexDB   $index
252
     */
253 1
    public function to_graph_index() {
254 1
        $index = $this->build_graph_index_db();
255
        $reader = new IndexDBReader
256 1
            ( $this->tables
257 1
            , $this->id_fields
258 1
            , $this->int_fields
259
            , function () { return $this->builder(); }
260 1
            , $index
261 1
            );
262 1
        $reader->run();
263 1
        return $index;
264
    }
265
266
    protected function build_graph_index_db() {
267
        return new Graph\IndexDB();
268
    }
269
270
    // INIT DATABASE
271
272 1
    public function init_table($name, Schema\Schema $schema, Schema\Table $file_table = null, Schema\Table $namespace_table = null) {
273 1
        assert('array_key_exists($name, $this->tables)');
274 1
        $table = $schema->createTable($name);
275 1
        foreach ($this->tables[$name][1] as $field) {
276
            switch ($field) {
277 1
                case "id":
278 1
                case "file_id":
279 1
                case "class_id":
280 1
                case "left_id":
281 1
                case "right_id":
282 1
                case "start_line":
283 1
                case "end_line":
284 1
                case "line":
285 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...
286 1
                    $table->addColumn
287 1
                        ($field, "integer"
288 1
                        , ["notnull" => true, "unsigned" => true]
289 1
                        );
290 1
                    break;
291 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...
292 1
                    $table->addColumn
293 1
                        ($field, "integer"
294 1
                        , ["notnull" => false, "unsigned" => true]
295 1
                        );
296 1
                   break;
297 1
                case "path":
298 1
                case "source":
299 1
                case "name":
300 1
                case "relation":
301 1
                    $table->addColumn
302 1
                        ($field, "string"
303 1
                        , ["notnull" => true]
304 1
                        );
305 1
                    break;
306
                default:
307
                    throw new \LogicException("Unknown field '$field'");
308
            }
309 1
            if ($field == "id") {
310 1
                $table->setPrimaryKey(["id"]);
311 1
            }
312 1
            if ($field == "file_id") {
313 1
                if ($file_table instanceof Schema\Table) {
314 1
                    $table->addForeignKeyConstraint
315 1
                        ( $file_table
316 1
                        , array("file_id")
317 1
                        , array("id")
318 1
                        );
319 1
                }
320
                else {
321
                    throw new \LogicException(
322
                        "Expected \$file_table to be a schema when file_id is used.");
323
                }
324 1
            }
325 1
            if ($field == "namespace_id") {
326 1
                if ($namespace_table instanceof Schema\Table) {
327 1
                    $table->addForeignKeyConstraint
328 1
                        ( $namespace_table
329 1
                        , array("namespace_id")
330 1
                        , array("id")
331 1
                        );
332 1
                }
333
                else {
334
                    throw new \LogicException(
335
                        "Expected \$namespace_table to be a schema when namespace_id is used.");
336
                }
337 1
            }
338 1
        }
339 1
        return $table;
340
    }
341
342 1
    public function init_database_schema() {
343 1
        $schema = new Schema\Schema();
344
345 1
        $file_table = $this->init_table("files", $schema);
346 1
        $namespace_table = $this->init_table("namespaces", $schema);
347 1
        $this->init_table("classes", $schema, $file_table, $namespace_table);
348 1
        $this->init_table("interfaces", $schema, $file_table, $namespace_table);
349 1
        $this->init_table("traits", $schema, $file_table, $namespace_table);
350 1
        $this->init_table("methods", $schema, $file_table);
351 1
        $this->init_table("functions", $schema, $file_table, $namespace_table);
352 1
        $this->init_table("globals", $schema);
353 1
        $this->init_table("language_constructs", $schema);
354 1
        $this->init_table("method_references", $schema, $file_table);
355 1
        $this->init_table("function_references", $schema, $file_table);
356 1
        $relation_table = $this->init_table("relations", $schema, $file_table);
357 1
        $relation_table->setPrimaryKey(["left_id", "relation", "right_id"]);
358
359 1
        $sync = new SingleDatabaseSynchronizer($this->connection);
360 1
        $sync->createSchema($schema);
361 1
    }
362
}
363