Response   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 128
c 4
b 1
f 0
dl 0
loc 327
rs 9.0399
wmc 42

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getArgument() 0 9 1
C _receive() 0 72 17
C __construct() 0 61 14
A __debugInfo() 0 4 1
A getType() 0 3 1
A setType() 0 16 6
A getUnrecognizedWords() 0 3 1
A getProperty() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Response often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Response, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * ~~summary~~
5
 *
6
 * ~~description~~
7
 *
8
 * PHP version 5
9
 *
10
 * @category  Net
11
 * @package   PEAR2_Net_RouterOS
12
 * @author    Vasil Rangelov <[email protected]>
13
 * @copyright 2011 Vasil Rangelov
14
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
15
 * @version   GIT: $Id$
16
 * @link      http://pear2.php.net/PEAR2_Net_RouterOS
17
 */
18
/**
19
 * The namespace declaration.
20
 */
21
namespace PEAR2\Net\RouterOS;
22
23
/**
24
 * Refers to transmitter direction constants.
25
 */
26
use PEAR2\Net\Transmitter as T;
27
28
/**
29
 * Locks are released upon any exception from anywhere.
30
 */
31
use Exception as E;
32
33
/**
34
 * Represents a RouterOS response.
35
 *
36
 * @category Net
37
 * @package  PEAR2_Net_RouterOS
38
 * @author   Vasil Rangelov <[email protected]>
39
 * @license  http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
40
 * @link     http://pear2.php.net/PEAR2_Net_RouterOS
41
 */
