Completed
Push — master ( 68cb49...5652ae )
by Sébastien
07:10
created

ResultSet::toArray()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.9256

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
ccs 10
cts 15
cp 0.6667
rs 8.8571
cc 5
eloc 13
nc 5
nop 0
crap 5.9256
1
<?php
2
3
namespace Soluble\FlexStore\ResultSet;
4
5
use Soluble\FlexStore\Source\AbstractSource;
6
use Soluble\FlexStore\Helper\Paginator;
7
use Soluble\FlexStore\Options\HydrationOptions;
8
use Soluble\FlexStore\Column\ColumnModel;
9
use ArrayObject;
10
11
class ResultSet extends AbstractResultSet
12
{
13
    /**
14
     *
15
     * @var Paginator
16
     */
17
    protected $paginator;
18
19
    /**
20
     *
21
     * @var integer
22
     */
23
    protected $totalRows;
24
25
    /**
26
     * @var AbstractSource
27
     */
28
    protected $source;
29
30
    /**
31
     *
32
     * @var HydrationOptions
33
     */
34
    protected $hydrationOptions;
35
36
    /**
37
     *
38
     * @var boolean
39
     */
40
    protected $hydrate_options_initialized = false;
41
42
    /**
43
     * @var ArrayObject
44
     */
45
    protected $hydration_formatters;
46
47
    /**
48
     * @var ArrayObject
49
     */
50
    protected $hydration_renderers;
51
52
    /**
53
     * @var ArrayObject|null
54
     */
55
    protected $hydrated_columns;
56
57
58
59
    /**
60
     * Return source column model
61
     *
62
     * @throws Exception\RuntimeException
63
     * @return ColumnModel
64
     */
65 10
    public function getColumnModel()
66
    {
67 10
        if ($this->source === null) {
68 1
            throw new Exception\RuntimeException(__METHOD__ . " Prior to get column model, a source must be set.");
69
        }
70 9
        $this->hydrate_options_initialized = false;
71 9
        return $this->source->getColumnModel();
72
    }
73
74
    /**
75
     *
76
     * @param AbstractSource $source
77
     * @return ResultSet
78
     */
79 39
    public function setSource(AbstractSource $source)
80
    {
81 39
        $this->source = $source;
82 39
        return $this;
83
    }
84
85
    /**
86
     *
87
     * @return AbstractSource
88
     */
89 9
    public function getSource()
90
    {
91 9
        return $this->source;
92
    }
93
94
    /**
95
     *
96
     * @param HydrationOptions $hydrationOptions
97
     * @return ResultSet
98
     */
99 39
    public function setHydrationOptions(HydrationOptions $hydrationOptions)
100
    {
101 39
        $this->hydrationOptions = $hydrationOptions;
102 39
        return $this;
103
    }
104
105
    /**
106
     *
107
     * @return HydrationOptions
108
     */
109 9
    public function getHydrationOptions()
110 9
    {
111 9
        return $this->hydrationOptions;
112
    }
113
114
115
    /**
116
     *
117
     * @return Paginator
118
     */
119 2
    public function getPaginator()
120
    {
121 2
        if ($this->paginator === null) {
122 2
            $this->paginator = new Paginator(
123 2
                $this->getTotalRows(),
124 2
                $this->getSource()->getOptions()->getLimit(),
125 2
                $this->getSource()->getOptions()->getOffset()
126 2
            );
127 1
        }
128 1
        return $this->paginator;
129
    }
130
131
    /**
132
     * Set the total rows
133
     * @param int $totalRows
134
     * @return ResultSet
135
     */
136 39
    public function setTotalRows($totalRows)
137
    {
138 39
        $this->totalRows = (int) $totalRows;
139 39
        return $this;
140
    }
141
142
    /**
143
     * @return int
144
     */
145 13
    public function getTotalRows()
146
    {
147 13
        return $this->totalRows;
148
    }
149
150
    /**
151
     *
152
     * @param ArrayObject $row
153
     * @return null
154
     */
155 26
    protected function initColumnModelHydration(ArrayObject $row)
156
    {
157 26
        $this->hydration_formatters = new ArrayObject();
158 26
        $this->hydration_renderers = new ArrayObject();
159 26
        $this->hydrated_columns = null;
160
161 26
        if ($this->source->hasColumnModel()) {
162 9
            $cm = $this->getColumnModel();
163
164
            // 1. Initialize columns hydrators
165 9
            if ($this->getHydrationOptions()->isFormattersEnabled()) {
166 9
                $formatters = $cm->getUniqueFormatters();
167 9
                if ($formatters->count() > 0) {
168 2
                    $this->hydration_formatters = $formatters;
169 2
                }
170 9
            }
171
172
            // 2. Initialize hydrated columns
173
174 9
            if ($this->getHydrationOptions()->isColumnExclusionEnabled()) {
175 9
                $columns = $cm->getColumns();
176
177
                // Performance:
178
                // Only if column model definition differs from originating
179
                // source row definition.
180 9
                $hydrated_columns = array_keys((array) $columns);
181 9
                $row_columns = array_keys((array) $row);
182 9
                if ($hydrated_columns != $row_columns) {
183 5
                    $this->hydrated_columns = new ArrayObject($hydrated_columns);
184 5
                }
185 9
            }
186
187
            // 3. Initialize row renderers
188 9
            if ($this->getHydrationOptions()->isRenderersEnabled()) {
189 9
                $this->hydration_renderers = $cm->getRowRenderers();
190 9
            }
191 9
        }
192 26
        $this->hydrate_options_initialized = true;
193 26
    }
194
195
    /**
196
     * Return the current row as an array|ArrayObject.
197
     * If setLimitColumns() have been set, will only return
198
     * the limited columns.
199
     *
200
     * @throws Exception\UnknownColumnException
201
     * @return array|ArrayObject|null
202
     */
203 26
    public function current()
204
    {
205 26
        $row = $this->zfResultSet->current();
206 26
        if ($row === null) {
207
            return;
208
        }
209
210 26
        if (!$this->hydrate_options_initialized) {
211 26
            $this->initColumnModelHydration($row);
0 ignored issues
show
Bug introduced by
It seems like $row defined by $this->zfResultSet->current() on line 205 can also be of type array; however, Soluble\FlexStore\Result...tColumnModelHydration() does only seem to accept object<ArrayObject>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
212 26
        }
213
214
215
        // 1. Row renderers
216 26
        foreach ($this->hydration_renderers as $renderer) {
217 5
            $renderer->apply($row);
218 25
        }
219
220
        // 2. Formatters
221 25
        foreach ($this->hydration_formatters as $formatters) {
222 2
            foreach ($formatters['columns'] as $column) {
223 2
                $row[$column] = $formatters['formatter']->format($row[$column], $row);
224 2
            }
225 25
        }
226
227
        // 3. Process column hydration
228 25
        if ($this->hydrated_columns !== null) {
229 4
            $d = new ArrayObject();
230 4
            foreach ($this->hydrated_columns as $column) {
231 4
                $d->offsetSet($column, isset($row[$column]) ? $row[$column] : null);
232 4
            }
233 4
            $row->exchangeArray($d);
234 4
        }
235
236 25
        if ($this->returnType === self::TYPE_ARRAY) {
237
            return (array) $row;
238
        }
239 25
        return $row;
240
    }
241
242
    /**
243
     * Cast result set to array of arrays
244
     *
245
     * @return array
246
     * @throws Exception\RuntimeException if any row is not castable to an array
247
     */
248 25
    public function toArray()
249
    {
250 25
        $return = [];
251 25
        foreach ($this as $row) {
252 23
            if (is_array($row)) {
253
                $return[] = $row;
254 23
            } elseif (method_exists($row, 'toArray')) {
255
                $return[] = $row->toArray();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ArrayObject as the method toArray() does only exist in the following sub-classes of ArrayObject: Zend\Stdlib\Parameters. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
256 23
            } elseif (method_exists($row, 'getArrayCopy')) {
257 23
                $return[] = $row->getArrayCopy();
258 23
            } else {
259
                throw new Exception\RuntimeException(
260
                    __METHOD__ . ': Rows as part of this DataSource, with type ' . gettype($row) . ' cannot be cast to an array'
261
                );
262
            }
263 24
        }
264 24
        return $return;
265
    }
266
267
    /**
268
     * Iterator: is pointer valid?
269
     *
270
     * @return bool
271
     */
272 25
    public function valid()
273
    {
274 25
        $valid =  $this->zfResultSet->valid();
275 25
        if (!$valid) {
276 24
            $this->hydrate_options_initialized = false;
277 24
        }
278 25
        return $valid;
279
    }
280
281
    /**
282
     * Iterator: rewind
283
     *
284
     * @return void
285
     */
286 25
    public function rewind()
287
    {
288 25
        $this->hydrate_options_initialized = false;
289 25
        $this->zfResultSet->rewind();
290 25
    }
291
}
292