Completed
Push — master ( 86b2cd...b16d12 )
by Richard
03:26
created

ResultDB::begin_rule()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 35
Code Lines 30

Duplication

Lines 27
Ratio 77.14 %

Importance

Changes 0
Metric Value
cc 3
eloc 30
nc 4
nop 1
dl 27
loc 35
rs 8.8571
c 0
b 0
f 0
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\Report;
12
13
use Lechimp\Dicto\Analysis\Listener;
14
use Lechimp\Dicto\Analysis\Violation;
15
use Lechimp\Dicto\DB\DB;
16
use Lechimp\Dicto\Rules\Ruleset;
17
use Lechimp\Dicto\Rules\Rule;
18
use Lechimp\Dicto\Variables\Variable;
19
use Doctrine\DBAL\Schema;
20
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
21
22
class ResultDB extends DB implements Listener {
23
    /**
24
     * @var int|null
25
     */
26
    private $current_run_id = null;
27
28
    /**
29
     * @var int|null
30
     */
31
    private $current_rule_id = null;
32
33
    // Analysis\Listener implementation
34
35
    /**
36
     * Announce to start a new run of the analysis now.
37
     *
38
     * @param   string  $commit_hash
39
     * @return  null
40
     */
41
    public function begin_run($commit_hash) {
42
        assert('is_string($commit_hash)');
43
        $this->builder()
44
            ->insert("runs")
45
            ->values(array
46
                ( "commit_hash" => "?"
47
                ))
48
            ->setParameter(0, $commit_hash)
49
            ->execute();
50
        $this->current_run_id = (int)$this->connection->lastInsertId();
51
    }
52
53
    /**
54
     * @inheritdoc
55
     */
56
    public function end_run() {
57
    }
58
59
    /**
60
     * @inheritdoc
61
     */
62
    public function begin_ruleset(Ruleset $rule) {
63
        // Nothing to do here...
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function end_ruleset() {
70
        // Nothing to do here...
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76
    public function begin_rule(Rule $rule) {
77
        assert('$this->current_run_id !== null');
78
        $rule_id = $this->rule_id($rule);
79 View Code Duplication
        if ($rule_id === null) {
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...
80
            $this->builder()
81
                ->insert("rules")
82
                ->values(array
83
                    ( "rule" => "?"
84
                    , "first_seen" => "?"
85
                    , "last_seen" => "?"
86
                    , "explanation" => "?"
87
                    ))
88
                ->setParameter(0, $rule->pprint())
89
                ->setParameter(1, $this->current_run_id)
90
                ->setParameter(2, $this->current_run_id)
91
                ->setParameter(3, $rule->explanation())
92
                ->execute();
93
            $rule_id = (int)$this->connection->lastInsertId();
94
        }
95
        else {
96
            $this->builder()
97
                ->update("rules")
98
                ->set("last_seen", "?")
99
                ->set("explanation", "?")
100
                ->where("id = ?")
101
                ->setParameter(0, $this->current_run_id)
102
                ->setParameter(1, $rule->explanation())
103
                ->setParameter(2, $rule_id)
104
                ->execute();
105
        }
106
        foreach ($rule->variables() as $variable) {
107
            $this->upsert_variable($variable);
108
        } 
109
        $this->current_rule_id = $rule_id;
110
    }
111
112
    /**
113
     * @inheritdoc
114
     */
115
    public function end_rule() {
116
        $this->current_rule_id = null;
117
    }
118
119
    /**
120
     * @inheritdoc
121
     */
122
    public function report_violation(Violation $violation) {
123
        assert('$this->current_run_id !== null');
124
        assert('$this->current_rule_id !== null');
125
        $violation_id = $this->violation_id($violation);
126 View Code Duplication
        if ($violation_id === null) {
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...
127
            $this->builder()
128
                ->insert("violations")
129
                ->values(array
130
                    ( "rule_id" => "?"
131
                    , "file" => "?"
132
                    , "line" => "?"
133
                    , "first_seen" => "?"
134
                    , "last_seen" => "?"
135
                    ))
136
                ->setParameter(0, $this->current_rule_id)
137
                ->setParameter(1, $violation->filename())
138
                ->setParameter(2, $violation->line())
139
                ->setParameter(3, $this->current_run_id)
140
                ->setParameter(4, $this->current_run_id)
141
                ->execute();
142
            $violation_id = (int)$this->connection->lastInsertId();
143
        }
144
        else {
145
            $this->builder()
146
                ->update("violations")
147
                ->set("last_seen", "?")
148
                ->where("id = ?")
149
                ->setParameter(0, $this->current_run_id)
150
                ->setParameter(1, $violation_id)
151
                ->execute();
152
        }
153
154
        $this->builder()
155
            ->insert("violation_locations")
156
            ->values(array
157
                ( "violation_id" => "?"
158
                , "run_id"  => "?"
159
                , "line_no" => "?"
160
                ))
161
            ->setParameter(0, $violation_id)
162
            ->setParameter(1, $this->current_run_id)
163
            ->setParameter(2, $violation->line_no())
164
            ->execute();
165
    }
166
167
    // Helpers
168
169
170
    /**
171
     * @param   Rule    $rule
172
     * @return  int|null
173
     */
174
    protected function rule_id(Rule $rule) {
175
        $res = $this->builder()
176
            ->select("id")
177
            ->from("rules")
178
            ->where("rule = ?")
179
            ->setParameter(0, $rule->pprint())
180
            ->execute()
181
            ->fetch();
182
        if ($res) {
183
            return (int)$res["id"];
184
        }
185
        else {
186
            return null;
187
        }
188
    }
189
190
    /**
191
     * @param   Variable $var
192
     * @return  int|null
193
     */
194 View Code Duplication
    protected function variable_id(Variable $var) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
195
        $res = $this->builder()
196
            ->select("id")
197
            ->from("variables")
198
            ->where($this->builder()->expr()->andX
199
                ( "name = ?"
200
                , "meaning = ?"
201
                ))
202
            ->setParameter(0, $var->name())
203
            ->setParameter(1, $var->meaning())
204
            ->execute()
205
            ->fetch();
206
        if ($res) {
207
            return (int)$res["id"];
208
        }
209
        else {
210
            return null;
211
        }
212
    }
213
214
    protected function upsert_variable(Variable $var) {
215
        $var_id = $this->variable_id($var);
216
        if ($var_id === null) {
217
            $var_id = $this->insert_variable($var);            
218
        }
219
        else {
220
            $this->update_variable($var_id);
221
        }
222
        return $var_id;
223
    }
224
225
    protected function insert_variable(Variable $var) {
226
        assert('$this->current_run_id !== null');
227
        $this->builder()
228
            ->insert("variables")
229
            ->values(array
230
                ( "name" => "?"
231
                , "meaning" => "?"
232
                , "first_seen" => "?"
233
                , "last_seen" => "?"
234
                ))
235
            ->setParameter(0, $var->name())
236
            ->setParameter(1, $var->meaning())
237
            ->setParameter(2, $this->current_run_id)
238
            ->setParameter(3, $this->current_run_id)
239
            ->execute();
240
        return (int)$this->connection->lastInsertId();
241
    }
242
243
    protected function update_variable($var_id) {
244
        assert('is_integer($var_id)');
245
        assert('$this->current_run_id !== null');
246
        $this->builder()
247
            ->update("variables")
248
            ->set("last_seen", "?")
249
            ->where("id = ?")
250
            ->setParameter(0, $this->current_run_id)
251
            ->setParameter(1, $var_id)
252
            ->execute();
253
    }
254
255
    /**
256
     * @param   Violation   $violation
257
     * @return  int|null
258
     */
259 View Code Duplication
    protected function violation_id(Violation $violation) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
260
        $res = $this->builder()
261
            ->select("id")
262
            ->from("violations")
263
            ->where($this->builder()->expr()->andX
264
                ( "rule_id = ?"
265
                , "file = ?"
266
                , "line = ?"
267
                ))
268
            ->setParameter(0, $this->current_rule_id)
269
            ->setParameter(1, $violation->filename())
270
            ->setParameter(2, $violation->line())
271
            ->execute()
272
            ->fetch();
273
        if ($res) {
274
            return (int)$res["id"];
275
        }
276
        else {
277
            return null;
278
        }
279
    }
280
281
    // Creation of database.
282
283
    protected function init_run_table(Schema\Schema $schema) {
284
        $run_table = $schema->createTable("runs");
285
        $run_table->addColumn
286
            ("id", "integer"
287
            , array("notnull" => true, "unsigned" => true, "autoincrement" => true)
288
            );
289
        $run_table->addColumn
290
            ( "commit_hash", "string"
291
            , array("notnull" => true)
292
            );
293
        // TODO: maybe add time
294
        // TODO: do we need some other meta information per run of the analysis? 
295
        // TODO: Might be a good idea to store config and rules file here
296
        $run_table->setPrimaryKey(array("id"));
297
298
        return $run_table;
299
    }
300
301
    protected function init_rule_table(Schema\Schema $schema, Schema\Table $run_table) {
302
        $rule_table = $schema->createTable("rules");
303
        $rule_table->addColumn
304
            ( "id", "integer"
305
            , array("notnull" => true, "unsigned" => true, "autoincrement" => true)
306
            );
307
        $rule_table->addColumn
308
            ( "rule", "string"
309
            , array("notnull" => true)
310
            );
311
312
        $rule_table->addColumn
313
            ( "explanation", "string"
314
            , array("notnull" => false)
315
            );
316
        $rule_table->addColumn
317
            ( "first_seen", "integer"
318
            , array("notnull" => true)
319
            );
320
        $rule_table->addColumn
321
            ( "last_seen", "integer"
322
            , array("notnull" => true)
323
            );
324
        $rule_table->setPrimaryKey(array("id"));
325
        $rule_table->addUniqueIndex(array("rule"));
326
        $rule_table->addForeignKeyConstraint
327
            ( $run_table
328
            , array("first_seen")
329
            , array("id")
330
            );
331
        $rule_table->addForeignKeyConstraint
332
            ( $run_table
333
            , array("last_seen")
334
            , array("id")
335
            );
336
337
        return $rule_table;
338
    }
339
340
    public function init_variable_table(Schema\Schema $schema) {
341
        $variable_table = $schema->createTable("variables");
342
        $variable_table->addColumn
343
            ( "id", "integer"
344
            , array("notnull" => true)
345
            );
346
        $variable_table->addColumn
347
            ( "name", "string"
348
            , array("notnull" => true)
349
            );
350
        $variable_table->addColumn
351
            ( "meaning", "string"
352
            , array("notnull" => true)
353
            );
354
        // TODO: Some field for explanation is missing here.
355
        $variable_table->addColumn
356
            ( "first_seen", "integer"
357
            , array("notnull" => true)
358
            );
359
        $variable_table->addColumn
360
            ( "last_seen", "integer"
361
            , array("notnull" => true)
362
            );
363
        $variable_table->setPrimaryKey(array("id"));
364
365
        return $variable_table;
366
    }
367
368
    protected function init_violation_table(Schema\Schema $schema, Schema\Table $run_table, Schema\Table $rule_table) {
369
        $violation_table = $schema->createTable("violations");
370
        $violation_table->addColumn
371
            ( "id", "integer"
372
            , array("notnull" => true, "unsigned" => true, "autoincrement" => true)
373
            );
374
        $violation_table->addColumn
375
            ( "rule_id", "integer"
376
            , array("notnull" => true)
377
            );
378
        $violation_table->addColumn
379
            ( "file", "string"
380
            , array("notnull" => true)
381
            );
382
        $violation_table->addColumn
383
            ( "line", "string"
384
            , array("notnull" => true)
385
            );
386
        $violation_table->addColumn
387
            ( "first_seen", "integer"
388
            , array("notnull" => true)
389
            );
390
        $violation_table->addColumn
391
            ( "last_seen", "integer"
392
            , array("notnull" => true)
393
            );
394
        $violation_table->setPrimaryKey(array("id"));
395
        $violation_table->addUniqueIndex(array("rule_id", "file", "line"));
396
        $violation_table->addForeignKeyConstraint
397
            ( $rule_table
398
            , array("rule_id")
399
            , array("id")
400
            );
401
        $violation_table->addForeignKeyConstraint
402
            ( $run_table
403
            , array("first_seen")
404
            , array("id")
405
            );
406
        $violation_table->addForeignKeyConstraint
407
            ( $run_table
408
            , array("last_seen")
409
            , array("id")
410
            );
411
412
        return $violation_table;
413
    }
414
415
    protected function init_violation_location_table(Schema\Schema $schema, Schema\Table $run_table, Schema\Table $violation_table) {
416
        $violation_location_table = $schema->createTable("violation_locations");
417
        $violation_location_table->addColumn
418
            ( "id", "integer"
419
            , array("notnull" => true, "unsigned" => true, "autoincrement" => true)
420
            );
421
        $violation_location_table->addColumn
422
            ( "violation_id", "integer"
423
            , array("notnull" => true)
424
            );
425
        $violation_location_table->addColumn
426
            ( "run_id", "integer"
427
            , array("notnull" => true)
428
            );
429
        $violation_location_table->addColumn
430
            ( "line_no", "integer"
431
            , array("notnull" => true)
432
            );
433
        $violation_location_table->setPrimaryKey(array("id"));
434
        $violation_location_table->addForeignKeyConstraint
435
            ( $violation_table
436
            , array("violation_id")
437
            , array("id")
438
            );
439
        $violation_location_table->addForeignKeyConstraint
440
            ( $run_table
441
            , array("run_id")
442
            , array("id")
443
            );
444
        return $violation_location_table;
445
    }
446
447
    public function init_database_schema() {
448
        $schema = new Schema\Schema();
449
450
        $run_table = $this->init_run_table($schema);
451
        $rule_table = $this->init_rule_table($schema, $run_table);
452
        $this->init_variable_table($schema);
453
        $violation_table = $this->init_violation_table($schema, $run_table, $rule_table);
454
        $this->init_violation_location_table($schema, $run_table, $violation_table);
455
456
        $sync = new SingleDatabaseSynchronizer($this->connection);
457
        $sync->createSchema($schema);
458
    }
459
}
460