Completed
Branch develop (b42f4c)
by Vasil
13:57
created

Client::__construct()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 44
rs 5.3846
cc 8
eloc 32
nc 18
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Stream as S;
27
28
/**
29
 * Refers to the cryptography constants.
30
 */
31
use PEAR2\Net\Transmitter\NetworkStream as N;
32
33
/**
34
 * Catches arbitrary exceptions at some points.
35
 */
36
use Exception as E;
37
38
/**
39
 * A RouterOS client.
40
 *
41
 * Provides functionality for easily communicating with a RouterOS host.
42
 *
43
 * @category Net
44
 * @package  PEAR2_Net_RouterOS
45
 * @author   Vasil Rangelov <[email protected]>
46
 * @license  http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
47
 * @link     http://pear2.php.net/PEAR2_Net_RouterOS
48
 */
49
class Client
50
{
51
    /**
52
     * Used in {@link static::isRequestActive()} to limit search only to
53
     * requests that have a callback.
54
     */
55
    const FILTER_CALLBACK = 1;
56
    /**
57
     * Used in {@link static::isRequestActive()} to limit search only to
58
     * requests that use the buffer.
59
     */
60
    const FILTER_BUFFER = 2;
61
    /**
62
     * Used in {@link static::isRequestActive()} to indicate no limit in search.
63
     */
64
    const FILTER_ALL = 3;
65
66
    /**
67
     * The communicator for this client.
68
     *
69
     * @var Communicator
70
     */
71
    protected $com;
72
73
    /**
74
     * The number of currently pending requests.
75
     *
76
     * @var int
77
     */
78
    protected $pendingRequestsCount = 0;
79
80
    /**
81
     * An array of responses that have not yet been extracted
82
     * or passed to a callback.
83
     *
84
     * Key is the tag of the request, and the value is an array of
85
     * associated responses.
86
     *
87
     * @var array<string,Response[]>
88
     */
89
    protected $responseBuffer = array();
90
91
    /**
92
     * An array of callbacks to be executed as responses come.
93
     *
94
     * Key is the tag of the request, and the value is the callback for it.
95
     *
96
     * @var array<string,callback>
97
     */
98
    protected $callbacks = array();
99
100
    /**
101
     * A registry for the operations.
102
     *
103
     * Particularly helpful at persistent connections.
104
     *
105
     * @var Registry
106
     */
107
    protected $registry = null;
108
109
    /**
110
     * Stream response words that are above this many bytes.
111
     * NULL to disable streaming completely.
112
     *
113
     * @var int|null
114
     */
115
    private $_streamingResponses = null;
116
117
    /**
118
     * Creates a new instance of a RouterOS API client.
119
     *
120
     * Creates a new instance of a RouterOS API client with the specified
121
     * settings.
122
     *
123
     * @param string        $host     Hostname (IP or domain) of RouterOS.
124
     * @param string        $username The RouterOS username.
125
     * @param string        $password The RouterOS password.
126
     * @param int|null      $port     The port on which the RouterOS host
127
     *     provides the API service. You can also specify NULL, in which case
128
     *     the port will automatically be chosen between 8728 and 8729,
129
     *     depending on the value of $crypto.
130
     * @param bool          $persist  Whether or not the connection should be a
131
     *     persistent one.
132
     * @param double|null   $timeout  The timeout for the connection.
133
     * @param string        $crypto   The encryption for this connection.
134
     *     Must be one of the PEAR2\Net\Transmitter\NetworkStream::CRYPTO_*
135
     *     constants. Off by default. RouterOS currently supports only TLS, but
136
     *     the setting is provided in this fashion for forward compatibility's
137
     *     sake. And for the sake of simplicity, if you specify an encryption,
138
     *     don't specify a context and your default context uses the value
139
     *     "DEFAULT" for ciphers, "ADH" will be automatically added to the list
140
     *     of ciphers.
141
     * @param resource|null $context  A context for the socket.
142
     *
143
     * @see sendSync()
144
     * @see sendAsync()
145
     */
146
    public function __construct(
147
        $host,
148
        $username,
149
        $password = '',
150
        $port = 8728,
151
        $persist = false,
152
        $timeout = null,
153
        $crypto = N::CRYPTO_OFF,
154
        $context = null
155
    ) {
156
        $this->com = new Communicator(
157
            $host,
158
            $port,
159
            $persist,
160
            $timeout,
161
            $username . '/' . $password,
162
            $crypto,
163
            $context
164
        );
165
        $timeout = null == $timeout
166
            ? ini_get('default_socket_timeout')
167
            : (int) $timeout;
168
        //Login the user if necessary
169
        if ((!$persist
170
            || !($old = $this->com->getTransmitter()->lock(S::DIRECTION_ALL)))
171
            && $this->com->getTransmitter()->isFresh()
172
        ) {
173
            if (!static::login($this->com, $username, $password, $timeout)) {
174
                $this->com->close();
175
                throw new DataFlowException(
176
                    'Invalid username or password supplied.',
177
                    DataFlowException::CODE_INVALID_CREDENTIALS
178
                );
179
            }
180
        }
181
182
        if (isset($old)) {
183
            $this->com->getTransmitter()->lock($old, true);
184
        }
185
186
        if ($persist) {
187
            $this->registry = new Registry("{$host}:{$port}/{$username}");
188
        }
189
    }
190
191
    /**
192
     * A shorthand gateway.
193
     *
194
     * This is a magic PHP method that allows you to call the object as a
195
     * function. Depending on the argument given, one of the other functions in
196
     * the class is invoked and its returned value is returned by this function.
197
     *
198
     * @param mixed $arg Value can be either a {@link Request} to send, which
199
     *     would be sent asynchronously if it has a tag, and synchronously if
200
     *     not, a number to loop with or NULL to complete all pending requests.
201
     *     Any other value is converted to string and treated as the tag of a
202
     *     request to complete.
203
     *
204
     * @return mixed Whatever the long form function would have returned.
205
     */
206
    public function __invoke($arg = null)
207
    {
208
        if (is_int($arg) || is_double($arg)) {
209
            return $this->loop($arg);
210
        } elseif ($arg instanceof Request) {
211
            return '' == $arg->getTag() ? $this->sendSync($arg)
212
                : $this->sendAsync($arg);
213
        } elseif (null === $arg) {
214
            return $this->completeRequest();
215
        }
216
        return $this->completeRequest((string) $arg);
217
    }
218
219
    /**
220
     * Login to a RouterOS connection.
221
     *
222
     * @param Communicator $com      The communicator to attempt to login to.
223
     * @param string       $username The RouterOS username.
224
     * @param string       $password The RouterOS password.
225
     * @param int|null     $timeout  The time to wait for each response. NULL
226
     *     waits indefinitely.
227
     *
228
     * @return bool TRUE on success, FALSE on failure.
229
     */
230
    public static function login(
231
        Communicator $com,
232
        $username,
233
        $password = '',
234
        $timeout = null
235
    ) {
236
        if (null !== ($remoteCharset = $com->getCharset($com::CHARSET_REMOTE))
237
            && null !== ($localCharset = $com->getCharset($com::CHARSET_LOCAL))
238
        ) {
239
            $password = iconv(
240
                $localCharset,
241
                $remoteCharset . '//IGNORE//TRANSLIT',
242
                $password
243
            );
244
        }
245
        $old = null;
246
        try {
247
            if ($com->getTransmitter()->isPersistent()) {
248
                $old = $com->getTransmitter()->lock(S::DIRECTION_ALL);
249
                $result = self::_login($com, $username, $password, $timeout);
250
                $com->getTransmitter()->lock($old, true);
251
                return $result;
252
            }
253
            return self::_login($com, $username, $password, $timeout);
254
        } catch (E $e) {
255
            if ($com->getTransmitter()->isPersistent() && null !== $old) {
256
                $com->getTransmitter()->lock($old, true);
257
            }
258
            throw ($e instanceof NotSupportedException
259
            || $e instanceof UnexpectedValueException
260
            || !$com->getTransmitter()->isDataAwaiting()) ? new SocketException(
261
                'This is not a compatible RouterOS service',
262
                SocketException::CODE_SERVICE_INCOMPATIBLE,
263
                $e
264
            ) : $e;
265
        }
266
    }
267
268
    /**
269
     * Login to a RouterOS connection.
270
     *
271
     * This is the actual login procedure, applied regardless of persistence and
272
     * charset settings.
273
     *
274
     * @param Communicator $com      The communicator to attempt to login to.
275
     * @param string       $username The RouterOS username.
276
     * @param string       $password The RouterOS password. Potentially parsed
277
     *     already by iconv.
278
     * @param int|null     $timeout  The time to wait for each response. NULL
279
     *     waits indefinitely.
280
     *
281
     * @return bool TRUE on success, FALSE on failure.
282
     */
283
    private static function _login(
284
        Communicator $com,
285
        $username,
286
        $password = '',
287
        $timeout = null
288
    ) {
289
        $request = new Request('/login');
290
        $request->send($com);
291
        $response = new Response($com, null, $timeout);
292
        $request->setArgument('name', $username);
293
        $request->setArgument(
294
            'response',
295
            '00' . md5(
296
                chr(0) . $password
297
                . pack('H*', $response->getProperty('ret'))
298
            )
299
        );
300
        $request->verify($com)->send($com);
301
302
        $response = new Response($com, null, $timeout);
303
        if ($response->getType() === Response::TYPE_FINAL) {
304
            return null === $response->getProperty('ret');
305
        } else {
306
            while ($response->getType() !== Response::TYPE_FINAL
307
                && $response->getType() !== Response::TYPE_FATAL
308
            ) {
309
                $response = new Response($com, null, $timeout);
310
            }
311
            return false;
312
        }
313
    }
314
315
    /**
316
     * Sets the charset(s) for this connection.
317
     *
318
     * Sets the charset(s) for this connection. The specified charset(s) will be
319
     * used for all future requests and responses. When sending,
320
     * {@link Communicator::CHARSET_LOCAL} is converted to
321
     * {@link Communicator::CHARSET_REMOTE}, and when receiving,
322
     * {@link Communicator::CHARSET_REMOTE} is converted to
323
     * {@link Communicator::CHARSET_LOCAL}. Setting NULL to either charset will
324
     * disable charset convertion, and data will be both sent and received "as
325
     * is".
326
     *
327
     * @param mixed $charset     The charset to set. If $charsetType is
328
     *     {@link Communicator::CHARSET_ALL}, you can supply either a string to
329
     *     use for all charsets, or an array with the charset types as keys, and
330
     *     the charsets as values.
331
     * @param int   $charsetType Which charset to set. Valid values are the
332
     *     Communicator::CHARSET_* constants. Any other value is treated as
333
     *     {@link Communicator::CHARSET_ALL}.
334
     *
335
     * @return string|array The old charset. If $charsetType is
336
     *     {@link Communicator::CHARSET_ALL}, the old values will be returned as
337
     *     an array with the types as keys, and charsets as values.
338
     *
339
     * @see Communicator::setDefaultCharset()
340
     */
341
    public function setCharset(
342
        $charset,
343
        $charsetType = Communicator::CHARSET_ALL
344
    ) {
345
        return $this->com->setCharset($charset, $charsetType);
346
    }
347
348
    /**
349
     * Gets the charset(s) for this connection.
350
     *
351
     * @param int $charsetType Which charset to get. Valid values are the
352
     *     Communicator::CHARSET_* constants. Any other value is treated as
353
     *     {@link Communicator::CHARSET_ALL}.
354
     *
355
     * @return string|array The current charset. If $charsetType is
356
     *     {@link Communicator::CHARSET_ALL}, the current values will be
357
     *     returned as an array with the types as keys, and charsets as values.
358
     *
359
     * @see setCharset()
360
     */
361
    public function getCharset($charsetType)
362
    {
363
        return $this->com->getCharset($charsetType);
364
    }
365
366
    /**
367
     * Sends a request and waits for responses.
368
     *
369
     * @param Request       $request  The request to send.
370
     * @param callback|null $callback Optional. A function that is to be
371
     *     executed when new responses for this request are available.
372
     *     The callback takes two parameters. The {@link Response} object as
373
     *     the first, and the {@link Client} object as the second one. If the
374
     *     callback returns TRUE, the request is canceled. Note that the
375
     *     callback may be executed at least two times after that. Once with a
376
     *     {@link Response::TYPE_ERROR} response that notifies about the
377
     *     canceling, plus the {@link Response::TYPE_FINAL} response.
378
     *
379
     * @return $this The client object.
380
     *
381
     * @see completeRequest()
382
     * @see loop()
383
     * @see cancelRequest()
384
     */
385
    public function sendAsync(Request $request, $callback = null)
386
    {
387
        //Error checking
388
        $tag = $request->getTag();
389
        if ('' == $tag) {
390
            throw new DataFlowException(
391
                'Asynchonous commands must have a tag.',
392
                DataFlowException::CODE_TAG_REQUIRED
393
            );
394
        }
395
        if ($this->isRequestActive($tag)) {
396
            throw new DataFlowException(
397
                'There must not be multiple active requests sharing a tag.',
398
                DataFlowException::CODE_TAG_UNIQUE
399
            );
400
        }
401
        if (null !== $callback && !is_callable($callback, true)) {
402
            throw new UnexpectedValueException(
403
                'Invalid callback provided.',
404
                UnexpectedValueException::CODE_CALLBACK_INVALID
405
            );
406
        }
407
408
        $this->send($request);
409
410
        if (null === $callback) {
411
            //Register the request at the buffer
412
            $this->responseBuffer[$tag] = array();
413
        } else {
414
            //Prepare the callback
415
            $this->callbacks[$tag] = $callback;
416
        }
417
        return $this;
418
    }
419
420
    /**
421
     * Checks if a request is active.
422
     *
423
     * Checks if a request is active. A request is considered active if it's a
424
     * pending request and/or has responses that are not yet extracted.
425
     *
426
     * @param string $tag    The tag of the request to look for.
427
     * @param int    $filter One of the FILTER_* constants. Limits the search
428
     *     to the specified places.
429
     *
430
     * @return bool TRUE if the request is active, FALSE otherwise.
431
     *
432
     * @see getPendingRequestsCount()
433
     * @see completeRequest()
434
     */
435
    public function isRequestActive($tag, $filter = self::FILTER_ALL)
436
    {
437
        $result = 0;
438
        if ($filter & self::FILTER_CALLBACK) {
439
            $result |= (int) array_key_exists($tag, $this->callbacks);
440
        }
441
        if ($filter & self::FILTER_BUFFER) {
442
            $result |= (int) array_key_exists($tag, $this->responseBuffer);
443
        }
444
        return 0 !== $result;
445
    }
446
447
    /**
448
     * Sends a request and gets the full response.
449
     *
450
     * @param Request $request The request to send.
451
     *
452
     * @return ResponseCollection The received responses as a collection.
453
     *
454
     * @see sendAsync()
455
     * @see close()
456
     */
457
    public function sendSync(Request $request)
458
    {
459
        $tag = $request->getTag();
460
        if ('' == $tag) {
461
            $this->send($request);
462
        } else {
463
            $this->sendAsync($request);
464
        }
465
        return $this->completeRequest($tag);
466
    }
467
468
    /**
469
     * Completes a specified request.
470
     *
471
     * Starts an event loop for the RouterOS callbacks and finishes when a
472
     * specified request is completed.
473
     *
474
     * @param string|null $tag The tag of the request to complete.
475
     *     Setting NULL completes all requests.
476
     *
477
     * @return ResponseCollection A collection of {@link Response} objects that
478
     *     haven't been passed to a callback function or previously extracted
479
     *     with {@link static::extractNewResponses()}. Returns an empty
480
     *     collection when $tag is set to NULL (responses can still be
481
     *     extracted).
482
     */
483
    public function completeRequest($tag = null)
484
    {
485
        $hasNoTag = '' == $tag;
486
        $result = $hasNoTag ? array()
487
            : $this->extractNewResponses($tag)->toArray();
488
        while ((!$hasNoTag && $this->isRequestActive($tag))
489
        || ($hasNoTag && 0 !== $this->getPendingRequestsCount())
490
        ) {
491
            $newReply = $this->dispatchNextResponse(null);
492
            if ($newReply->getTag() === $tag) {
493
                if ($hasNoTag) {
494
                    $result[] = $newReply;
495
                }
496
                if ($newReply->getType() === Response::TYPE_FINAL) {
497
                    if (!$hasNoTag) {
498
                        $result = array_merge(
499
                            $result,
500
                            $this->isRequestActive($tag)
501
                            ? $this->extractNewResponses($tag)->toArray()
502
                            : array()
503
                        );
504
                    }
505
                    break;
506
                }
507
            }
508
        }
509
        return new ResponseCollection($result);
510
    }
511
512
    /**
513
     * Extracts responses for a request.
514
     *
515
     * Gets all new responses for a request that haven't been passed to a
516
     * callback and clears the buffer from them.
517
     *
518
     * @param string|null $tag The tag of the request to extract
519
     *     new responses for.
520
     *     Specifying NULL with extract new responses for all requests.
521
     *
522
     * @return ResponseCollection A collection of {@link Response} objects for
523
     *     the specified request.
524
     *
525
     * @see loop()
526
     */
527
    public function extractNewResponses($tag = null)
528
    {
529
        if (null === $tag) {
530
            $result = array();
531
            foreach (array_keys($this->responseBuffer) as $tag) {
532
                $result = array_merge(
533
                    $result,
534
                    $this->extractNewResponses($tag)->toArray()
535
                );
536
            }
537
            return new ResponseCollection($result);
538
        } elseif ($this->isRequestActive($tag, self::FILTER_CALLBACK)) {
539
            return new ResponseCollection(array());
540
        } elseif ($this->isRequestActive($tag, self::FILTER_BUFFER)) {
541
            $result = $this->responseBuffer[$tag];
542
            if (!empty($result)) {
543
                if (end($result)->getType() === Response::TYPE_FINAL) {
544
                    unset($this->responseBuffer[$tag]);
545
                } else {
546
                    $this->responseBuffer[$tag] = array();
547
                }
548
            }
549
            return new ResponseCollection($result);
550
        } else {
551
            throw new DataFlowException(
552
                'No such request, or the request has already finished.',
553
                DataFlowException::CODE_UNKNOWN_REQUEST
554
            );
555
        }
556
    }
557
558
    /**
559
     * Starts an event loop for the RouterOS callbacks.
560
     *
561
     * Starts an event loop for the RouterOS callbacks and finishes when there
562
     * are no more pending requests or when a specified timeout has passed
563
     * (whichever comes first).
564
     *
565
     * @param int|null $sTimeout  Timeout for the loop.
566
     *     If NULL, there is no time limit.
567
     * @param int      $usTimeout Microseconds to add to the time limit.
568
     *
569
     * @return bool TRUE when there are any more pending requests, FALSE
570
     *     otherwise.
571
     *
572
     * @see extractNewResponses()
573
     * @see getPendingRequestsCount()
574
     */
575
    public function loop($sTimeout = null, $usTimeout = 0)
576
    {
577
        try {
578
            if (null === $sTimeout) {
579
                while ($this->getPendingRequestsCount() !== 0) {
580
                    $this->dispatchNextResponse(null);
581
                }
582
            } else {
583
                list($usStart, $sStart) = explode(' ', microtime());
584
                while ($this->getPendingRequestsCount() !== 0
585
                    && ($sTimeout >= 0 || $usTimeout >= 0)
586
                ) {
587
                    $this->dispatchNextResponse($sTimeout, $usTimeout);
588
                    list($usEnd, $sEnd) = explode(' ', microtime());
589
590
                    $sTimeout -= $sEnd - $sStart;
591
                    $usTimeout -= $usEnd - $usStart;
592
                    if ($usTimeout <= 0) {
593
                        if ($sTimeout > 0) {
594
                            $usTimeout = 1000000 + $usTimeout;
595
                            $sTimeout--;
596
                        }
597
                    }
598
599
                    $sStart = $sEnd;
600
                    $usStart = $usEnd;
601
                }
602
            }
603
        } catch (SocketException $e) {
604
            if ($e->getCode() !== SocketException::CODE_NO_DATA) {
605
                // @codeCoverageIgnoreStart
606
                // It's impossible to reliably cause any other SocketException.
607
                // This line is only here in case the unthinkable happens:
608
                // The connection terminates just after it was supposedly
609
                // about to send back some data.
610
                throw $e;
611
                // @codeCoverageIgnoreEnd
612
            }
613
        }
614
        return $this->getPendingRequestsCount() !== 0;
615
    }
616
617
    /**
618
     * Gets the number of pending requests.
619
     *
620
     * @return int The number of pending requests.
621
     *
622
     * @see isRequestActive()
623
     */
624
    public function getPendingRequestsCount()
625
    {
626
        return $this->pendingRequestsCount;
627
    }
628
629
    /**
630
     * Cancels a request.
631
     *
632
     * Cancels an active request. Using this function in favor of a plain call
633
     * to the "/cancel" command is highly recommended, as it also updates the
634
     * counter of pending requests properly. Note that canceling a request also
635
     * removes any responses for it that were not previously extracted with
636
     * {@link static::extractNewResponses()}.
637
     *
638
     * @param string|null $tag Tag of the request to cancel.
639
     *     Setting NULL will cancel all requests.
640
     *
641
     * @return $this The client object.
642
     *
643
     * @see sendAsync()
644
     * @see close()
645
     */
646
    public function cancelRequest($tag = null)
647
    {
648
        $cancelRequest = new Request('/cancel');
649
        $hasTag = !('' == $tag);
650
        $hasReg = null !== $this->registry;
651
        if ($hasReg && !$hasTag) {
652
            $tags = array_merge(
653
                array_keys($this->responseBuffer),
654
                array_keys($this->callbacks)
655
            );
656
            $this->registry->setTaglessMode(true);
657
            foreach ($tags as $t) {
658
                $cancelRequest->setArgument(
659
                    'tag',
660
                    $this->registry->getOwnershipTag() . $t
661
                );
662
                $this->sendSync($cancelRequest);
663
            }
664
            $this->registry->setTaglessMode(false);
665
        } else {
666
            if ($hasTag) {
667
                if ($this->isRequestActive($tag)) {
668
                    if ($hasReg) {
669
                        $this->registry->setTaglessMode(true);
670
                        $cancelRequest->setArgument(
671
                            'tag',
672
                            $this->registry->getOwnershipTag() . $tag
673
                        );
674
                    } else {
675
                        $cancelRequest->setArgument('tag', $tag);
676
                    }
677
                } else {
678
                    throw new DataFlowException(
679
                        'No such request. Canceling aborted.',
680
                        DataFlowException::CODE_CANCEL_FAIL
681
                    );
682
                }
683
            }
684
            $this->sendSync($cancelRequest);
685
            if ($hasReg) {
686
                $this->registry->setTaglessMode(false);
687
            }
688
        }
689
690
        if ($hasTag) {
691
            if ($this->isRequestActive($tag, self::FILTER_BUFFER)) {
692
                $this->responseBuffer[$tag] = $this->completeRequest($tag);
693
            } else {
694
                $this->completeRequest($tag);
695
            }
696
        } else {
697
            $this->loop();
698
        }
699
        return $this;
700
    }
701
702
    /**
703
     * Sets response streaming setting.
704
     *
705
     * Sets when future response words are streamed. If a word is streamed,
706
     * the property value is returned a stream instead of a string, and
707
     * unrecognized words are returned entirely as streams instead of strings.
708
     * This is particularly useful if you expect a response that may contain
709
     * one or more very large words.
710
     *
711
     * @param int|null $threshold Threshold after which to stream
712
     *     a word. That is, a word less than this length will not be streamed.
713
     *     If set to 0, effectively all words are streamed.
714
     *     NULL to disable streaming altogether.
715
     *
716
     * @return $this The client object.
717
     *
718
     * @see getStreamingResponses()
719
     */
720
    public function setStreamingResponses($threshold)
721
    {
722
        $this->_streamingResponses = $threshold === null
723
            ? null
724
            : (int) $threshold;
725
        return $this;
726
    }
727
728
    /**
729
     * Gets response streaming setting.
730
     *
731
     * Gets when future response words are streamed.
732
     *
733
     * @return int|null The value of the setting.
734
     *
735
     * @see setStreamingResponses()
736
     */
737
    public function getStreamingResponses()
738
    {
739
        return $this->_streamingResponses;
740
    }
741
742
    /**
743
     * Closes the opened connection, even if it is a persistent one.
744
     *
745
     * Closes the opened connection, even if it is a persistent one. Note that
746
     * {@link static::extractNewResponses()} can still be used to extract
747
     * responses collected prior to the closing.
748
     *
749
     * @return bool TRUE on success, FALSE on failure.
750
     */
751
    public function close()
752
    {
753
        $result = true;
754
        /*
755
         * The check below is done because for some unknown reason
756
         * (either a PHP or a RouterOS bug) calling "/quit" on an encrypted
757
         * connection makes one end hang.
758
         *
759
         * Since encrypted connections only appeared in RouterOS 6.1, and
760
         * the "/quit" call is needed for all <6.0 versions, problems due
761
         * to its absence should be limited to some earlier 6.* versions
762
         * on some RouterBOARD devices.
763
         */
764
        if ($this->com->getTransmitter()->getCrypto() === N::CRYPTO_OFF) {
765
            if (null !== $this->registry) {
766
                $this->registry->setTaglessMode(true);
767
            }
768
            try {
769
                $response = $this->sendSync(new Request('/quit'));
770
                $result = $response[0]->getType() === Response::TYPE_FATAL;
771
            } catch (SocketException $e) {
772
                $result
773
                    = $e->getCode() === SocketException::CODE_REQUEST_SEND_FAIL;
774
            } catch (E $e) {
775
                //Ignore unknown errors.
776
            }
777
            if (null !== $this->registry) {
778
                $this->registry->setTaglessMode(false);
779
            }
780
        }
781
        $result = $result && $this->com->close();
782
        $this->callbacks = array();
783
        $this->pendingRequestsCount = 0;
784
        return $result;
785
    }
786
787
    /**
788
     * Closes the connection, unless it's a persistent one.
789
     */
790
    public function __destruct()
791
    {
792
        if ($this->com->getTransmitter()->isPersistent()) {
793
            if (0 !== $this->pendingRequestsCount) {
794
                $this->cancelRequest();
795
            }
796
        } else {
797
            $this->close();
798
        }
799
    }
800
801
    /**
802
     * Sends a request to RouterOS.
803
     *
804
     * @param Request $request The request to send.
805
     *
806
     * @return $this The client object.
807
     *
808
     * @see sendSync()
809
     * @see sendAsync()
810
     */
811
    protected function send(Request $request)
812
    {
813
        $request->verify($this->com)->send($this->com, $this->registry);
814
        $this->pendingRequestsCount++;
815
        return $this;
816
    }
817
818
    /**
819
     * Dispatches the next response in queue.
820
     *
821
     * Dispatches the next response in queue, i.e. it executes the associated
822
     * callback if there is one, or places the response in the response buffer.
823
     *
824
     * @param int|null $sTimeout  If a response is not immediately available,
825
     *     wait this many seconds.
826
     *     If NULL, wait indefinitely.
827
     * @param int      $usTimeout Microseconds to add to the waiting time.
828
     *
829
     * @throws SocketException When there's no response within the time limit.
830
     * @return Response The dispatched response.
831
     */
832
    protected function dispatchNextResponse($sTimeout = 0, $usTimeout = 0)
833
    {
834
        $response = new Response(
835
            $this->com,
836
            $this->_streamingResponses,
837
            $sTimeout,
838
            $usTimeout,
839
            $this->registry
840
        );
841
        if ($response->getType() === Response::TYPE_FATAL) {
842
            $this->pendingRequestsCount = 0;
843
            $this->com->close();
844
            return $response;
845
        }
846
847
        $tag = $response->getTag();
848
        $isLastForRequest = $response->getType() === Response::TYPE_FINAL;
849
        if ($isLastForRequest) {
850
            $this->pendingRequestsCount--;
851
        }
852
853
        if ('' != $tag) {
854
            if ($this->isRequestActive($tag, self::FILTER_CALLBACK)) {
855
                if ($this->callbacks[$tag]($response, $this)) {
856
                    try {
857
                        $this->cancelRequest($tag);
858
                    } catch (DataFlowException $e) {
859
                        if ($e->getCode() !== DataFlowException::CODE_UNKNOWN_REQUEST
860
                        ) {
861
                            throw $e;
862
                        }
863
                    }
864
                } elseif ($isLastForRequest) {
865
                    unset($this->callbacks[$tag]);
866
                }
867
            } else {
868
                $this->responseBuffer[$tag][] = $response;
869
            }
870
        }
871
        return $response;
872
    }
873
}
874