Completed
Push — develop ( 9cdb61...7482a6 )
by Vasil
03:28
created

Client::__construct()   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

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