Completed
Push — master ( 93cace...d12a55 )
by Richard
06:16
created

QueryImpl   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 212
Duplicated Lines 9.43 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 97.25%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 5
dl 20
loc 212
ccs 106
cts 109
cp 0.9725
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A predicate_factory() 0 3 1
A expand() 0 6 1
A extract() 0 6 1
A filter() 0 15 2
B run() 0 23 5
A switch_run_command() 0 15 4
A run_expand() 10 10 3
A run_extract() 0 11 3
A run_filter() 10 10 3
A add_result() 0 7 2
A filter_by_types() 0 16 2
A expand_relations() 0 9 1
A expand_target() 0 5 1

How to fix   Duplicated Code   

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:

1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 * 
5
 * Copyright (c) 2016 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\Graph;
12
13
class QueryImpl implements Query {
14
    /**
15
     * @var Graph
16
     */
17
    protected $graph;
18
19
    /**
20
     * @var array[]
21
     */
22
    protected $steps;
23
24
    /**
25
     * @var PredicateFactory
26
     */
27
    protected $predicate_factory;
28
29 62
    public function __construct(Graph $graph) {
30 62
        $this->graph = $graph;
31 62
        $this->steps = [];
32 62
        $this->predicate_factory = new PredicateFactory();
33 62
    }
34
35
    /**
36
     * @inheritdocs
37
     */
38 42
    public function predicate_factory() {
39 42
        return $this->predicate_factory;
40
    }
41
42
    /**
43
     * @inheritdocs
44
     */
45 34
    public function expand(\Closure $expander) {
46 34
        $clone = clone $this;
47 34
        $clone->steps[] = ["expand", $expander];
48 34
        assert('$this->steps != $clone->steps');
49 34
        return $clone;
50
    }
51
52
    /**
53
     * @inheritdocs
54
     */
55 60
    public function extract(\Closure $extractor) {
56 60
        $clone = clone $this;
57 60
        $clone->steps[] = ["extract", $extractor];
58 60
        assert('$this->steps != $clone->steps');
59 60
        return $clone;
60
    }
61
62
    /**
63
     * @inheritdocs
64
     */
65 54
    public function filter(Predicate $predicate) {
66 54
        $clone = clone $this;
67 54
        if (count($this->steps) == 0) {
68
            // On the first step we keep the predicate to later put it into
69
            // graph->nodes.
70 54
            $clone->steps[] = ["filter", $predicate];
71 54
        }
72
        else {
73
            // For later steps, we already compile the predicate to only
74
            // compile it once.
75 27
            $clone->steps[] = ["filter", $predicate->compile()];
76
        }
77 54
        assert('$this->steps != $clone->steps');
78 54
        return $clone;
79
    }
80
81
    /**
82
     * @inheritdocs
83
     */
84 60
    public function run($result) {
85 60
        $steps = $this->steps;
86 60
        if (count($steps) > 0 && $steps[0][0] == "filter") {
87 54
            $nodes = $this->graph->nodes($steps[0][1]);
88 54
            array_shift($steps);
89 54
        }
90
        else {
91 6
            $nodes = $this->graph->nodes();
92
        }
93 60
        $nodes = $this->add_result($nodes, $result);
94
95 60
        foreach ($steps as $step) {
96 60
            $nodes = $this->switch_run_command($nodes, $step);
97 60
        }
98
99 60
        $res = array();
100 60
        while ($nodes->valid()) {
101 43
            $val = $nodes->current();
102 43
            $res[] = $val[1];
103 43
            $nodes->next();
104 43
        }
105 60
        return $res;
106
    }
107
108
    /**
109
     * @return  Iterator <[Node,mixed]>
110
     */
111 60
    protected function switch_run_command(\Iterator $nodes, $step) {
112 60
        list($cmd,$par) = $step;
113 60
        if ($cmd == "expand") {
114 34
            return $this->run_expand($nodes, $par);
115
        }
116 60
        elseif ($cmd == "extract") {
117 60
            return $this->run_extract($nodes, $par);
118
        }
119 27
        elseif ($cmd == "filter") {
120 27
            return $this->run_filter($nodes, $par);
121
        }
122
        else {
123
            throw new \LogicException("Unknown command: $cmd");
124
        }
125
    }
126
127
    /**
128
     * @return  Iterator <[Node,mixed]>
129
     */
130 34 View Code Duplication
    protected function run_expand(\Iterator $nodes, \Closure $clsr) {
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...
131 34
        while ($nodes->valid()) {
132 27
            list($node, $result) = $nodes->current();
133
            // TODO: let closure return an Iterator too.
134 27
            foreach($clsr($node) as $new_node) {
135 22
                yield [$new_node, $result];
136 27
            }
137 27
            $nodes->next();
138 27
        }
139 34
    }
140
141
    /**
142
     * @return  Iterator <[Node,mixed]>
143
     */
144 60
    protected function run_extract(\Iterator $nodes, \Closure $clsr) {
145 60
        while ($nodes->valid()) {
146 44
            list($node, $result) = $nodes->current();
147 44
            if (is_object($result)) {
148
                $result = clone($result);
149
            }
150 44
            $clsr($node, $result);
151 44
            yield [$node, $result];
152 44
            $nodes->next();
153 44
        }
154 60
    }
155
156
    /**
157
     * @return  Iterator <[Node,mixed]>
158
     */
159 27 View Code Duplication
    protected function run_filter(\Iterator $nodes, \Closure $predicate) {
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...
160 27
        while ($nodes->valid()) {
161 21
            $val = $nodes->current();
162 21
            list($node, $result) = $val;
163 21
            if ($predicate($node, $result)) {
164 12
                yield $val;
165 12
            }
166 21
            $nodes->next();
167 21
        }
168 27
    }
169
170
    /**
171
     * @return  Iterator <[Node,mixed]>
172
     */
173 60
    protected function add_result(\Iterator $nodes, &$result) {
174 60
        while ($nodes->valid()) {
175 57
            $node = $nodes->current();
176 57
            yield [$node, $result];
177 57
            $nodes->next();
178 57
        }
179 60
    }
180
181
    // Convenience Functions
182
183
    /**
184
     * @inheritdocs
185
     */
186 14
    public function filter_by_types(array $types) {
187 14
        $f = $this->predicate_factory();
188 14
        if (count($types) == 0) {
189 1
            return $f->_false();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $f->_false(); (Lechimp\Dicto\Graph\Predicate\_False) is incompatible with the return type declared by the interface Lechimp\Dicto\Graph\Query::filter_by_types of type Lechimp\Dicto\Graph\Query.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
190
        }
191 13
        return $this->filter
192 13
            ( $f->_and
0 ignored issues
show
Bug introduced by
It seems like $f->_and(array_map(funct...ype_is($t); }, $types)) can be null; however, filter() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
193 13
                ( array_map
194
                    ( function($t) use ($f) {
195 13
                            return $f->_type_is($t);
196
                        }
197 13
                    , $types
198 13
                    )
199 13
                )
200 13
            );
201
    }
202
203
    /**
204
     * @inheritdocs
205
     */
206 29
    public function expand_relations(array $types) {
207
        return $this->expand(function(Node $n) use (&$types) {
208
            return array_filter
209 23
                ( $n->relations()
210
                , function(Relation $r) use (&$types) {
211 22
                    return in_array($r->type(), $types);
212 23
                });
213 29
        });
214
    }
215
216
    /**
217
     * @inheritdocs
218
     */
219
    public function expand_target() {
220 18
        return $this->expand(function(Relation $r) {
221 14
            return [$r->target()];
222 18
        });
223
    }
224
}
225