42
class Response extends Message
43
{
44
45
    /**
46
     * The last response for a request.
47
     */
48
    const TYPE_FINAL = '!done';
49
50
    /**
51
     * A response with data.
52
     */
53
    const TYPE_DATA = '!re';
54
55
    /**
56
     * A response with empty data (from RouterOS 7.18).
57
     */
58
    const TYPE_EMPTY = '!empty';
59
60
    /**
61
     * A response signifying error.
62
     */
63
    const TYPE_ERROR = '!trap';
64
65
    /**
66
     * A response signifying a fatal error, due to which the connection would be
67
     * terminated.
68
     */
69
    const TYPE_FATAL = '!fatal';
70
71
    /**
72
     * An array of unrecognized words in network order.
73
     *
74
     * @var (string|resource)[]
75
     */
76
    protected $unrecognizedWords = array();
77
78
    /**
79
     * The response type.
80
     *
81
     * @var string
82
     */
83
    private $_type;
84
85
    /**
86
     * Extracts a new response from a communicator.
87
     *
88
     * @param Communicator  $com       The communicator from which to extract
89
     *     the new response.
90
     * @param int|null      $streamOn  Threshold after which to stream
91
     *     a word. NULL to disable streaming altogether.
92
     * @param int           $sTimeout  If a response is not immediately
93
     *     available, wait this many seconds. If NULL, wait indefinitely.
94
     * @param int|null      $usTimeout Microseconds to add to the waiting time.
95
     * @param Registry|null $reg       An optional registry to sync the
96
     *     response with.
97
     *
98
     * @see getType()
99
     * @see getArgument()
100
     */
101
    public function __construct(
102
        Communicator $com,
103
        $streamOn = null,
104
        $sTimeout = 0,
105
        $usTimeout = null,
106
        Registry $reg = null
107
    ) {
108
        if (null === $reg) {
109
            if ($com->getTransmitter()->isPersistent()) {
110
                $old = $com->getTransmitter()
111
                    ->lock(T\Stream::DIRECTION_RECEIVE);
112
                try {
113
                    $this->_receive($com, $streamOn, $sTimeout, $usTimeout);
114
                } catch (E $e) {
115
                    $com->getTransmitter()->lock($old, true);
116
                    throw $e;
117
                }
118
                $com->getTransmitter()->lock($old, true);
119
            } else {
120
                $this->_receive($com, $streamOn, $sTimeout, $usTimeout);
121
            }
122
        } else {
123
            while (null === ($response = $reg->getNextResponse())) {
124
                $newResponse = new self($com, 0, $sTimeout, $usTimeout);
125
                $tagInfo = $reg::parseTag($newResponse->getTag());
126
                $newResponse->setTag($tagInfo[1]);
127
                if (!$reg->add($newResponse, $tagInfo[0])) {
128
                    $response = $newResponse;
129
                    break;
130
                }
131
            }
132
133
            $this->_type = $response->_type;
134
            $this->setTag($response->getTag());
135
            $this->attributes = $response->attributes;
136
            $this->unrecognizedWords = $response->unrecognizedWords;
137
            if (null === $streamOn) {
138
                foreach ($response->attributes as $name => $value) {
139
                    $this->setAttribute(
140
                        $name,
141
                        stream_get_contents($value)
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $stream of stream_get_contents() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
                        stream_get_contents(/** @scrutinizer ignore-type */ $value)
Loading history...
142
                    );
143
                }
144
                foreach ($response->unrecognizedWords as $i => $value) {
145
                    $this->unrecognizedWords[$i] = stream_get_contents($value);
146
                }
147
            } elseif (0 !== $streamOn) {
148
                foreach ($response->attributes as $name => $value) {
149
                    $valueLength = $com::seekableStreamLength($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $stream of PEAR2\Net\RouterOS\Commu...:seekableStreamLength() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
                    $valueLength = $com::seekableStreamLength(/** @scrutinizer ignore-type */ $value);
Loading history...
150
                    if ((strlen($name) + 2 + $valueLength) < $streamOn) {
151
                        $this->setAttribute(
152
                            $name,
153
                            stream_get_contents($value)
154
                        );
155
                    }
156
                }
157
                foreach ($response->unrecognizedWords as $i => $value) {
158
                    $valueLength = $com::seekableStreamLength($value);
159
                    if ($valueLength < $streamOn) {
160
                        $this->unrecognizedWords[$i] = stream_get_contents(
161
                            $value
162
                        );
163
                    }
164
                }
165
            }
166
        }
167
    }
168
169
    /**
170
     * Extracts a new response from a communicator.
171
     *
172
     * This is the function that performs the actual receiving, while the
173
     * constructor is also involved in locks and registry sync.
174
     *
175
     * @param Communicator $com       The communicator from which to extract
176
     *     the new response.
177
     * @param int|null     $streamOn  Threshold after which to stream
178
     *     a word. NULL to disable streaming altogether.
179
     * @param int          $sTimeout  If a response is not immediately
180
     *     available, wait this many seconds. If NULL, wait indefinitely.
181
     *     Note that if an empty sentence is received, the timeout will be
182
     *     reset for another sentence receiving.
183
     * @param int|null     $usTimeout Microseconds to add to the waiting time.
184
     *
185
     * @return void
186
     */
187
    private function _receive(
188
        Communicator $com,
189
        $streamOn = null,
190
        $sTimeout = 0,
191
        $usTimeout = null
192
    ) {
193
        do {
194
            if (!$com->getTransmitter()->isDataAwaiting(
195
                $sTimeout,
196
                $usTimeout
197
            )
198
            ) {
199
                throw new SocketException(
200
                    'No data within the time limit',
201
                    SocketException::CODE_NO_DATA
202
                );
203
            }
204
            $type = $com->getNextWord();
205
        } while ('' === $type);
206
        $this->setType($type);
207
        if (null === $streamOn) {
208
            for ($word = $com->getNextWord(); '' !== $word; $word = $com->
209
                getNextWord()) {
210
                if (preg_match('/^=([^=]+)=(.*)$/sS', $word, $matches)) {
211
                    $this->setAttribute($matches[1], $matches[2]);
212
                } elseif (preg_match('/^\.tag=(.*)$/sS', $word, $matches)) {
213
                    $this->setTag($matches[1]);
214
                } else {
215
                    $this->unrecognizedWords[] = $word;
216
                }
217
            }
218
        } else {
219
            while ($com->getNextWordLength() !== 0) {
220
                if ($com->getNextWordLength() < $streamOn) {
221
                    $word = $com->getNextWord();
222
                    if (preg_match('/^=([^=]+)=(.*)$/sS', $word, $matches)) {
223
                        $this->setAttribute($matches[1], $matches[2]);
224
                    } elseif (preg_match('/^\.tag=(.*)$/sS', $word, $matches)) {
225
                        $this->setTag($matches[1]);
226
                    } else {
227
                        $this->unrecognizedWords[] = $word;
228
                    }
229
                } else {
230
                    $word = $com->getNextWordAsStream();
231
                    $ind = fread($word, 1);
232
                    if ('=' === $ind || '.' === $ind) {
233
                        $prefix = stream_get_line($word, 0, '=');
234
                    }
235
                    if ('=' === $ind) {
236
                        $value = fopen('php://temp', 'r+b');
237
                        $bytesCopied = ftell($word);
238
                        while (!feof($word)) {
239
                            $bytesCopied += stream_copy_to_stream(
240
                                $word,
241
                                $value,
242
                                0xFFFFF,
243
                                $bytesCopied
244
                            );
245
                        }
246
                        rewind($value);
247
                        $this->setAttribute($prefix, $value);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $prefix does not seem to be defined for all execution paths leading up to this point.
Loading history...
248
                        continue;
249
                    }
250
                    if ('.' === $ind && 'tag' === $prefix) {
251
                        $this->setTag(stream_get_contents($word, -1, -1));
252
                        continue;
253
                    }
254
                    rewind($word);
255
                    $this->unrecognizedWords[] = $word;
256
                }
257
            }
258
            $com->getNextWord();
259
        }
260
    }
261
262
    /**
263
     * Sets the response type.
264
     *
265
     * Sets the response type. Valid values are the TYPE_* constants.
266
     *
267
     * @param string $type The new response type.
268
     *
269
     * @return $this The response object.
270
     *
271
     * @see getType()
272
     */
273
    protected function setType($type)
274
    {
275
        switch ($type) {
276
        case self::TYPE_FINAL:
277
        case self::TYPE_DATA:
278
        case self::TYPE_EMPTY:
279
        case self::TYPE_ERROR:
280
        case self::TYPE_FATAL:
281
            $this->_type = $type;
282
            return $this;
283
        default:
284
            throw new UnexpectedValueException(
285
                'Unrecognized response type.',
286
                UnexpectedValueException::CODE_RESPONSE_TYPE_UNKNOWN,
287
                null,
288
                $type
289
            );
290
        }
291
    }
292
293
    /**
294
     * Gets the response type.
295
     *
296
     * @return string The response type.
297
     *
298
     * @see setType()
299
     */
300
    public function getType()
301
    {
302
        return $this->_type;
303
    }
304
305
    /**
306
     * Gets the value of an argument.
307
     *
308
     * @param string $name The name of the argument.
309
     *
310
     * @return string|resource|null The value of the specified argument.
311
     *     Returns NULL if such an argument is not set.
312
     *
313
     * @deprecated         1.0.0b5 Use {@link static::getProperty()} instead.
314
     *     This method will be removed upon final release, and is currently
315
     *     left standing merely because it can't be easily search&replaced in
316
     *     existing code, due to the fact the name "getArgument()" is shared
317
     *     with {@link Request::getArgument()}, which is still valid.
318
     * @codeCoverageIgnore
319
     */
320
    public function getArgument($name)
321
    {
322
        trigger_error(
323
            'Response::getArgument() is deprecated in favor of ' .
324
            'Response::getProperty() (but note that Request::getArgument() ' .
325
            'is still valid)',
326
            E_USER_DEPRECATED
327
        );
328
        return $this->getAttribute($name);
329
    }
330
331
    /**
332
     * Gets the value of a property.
333
     *
334
     * @param string $name The name of the property.
335
     *
336
     * @return string|resource|null The value of the specified property.
337
     *     Returns NULL if such a property is not set.
338
     */
339
    public function getProperty($name)
340
    {
341
        return $this->getAttribute($name);
342
    }
343
344
    /**
345
     * Gets a list of unrecognized words.
346
     *
347
     * @return string[] The list of unrecognized words.
348
     */
349
    public function getUnrecognizedWords()
350
    {
351
        return $this->unrecognizedWords;
352
    }
353
354
    /**
355
     * Get actionable debug info.
356
     *
357
     * This is a magic method available to PHP 5.6 and above, due to which
358
     * output of var_dump() will be more actionable.
359
     *
360
     * You can still call it in earlier versions to get the object as a
361
     * plain array.
362
     *
363
     * @return array The info, as an associative array.
364
     */
365
    public function __debugInfo()
366
    {
367
        return parent::__debugInfo() + array(
368
            'unrecognized' => $this->unrecognizedWords
369
        );
370
    }
371
}
372