Completed
Push — master ( 66dded...f21dfb )
by Sébastien
08:51
created

ResultSet::setSource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
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
     * @var Paginator
15
     */
16
    protected $paginator;
17
18
    /**
19
     * @var int
20
     */
21
    protected $totalRows;
22
23
    /**
24
     * @var AbstractSource
25
     */
26
    protected $source;
27
28
    /**
29
     * @var HydrationOptions
30
     */
31
    protected $hydrationOptions;
32
33
    /**
34
     * @var bool
35
     */
36
    protected $hydrate_options_initialized = false;
37
38
    /**
39
     * @var ArrayObject
40
     */
41
    protected $hydration_formatters;
42
43
    /**
44
     * @var ArrayObject
45
     */
46
    protected $hydration_renderers;
47
48
    /**
49
     * @var ArrayObject|null
50
     */
51
    protected $hydrated_columns;
52
53
    /**
54
     * Return source column model.
55
     *
56
     * @throws Exception\RuntimeException
57
     *
58
     * @return ColumnModel
59
     */
60 10
    public function getColumnModel()
61
    {
62 10
        if ($this->source === null) {
63 1
            throw new Exception\RuntimeException(__METHOD__ . ' Prior to get column model, a source must be set.');
64
        }
65 9
        $this->hydrate_options_initialized = false;
66
67 9
        return $this->source->getColumnModel();
68
    }
69
70
    /**
71
     * @param AbstractSource $source
72
     *
73
     * @return ResultSet
74
     */
75 40
    public function setSource(AbstractSource $source)
76
    {
77 40
        $this->source = $source;
78
79 40
        return $this;
80
    }
81
82
    /**
83
     * @return AbstractSource
84
     */
85 9
    public function getSource()
86
    {
87 9
        return $this->source;
88
    }
89
90
    /**
91
     * @param HydrationOptions $hydrationOptions
92
     *
93
     * @return ResultSet
94
     */
95 40
    public function setHydrationOptions(HydrationOptions $hydrationOptions)
96
    {
97 40
        $this->hydrationOptions = $hydrationOptions;
98
99 40
        return $this;
100
    }
101
102
    /**
103
     * @return HydrationOptions
104
     */
105 9
    public function getHydrationOptions()
106
    {
107 9
        return $this->hydrationOptions;
108
    }
109
110
    /**
111
     * @return Paginator
112
     */
113 2
    public function getPaginator()
114
    {
115 2
        if ($this->paginator === null) {
116 2
            $this->paginator = new Paginator(
117 2
                $this->getTotalRows(),
118 2
                $this->getSource()->getOptions()->getLimit(),
119 2
                $this->getSource()->getOptions()->getOffset()
120 2
            );
121 1
        }
122
123 1
        return $this->paginator;
124
    }
125
126
    /**
127
     * Set the total rows.
128
     *
129
     * @param int $totalRows
130
     *
131
     * @return ResultSet
132
     */
133 40
    public function setTotalRows($totalRows)
134
    {
135 40
        $this->totalRows = (int) $totalRows;
136
137 40
        return $this;
138
    }
139
140
    /**
141
     * @return int
142
     */
143 13
    public function getTotalRows()
144
    {
145 13
        return $this->totalRows;
146
    }
147
148
    /**
149
     * @param ArrayObject $row
150
     */
151 27
    protected function initColumnModelHydration(ArrayObject $row)
