Passed
Pull Request — master (#18)
by
unknown
06:05
created

GroupbyIterator   C

Complexity

Total Complexity 7

Size/Duplication

Total Lines 75
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 24

Test Coverage

Coverage 73.91%

Importance

Changes 4
Bugs 0 Features 2
Metric Value
dl 0
loc 75
ccs 17
cts 23
cp 0.7391
rs 5.238
c 4
b 0
f 2
wmc 7
lcom 0
cbo 24

4 Methods

Rating   Name   Duplication   Size   Complexity  
A key() 0 4 1
A count() 0 4 1
A __construct() 0 20 4
A toArray() 0 12 1
1
<?php
2
3
namespace Zicht\Itertools\lib;
4
5
use Zicht\Itertools\lib\Traits\AllTrait;
6
use Zicht\Itertools\lib\Traits\AnyTrait;
7
use Zicht\Itertools\lib\Traits\ArrayAccessTrait;
8
use Zicht\Itertools\lib\Traits\ChainTrait;
9
use Zicht\Itertools\lib\Traits\CycleTrait;
10
use Zicht\Itertools\lib\Traits\DebugInfoTrait;
11
use Zicht\Itertools\lib\Traits\FilterTrait;
12
use Zicht\Itertools\lib\Traits\FirstTrait;
13
use Zicht\Itertools\lib\Traits\GetterTrait;
14
use Zicht\Itertools\lib\Traits\GroupByTrait;
15
use Zicht\Itertools\lib\Traits\ItemsTrait;
16
use Zicht\Itertools\lib\Traits\KeysTrait;
17
use Zicht\Itertools\lib\Traits\LastTrait;
18
use Zicht\Itertools\lib\Traits\MapByTrait;
19
use Zicht\Itertools\lib\Traits\MapTrait;
20
use Zicht\Itertools\lib\Traits\ReduceTrait;
21
use Zicht\Itertools\lib\Traits\ReversedTrait;
22
use Zicht\Itertools\lib\Traits\SliceTrait;
23
use Zicht\Itertools\lib\Traits\SortedTrait;
24
use Zicht\Itertools\lib\Traits\ToArrayTrait;
25
use Zicht\Itertools\lib\Traits\UniqueTrait;
26
use Zicht\Itertools\lib\Traits\ValuesTrait;
27
use Zicht\Itertools\lib\Traits\ZipTrait;
28
29
// todo: add unit tests for Countable interface
30
// todo: add unit tests for ArrayAccess interface
31
// todo: place the two classed in their own file
32
33
class GroupedIterator extends \IteratorIterator implements \Countable, \ArrayAccess
34
{
35
    use ArrayAccessTrait;
36
    use DebugInfoTrait;
37
    use GetterTrait;
38
39
    // Fluent interface traits
40
    use AllTrait;
41
    use AnyTrait;
42
    use ChainTrait;
43
    use CycleTrait;
44
    use FilterTrait;
45
    use FirstTrait;
46
    use GroupByTrait;
47
    use ItemsTrait;
48
    use KeysTrait;
49
    use LastTrait;
50
    use MapByTrait;
51
    use MapTrait;
52
    use ReduceTrait;
53
    use ReversedTrait;
54
    use SliceTrait;
55
    use SortedTrait;
56
    use ToArrayTrait;
57
    use UniqueTrait;
58
    use ValuesTrait;
59
    use ZipTrait;
60
61
    protected $groupKey;
62
    protected $values;
63
64 13
    public function __construct($groupKey)
65
    {
66 13
        $this->groupKey = $groupKey;
67 13
        parent::__construct(new \ArrayIterator());
68 13
    }
69
70 12
    public function getGroupKey()
71
    {
72 12
        return $this->groupKey;
73
    }
74
75 13
    public function append($key, $value)
76
    {
77 13
        $this->getInnerIterator()->append(array($key, $value));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Iterator as the method append() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, Issue523, RecursiveArrayIterator, Zicht\Itertools\lib\ChainIterator, Zicht\Itertools\lib\GroupedIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
78 13
    }
79
80 12
    public function current()
81
    {
82 12
        return $this->getInnerIterator()->current()[1];
83
    }
84
85 12
    public function key()
86
    {
87 12
        return $this->getInnerIterator()->current()[0];
88
    }
89
90 11
    public function count()
91
    {
92 11
        return iterator_count($this->getInnerIterator());
93
    }
94
}
95
96
class GroupbyIterator extends \IteratorIterator implements \Countable, \ArrayAccess
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
97
{
98
    use ArrayAccessTrait;
99
    use DebugInfoTrait;
100
    use GetterTrait;
101
102
    // Fluent interface traits
103
    use AllTrait;
104
    use AnyTrait;
105
    use ChainTrait;
106
    use CycleTrait;
107
    use FilterTrait;
108
    use FirstTrait;
109
    use GroupByTrait;
110
    use ItemsTrait;
111
    use KeysTrait;
112
    use LastTrait;
113
    use MapByTrait;
114
    use MapTrait;
115
    use ReduceTrait;
116
    use ReversedTrait;
117
    use SliceTrait;
118
    use SortedTrait;
119
    use ToArrayTrait;
120
    use UniqueTrait;
121
    use ValuesTrait;
122
    use ZipTrait;
123
124 13
    public function __construct(\Closure $func, \Iterator $iterable)
125
    {
126
        // todo: this implementation pre-computes everything... this is
127
        // not the way an iterator should work.  Please re-write.
128 13
        $groupedIterator = null;
129 13
        $previousGroupKey = null;
130 13
        $data = array();
131
132 13
        foreach ($iterable as $key => $value) {
133 13
            $groupKey = call_user_func($func, $value, $key);
134 13
            if ($previousGroupKey !== $groupKey || $groupedIterator === null) {
135 13
                $previousGroupKey = $groupKey;
136 13
                $groupedIterator = new GroupedIterator($groupKey);
137 13
                $data [] = $groupedIterator;
138
            }
139 13
            $groupedIterator->append($key, $value);
140
        }
141
142 13
        parent::__construct(new \ArrayIterator($data));
143 13
    }
144
145 12
    public function key()
146
    {
147 12
        return $this->current()->getGroupKey();
148
    }
149
150 11
    public function count()
151
    {
152 11
        return iterator_count($this->getInnerIterator());
153
    }
154
155
    /**
156
     * @{inheritDoc}
157
     */
158
    public function toArray()
159
    {
160
        $array = iterator_to_array($this);
161
        array_walk(
162
            $array,
163
            function (&$value) {
164
                /** @var GroupedIterator $value */
165
                $value = $value->toArray();
166
            }
167
        );
168
        return $array;
169
    }
170
}
171