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