152
    {
153 27
        $this->hydration_formatters = new ArrayObject();
154 27
        $this->hydration_renderers = new ArrayObject();
155 27
        $this->hydrated_columns = null;
156
157 27
        if ($this->source->hasColumnModel()) {
158 9
            $cm = $this->getColumnModel();
159
160
            // 1. Initialize columns hydrators
161 9
            if ($this->getHydrationOptions()->isFormattersEnabled()) {
162 9
                $formatters = $cm->getUniqueFormatters();
163 9
                if ($formatters->count() > 0) {
164 2
                    $this->hydration_formatters = $formatters;
165 2
                }
166 9
            }
167
168
            // 2. Initialize hydrated columns
169
170 9
            if ($this->getHydrationOptions()->isColumnExclusionEnabled()) {
171 9
                $columns = $cm->getColumns();
172
173
                // Performance:
174
                // Only if column model definition differs from originating
175
                // source row definition.
176 9
                $hydrated_columns = array_keys((array) $columns);
177 9
                $row_columns = array_keys((array) $row);
178 9
                if ($hydrated_columns != $row_columns) {
179 5
                    $this->hydrated_columns = new ArrayObject($hydrated_columns);
180 5
                }
181 9
            }
182
183
            // 3. Initialize row renderers
184 9
            if ($this->getHydrationOptions()->isRenderersEnabled()) {
185 9
                $this->hydration_renderers = $cm->getRowRenderers();
186 9
            }
187 9
        }
188 27
        $this->hydrate_options_initialized = true;
189 27
    }
190
191
    /**
192
     * Return the current row as an array|ArrayObject.
193
     * If setLimitColumns() have been set, will only return
194
     * the limited columns.
195
     *
196
     * @throws Exception\UnknownColumnException
197
     *
198
     * @return array|ArrayObject|null
199
     */
200 27
    public function current()
201
    {
202 27
        $row = $this->zfResultSet->current();
203 27
        if ($row === null) {
204
            return;
205
        }
206
207 27
        if (!$this->hydrate_options_initialized) {
208 27
            $this->initColumnModelHydration($row);
0 ignored issues
show
Bug introduced by
It seems like $row defined by $this->zfResultSet->current() on line 202 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...
209 27
        }
210
211
        // 1. Row renderers
212 27
        foreach ($this->hydration_renderers as $renderer) {
213 5
            $renderer->apply($row);
214 26
        }
215
216
        // 2. Formatters
217 26
        foreach ($this->hydration_formatters as $formatters) {
218 2
            foreach ($formatters['columns'] as $column) {
219 2
                $row[$column] = $formatters['formatter']->format($row[$column], $row);
220 2
            }
221 26
        }
222
223
        // 3. Process column hydration
224 26
        if ($this->hydrated_columns !== null) {
225 4
            $d = new ArrayObject();
226 4
            foreach ($this->hydrated_columns as $column) {
227 4
                $d->offsetSet($column, isset($row[$column]) ? $row[$column] : null);
228 4
            }
229 4
            $row->exchangeArray($d);
230 4
        }
231
232 26
        if ($this->returnType === self::TYPE_ARRAY) {
233
            return (array) $row;
234
        }
235
236 26
        return $row;
237
    }
238
239
    /**
240
     * Cast result set to array of arrays.
241
     *
242
     * @return array
243
     *
244
     * @throws Exception\RuntimeException if any row is not castable to an array
245
     */
246 26
    public function toArray()
247
    {
248 26
        $return = [];
249 26
        foreach ($this as $row) {
250 24
            if (is_array($row)) {
251
                $return[] = $row;
252 24
            } elseif (method_exists($row, 'toArray')) {
253
                $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...
254 24
            } elseif (method_exists($row, 'getArrayCopy')) {
255 24
                $return[] = $row->getArrayCopy();
256 24
            } else {
257
                throw new Exception\RuntimeException(
258
                    __METHOD__ . ': Rows as part of this DataSource, with type ' . gettype($row) . ' cannot be cast to an array'
259
                );
260
            }
261 25
        }
262
263 25
        return $return;
264
    }
265
266
    /**
267
     * Iterator: is pointer valid?
268
     *
269
     * @return bool
270
     */
271 26
    public function valid()
272
    {
273 26
        $valid = $this->zfResultSet->valid();
274 26
        if (!$valid) {
275 25
            $this->hydrate_options_initialized = false;
276 25
        }
277
278 26
        return $valid;
279
    }
280
281
    /**
282
     * Iterator: rewind.
283
     */
284 26
    public function rewind()
285
    {
286 26
        $this->hydrate_options_initialized = false;
287 26
        $this->zfResultSet->rewind();
288 26
    }
289
}
290