Completed
Pull Request — master (#243)
by Дмитрий
05:05
created

Connection   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 700
Duplicated Lines 5.86 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 41
loc 700
rs 3.862
wmc 64
lcom 1
cbo 6

24 Methods

Rating   Name   Duplication   Size   Complexity  
A onConnected() 12 14 4
A onReady() 0 15 4
A gracefulShutdown() 0 12 2
A onFinish() 0 8 1
D onRead() 28 114 23
A auth() 0 21 3
A login() 0 11 1
A challenge() 0 10 1
A getSipPeers() 0 4 1
A getIaxPeers() 0 4 1
A getConfig() 0 4 1
A getConfigJSON() 0 4 1
A setVar() 0 15 3
A coreShowChannels() 0 8 1
A status() 0 10 2
A redirect() 0 4 1
A originate() 0 6 1
A extensionState() 0 4 1
A ping() 0 4 1
A action() 0 6 2
A logoff() 0 4 1
A onEvent() 0 4 1
B command() 0 25 5
A implodeParams() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Connection 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Connection, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace PHPDaemon\Clients\Asterisk;
3
4
use PHPDaemon\Clients\Asterisk\ConnectionFinished;
5
use PHPDaemon\Clients\Asterisk\Pool;
6
use PHPDaemon\Core\CallbackWrapper;
7
use PHPDaemon\Core\Daemon;
8
use PHPDaemon\Network\ClientConnection;
9
use PHPDaemon\Structures\StackCallbacks;
10
use PHPDaemon\Traits\EventHandlers;
11
12
/**
13
 * Asterisk Call Manager Connection
14
 */
15
class Connection extends ClientConnection
16
{
17
    /**
18
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
19
     */
20
    const CONN_STATE_START = 0;
21
22
    /**
23
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
24
     */
25
    const CONN_STATE_GOT_INITIAL_PACKET = 0.1;
26
27
    /**
28
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
29
     */
30
    const CONN_STATE_AUTH = 1;
31
32
    /**
33
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
34
     */
35
    const CONN_STATE_LOGIN_PACKET_SENT = 1.1;
36
37
    /**
38
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
39
     */
40
    const CONN_STATE_CHALLENGE_PACKET_SENT = 1.2;
41
42
    /**
43
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
44
     */
45
    const CONN_STATE_LOGIN_PACKET_SENT_AFTER_CHALLENGE = 1.3;
46
47
    /**
48
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
49
     */
50
    const CONN_STATE_HANDSHAKED_OK = 2.1;
51
52
    /**
53
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
54
     */
55
    const CONN_STATE_HANDSHAKED_ERROR = 2.2;
56
57
    /**
58
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
59
     */
60
    const INPUT_STATE_START = 0;
61
62
    /**
63
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
64
     */
65
    const INPUT_STATE_END_OF_PACKET = 1;
66
67
    /**
68
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
69
     */
70
    const INPUT_STATE_PROCESSING = 2;
71
72
    /**
73
     * @var string EOL
74
     */
75
    public $EOL = "\r\n";
76
77
    /**
78
     * @var string The username to access the interface
79
     */
80
    public $username;
81
82
    /**
83
     * @var string The password defined in manager interface of server
84
     */
85
    public $secret;
86
87
    /**
88
     * @var float Connection's state
89
     */
90
    public $state = self::CONN_STATE_START;
91
92
    /**
93
     * @var integer Input state
94
     */
95
    public $instate = self::INPUT_STATE_START;
96
97
    /**
98
     * @var array Received packets
99
     */
100
    public $packets = [];
101
102
    /**
103
     * @var integer For composite response on action
104
     */
105
    public $cnt = 0;
106
107
    /**
108
     * @var array Stack of callbacks called when response received
109
     */
110
    public $callbacks = [];
111
112
    /**
113
     * Assertions for callbacks.
114
     * Assertion: if more events may follow as response this is a main part or full
115
     * an action complete event indicating that all data has been sent
116
     * @var array
117
     */
118
    public $assertions = [];
119
120
    /**
121
     * @var callable Callback. Called when received response on challenge action
122
     */
123
    public $onChallenge;
124
125
    /**
126
     * Execute the given callback when/if the connection is handshaked
127
     * @param  callable Callback
128
     * @return void
129
     */
130
    public function onConnected($cb)
131
    {
132 View Code Duplication
        if ($this->state === self::CONN_STATE_HANDSHAKED_ERROR) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
133
            $cb($this);
134
        } elseif ($this->state === self::CONN_STATE_HANDSHAKED_OK) {
135
            $cb($this);
136
        } else {
137
            if (!$this->onConnected) {
138
                $this->onConnected = new StackCallbacks();
139
            }
140
141
            $this->onConnected->push($cb);
142
        }
143
    }
144
145
    /**
146
     * Called when the connection is handshaked (at low-level), and peer is ready to recv. data
147
     * @return void
148
     */
149
    public function onReady()
150
    {
151
        if ($this->url === null) {
152
            return;
153
        }
154
155
        if ($this->connected && !$this->busy) {
156
            $this->pool->servConnFree[$this->url]->attach($this);
157
        }
158
159
        $url = parse_url($this->url);
160
161
        $this->username = $url['user'];
162
        $this->secret = $url['pass'];
163
    }
164
165
    /**
166
     * Called when the worker is going to shutdown
167
     * @return boolean Ready to shutdown?
168
     */
169
    public function gracefulShutdown()
170
    {
171
        if ($this->finished) {
172
            return !$this->writing;
173
        }
174
175
        $this->logoff();
176
177
        $this->finish();
178
179
        return false;
180
    }
181
182
    /**
183
     * Called when session finishes
184
     * @return void
185
     */
186
    public function onFinish()
187
    {
188
        $this->state = self::CONN_STATE_START;
0 ignored issues
show
Documentation Bug introduced by
The property $state was declared of type double, but self::CONN_STATE_START is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
189
190
        parent::onFinish();
191
192
        $this->event('disconnect');
193
    }
194
195
    /**
196
     * Called when new data received
197
     * @return void
198
     */
199
    public function onRead()
200
    {
201
        if ($this->state === self::CONN_STATE_START) {
202
            if (($ver = $this->readline()) === null) {
203
                return;
204
            }
205
            $this->pool->setAmiVersion($this->addr, $ver);
206
            $this->state = self::CONN_STATE_GOT_INITIAL_PACKET;
207
            $this->auth();
208
        }
209
210
        while (($line = $this->readline()) !== null) {
211
            //Daemon::log('>>> '.$line);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
212
            if ($line === '') {
213
                $this->instate = self::INPUT_STATE_END_OF_PACKET;
214
                $packet =& $this->packets[$this->cnt];
215
                ++$this->cnt;
216
            } else {
217
                $this->instate = self::INPUT_STATE_PROCESSING;
218
                list($header, $value) = Pool::extract($line);
219
                $this->packets[$this->cnt][$header] = $value;
220
            }
221
222
            if ((int)$this->state === self::CONN_STATE_AUTH) {
223
                if ($this->instate === self::INPUT_STATE_END_OF_PACKET) {
224
                    if ($packet['response'] === 'success') {
225
                        if ($this->state === self::CONN_STATE_CHALLENGE_PACKET_SENT) {
226
                            if (is_callable($this->onChallenge)) {
227
                                $func = $this->onChallenge;
228
                                $func($this, $packet['challenge']);
0 ignored issues
show
Bug introduced by
The variable $packet does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
229
                            }
230 View Code Duplication
                        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
                            if ($packet['message'] === 'authentication accepted') {
232
                                $this->state = self::CONN_STATE_HANDSHAKED_OK;
233
234
                                Daemon::$process->log(
235
                                    __METHOD__ . ': Authentication ok. Connected to ' .
236
                                    parse_url($this->addr, PHP_URL_HOST)
237
                                );
238
239
                                if ($this->onConnected) {
240
                                    $this->connected = true;
241
                                    $this->onConnected->executeAll($this);
242
                                    $this->onConnected = null;
243
                                }
244
245
                                $this->event('connected');
246
                            }
247
                        }
248 View Code Duplication
                    } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
249
                        $this->state = self::CONN_STATE_HANDSHAKED_ERROR;
250
251
                        Daemon::$process->log(
252
                            __METHOD__ . ': Authentication failed. Connection to ' .
253
                            parse_url($this->addr, PHP_URL_HOST) . ' failed.'
254
                        );
255
256
                        if ($this->onConnected) {
257
                            $this->connected = false;
258
                            $this->onConnected->executeAll($this);
259
                            $this->onConnected = null;
260
                        }
261
262
                        $this->finish();
263
                    }
264
265
                    $this->packets = [];
266
                }
267
            } elseif ($this->state === self::CONN_STATE_HANDSHAKED_OK) {
268
                if ($this->instate === self::INPUT_STATE_END_OF_PACKET) {
269
                    // Event
270
                    if (isset($packet['event']) && !isset($packet['actionid'])) {
271
                        $this->event('event_' . $packet['event'], $packet);
272
                        $this->event('event', $packet);
273
                    } // Response
274
                    elseif (isset($packet['actionid'])) {
275
                        $action_id =& $packet['actionid'];
276
277
                        if (isset($this->callbacks[$action_id])) {
278
                            if (isset($this->assertions[$action_id])) {
279
                                $this->packets[$action_id][] = $packet;
280
281
                                $assertations = count(
282
                                    array_uintersect_uassoc(
283
                                        $this->assertions[$action_id],
284
                                        $packet,
285
                                        'strcasecmp',
286
                                        'strcasecmp'
287
                                    )
288
                                );
289
                                if ($assertations === count($this->assertions[$action_id])) {
290
                                    if (is_callable($this->callbacks[$action_id])) {
291
                                        $this->callbacks[$action_id]($this, $this->packets[$action_id]);
292
                                        unset($this->callbacks[$action_id]);
293
                                    }
294
295
                                    unset($this->assertions[$action_id]);
296
                                    unset($this->packets[$action_id]);
297
                                }
298
                            } else {
299
                                if (is_callable($this->callbacks[$action_id])) {
300
                                    $this->callbacks[$action_id]($this, $packet);
301
                                    unset($this->callbacks[$action_id]);
302
                                }
303
                            }
304
                        }
305
                    }
306
307
                    unset($packet);
308
                    unset($this->packets[$this->cnt - 1]);
309
                }
310
            }
311
        }
312
    }
313
314
    /**
315
     * Send authentication packet
316
     * @return void
317
     */
318
    protected function auth()
319
    {
320
        if ($this->state !== self::CONN_STATE_GOT_INITIAL_PACKET) {
321
            return;
322
        }
323
324
        if ($this->pool->config->authtype->value === 'md5') {
325
            $this->challenge(function ($conn, $challenge) {
326
                $packet = "Action: Login\r\n"
327
                    . "AuthType: MD5\r\n"
328
                    . "Username: " . $this->username . "\r\n"
329
                    . "Key: " . md5($challenge . $this->secret) . "\r\n"
330
                    . "Events: on\r\n"
331
                    . "\r\n";
332
                $this->state = self::CONN_STATE_LOGIN_PACKET_SENT_AFTER_CHALLENGE;
333
                $conn->write($packet);
334
            });
335
        } else {
336
            $this->login();
337
        }
338
    }
339
340
    /**
341
     * Action: Login
342
     * Synopsis: Login Manager
343
     * Privilege: <none>
344
     *
345
     * @return void
346
     */
347
    protected function login()
348
    {
349
        $this->state = self::CONN_STATE_LOGIN_PACKET_SENT;
350
        $this->write(
351
            "Action: login\r\n"
352
            . "Username: " . $this->username . "\r\n"
353
            . "Secret: " . $this->secret . "\r\n"
354
            . "Events: on\r\n"
355
            . "\r\n"
356
        );
357
    }
358
359
    /**
360
     * Action: Challenge
361
     * Synopsis: Generate Challenge for MD5 Auth
362
     * Privilege: <none>
363
     *
364
     * @param  callable $cb
365
     * @return void
366
     */
367
    protected function challenge($cb)
368
    {
369
        $this->onChallenge = $cb;
370
        $this->state = self::CONN_STATE_CHALLENGE_PACKET_SENT;
371
        $this->write(
372
            "Action: Challenge\r\n"
373
            . "AuthType: MD5\r\n"
374
            . "\r\n"
375
        );
376
    }
377
378
    /**
379
     * Action: SIPpeers
380
     * Synopsis: List SIP peers (text format)
381
     * Privilege: system,reporting,all
382
     * Description: Lists SIP peers in text format with details on current status.
383
     * Peerlist will follow as separate events, followed by a final event called
384
     * PeerlistComplete.
385
     * Variables:
386
     * ActionID: <id>    Action ID for this transaction. Will be returned.
387
     *
388
     * @param callable $cb Callback called when response received
389
     * @callback $cb ( Connection $conn, array $packet )
390
     * @return void
391
     */
392
    public function getSipPeers($cb)
393
    {
394
        $this->command("Action: SIPpeers\r\n", $cb, ['event' => 'peerlistcomplete']);
395
    }
396
397
    /**
398
     * Action: IAXpeerlist
399
     * Synopsis: List IAX Peers
400
     * Privilege: system,reporting,all
401
     *
402
     * @param callable $cb Callback called when response received
403
     * @callback $cb ( Connection $conn, array $packet )
404
     * @return void
405
     */
406
    public function getIaxPeers($cb)
407
    {
408
        $this->command("Action: IAXpeerlist\r\n", $cb, ['event' => 'peerlistcomplete']);
409
    }
410
411
    /**
412
     * Action: GetConfig
413
     * Synopsis: Retrieve configuration
414
     * Privilege: system,config,all
415
     * Description: A 'GetConfig' action will dump the contents of a configuration
416
     * file by category and contents or optionally by specified category only.
417
     * Variables: (Names marked with * are required)
418
     *   *Filename: Configuration filename (e.g. foo.conf)
419
     *   Category: Category in configuration file
420
     *
421
     * @param  string $filename Filename
422
     * @param  callable $cb Callback called when response received
423
     * @callback $cb ( Connection $conn, array $packet )
424
     * @return void
425
     */
426
    public function getConfig($filename, $cb)
427
    {
428
        $this->command("Action: GetConfig\r\nFilename: " . trim($filename) . "\r\n", $cb);
429
    }
430
431
    /**
432
     * Action: GetConfigJSON
433
     * Synopsis: Retrieve configuration
434
     * Privilege: system,config,all
435
     * Description: A 'GetConfigJSON' action will dump the contents of a configuration
436
     * file by category and contents in JSON format.  This only makes sense to be used
437
     * using rawman over the HTTP interface.
438
     * Variables:
439
     *    Filename: Configuration filename (e.g. foo.conf)
440
     *
441
     * @param  string $filename Filename
442
     * @param callable $cb Callback called when response received
443
     * @callback $cb ( Connection $conn, array $packet )
444
     * @return void
445
     */
446
    public function getConfigJSON($filename, $cb)
447
    {
448
        $this->command("Action: GetConfigJSON\r\nFilename: " . trim($filename) . "\r\n", $cb);
449
    }
450
451
    /**
452
     * Action: Setvar
453
     * Synopsis: Set Channel Variable
454
     * Privilege: call,all
455
     * Description: Set a global or local channel variable.
456
     * Variables: (Names marked with * are required)
457
     * Channel: Channel to set variable for
458
     *  *Variable: Variable name
459
     *  *Value: Value
460
     *
461
     * @param string $channel
462
     * @param string $variable
463
     * @param string $value
464
     * @param callable $cb
465
     * @callback $cb ( Connection $conn, array $packet )
466
     * @return void
467
     */
468
    public function setVar($channel, $variable, $value, $cb)
469
    {
470
        $cmd = "Action: SetVar\r\n";
471
472
        if ($channel) {
473
            $cmd .= "Channel: " . trim($channel) . "\r\n";
474
        }
475
476
        if (isset($variable, $value)) {
477
            $cmd .= "Variable: " . trim($variable) . "\r\n"
478
                . "Value: " . trim($value) . "\r\n";
479
480
            $this->command($cmd, $cb);
481
        }
482
    }
483
484
    /**
485
     * Action: CoreShowChannels
486
     * Synopsis: List currently active channels
487
     * Privilege: system,reporting,all
488
     * Description: List currently defined channels and some information
489
     *        about them.
490
     * Variables:
491
     *        ActionID: Optional Action id for message matching.
492
     *
493
     * @param callable $cb
494
     * @callback $cb ( Connection $conn, array $packet )
495
     * @return void
496
     */
497
    public function coreShowChannels($cb)
498
    {
499
        $this->command(
500
            "Action: CoreShowChannels\r\n",
501
            $cb,
502
            ['event' => 'coreshowchannelscomplete', 'eventlist' => 'complete']
503
        );
504
    }
505
506
    /**
507
     * Action: Status
508
     * Synopsis: Lists channel status
509
     * Privilege: system,call,reporting,all
510
     * Description: Lists channel status along with requested channel vars.
511
     * Variables: (Names marked with * are required)
512
     *Channel: Name of the channel to query for status
513
     *    Variables: Comma ',' separated list of variables to include
514
     * ActionID: Optional ID for this transaction
515
     * Will return the status information of each channel along with the
516
     * value for the specified channel variables.
517
     *
518
     * @param  callable $cb
519
     * @param  string $channel
0 ignored issues
show
Documentation introduced by
Should the type for parameter $channel not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
520
     * @callback $cb ( Connection $conn, array $packet )
521
     * @return void
522
     */
523
    public function status($cb, $channel = null)
524
    {
525
        $cmd = "Action: Status\r\n";
526
527
        if ($channel !== null) {
528
            $cmd .= 'Channel: ' . trim($channel) . "\r\n";
529
        }
530
531
        $this->command($cmd, $cb, ['event' => 'statuscomplete']);
532
    }
533
534
    /**
535
     * Action: Redirect
536
     * Synopsis: Redirect (transfer) a call
537
     * Privilege: call,all
538
     * Description: Redirect (transfer) a call.
539
     * Variables: (Names marked with * are required)
540
     * *Channel: Channel to redirect
541
     *  ExtraChannel: Second call leg to transfer (optional)
542
     * *Exten: Extension to transfer to
543
     * *Context: Context to transfer to
544
     * *Priority: Priority to transfer to
545
     * ActionID: Optional Action id for message matching.
546
     *
547
     * @param array $params
548
     * @param callable $cb Callback called when response received
549
     * @callback $cb ( Connection $conn, array $packet )
550
     * @return void
551
     */
552
    public function redirect(array $params, $cb)
553
    {
554
        $this->command("Action: Redirect\r\n" . $this->implodeParams($params), $cb);
555
    }
556
557
    /**
558
     * Action: Originate
559
     * Synopsis: Originate a call
560
     * Privilege: call,all
561
     * Description: first the Channel is rung. Then, when that answers, the Extension is dialled within the Context
562
     *  to initiate the other end of the call.
563
     * Variables: (Names marked with * are required)
564
     * *Channel: Channel on which to originate the call (The same as you specify in the Dial application command)
565
     * *Context: Context to use on connect (must use Exten & Priority with it)
566
     * *Exten: Extension to use on connect (must use Context & Priority with it)
567
     * *Priority: Priority to use on connect (must use Context & Exten with it)
568
     * Timeout: Timeout (in milliseconds) for the originating connection to happen(defaults to 30000 milliseconds)
569
     * *CallerID: CallerID to use for the call
570
     * Variable: Channels variables to set (max 32). Variables will be set for both channels (local and connected).
571
     * Account: Account code for the call
572
     * Application: Application to use on connect (use Data for parameters)
573
     * Data : Data if Application parameter is used
574
     * ActionID: Optional Action id for message matching.
575
     *
576
     * @param array $params
577
     * @param callable $cb Callback called when response received
578
     * @callback $cb ( Connection $conn, array $packet )
579
     * @return void
580
     */
581
    public function originate(array $params, $cb)
582
    {
583
        $params['Async'] = 1;
584
585
        $this->command("Action: Originate\r\n" . $this->implodeParams($params), $cb);
586
    }
587
588
    /**
589
     * Action: ExtensionState
590
     * Synopsis: Get an extension's state.
591
     * Description: function can be used to retrieve the state from any    hinted extension.
592
     * Variables: (Names marked with * are required)
593
     * *Exten: Extension to get state
594
     * Context: Context for exten
595
     * ActionID: Optional Action id for message matching.
596
     *
597
     * @param array $params
598
     * @param callable $cb Callback called when response received
599
     * @callback $cb ( Connection $conn, array $packet )
600
     * @return void
601
     */
602
    public function extensionState(array $params, $cb)
603
    {
604
        $this->command("Action: ExtensionState\r\n" . $this->implodeParams($params), $cb);
605
    }
606
607
    /**
608
     * Action: Ping
609
     * Description: A 'Ping' action will ellicit a 'Pong' response.  Used to keep the
610
     *   manager connection open.
611
     * Variables: NONE
612
     *
613
     * @param callable $cb Callback called when response received
614
     * @callback $cb ( Connection $conn, array $packet )
615
     * @return void
616
     */
617
    public function ping($cb)
618
    {
619
        $this->command("Action: Ping\r\n", $cb);
620
    }
621
622
    /**
623
     * For almost any actions in Action: ListCommands
624
     * Privilege: depends on $action
625
     *
626
     * @param string $action Action
627
     * @param callable $cb Callback called when response received
628
     * @param array $params Parameters
0 ignored issues
show
Documentation introduced by
Should the type for parameter $params not be null|array?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
629
     * @param array $assertion If more events may follow as response this is a main part or full an action complete event indicating that all data has been sent
0 ignored issues
show
Documentation introduced by
Should the type for parameter $assertion not be null|array?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
630
     * @callback $cb ( Connection $conn, array $packet )
631
     * @return void
632
     */
633
    public function action($action, $cb, array $params = null, array $assertion = null)
634
    {
635
        $action = trim($action);
636
637
        $this->command("Action: {$action}\r\n" . ($params ? $this->implodeParams($params) : ''), $cb, $assertion);
638
    }
639
640
    /**
641
     * Action: Logoff
642
     * Synopsis: Logoff Manager
643
     * Privilege: <none>
644
     * Description: Logoff this manager session
645
     * Variables: NONE
646
     *
647
     * @param callable $cb Optional callback called when response received
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
648
     * @callback $cb ( Connection $conn, array $packet )
649
     * @return void
650
     */
651
    public function logoff($cb = null)
652
    {
653
        $this->command("Action: Logoff\r\n", $cb);
0 ignored issues
show
Bug introduced by
It seems like $cb defined by parameter $cb on line 651 can also be of type null; however, PHPDaemon\Clients\Asterisk\Connection::command() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
654
    }
655
656
    /**
657
     * Called when event occured
658
     * @deprecated Replaced with ->bind('event', ...)
659
     * @param callable $cb Callback
660
     * @return void
661
     */
662
    public function onEvent($cb)
663
    {
664
        $this->bind('event', $cb);
665
    }
666
667
    /**
668
     * Sends arbitrary command
669
     * @param string $packet A packet for sending by the connected client to Asterisk
670
     * @param callable $cb Callback called when response received
671
     * @param array $assertion If more events may follow as response this is a main part or full an action complete event indicating that all data has been sent
0 ignored issues
show
Documentation introduced by
Should the type for parameter $assertion not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
672
     */
673
    protected function command($packet, $cb, $assertion = null)
674
    {
675
        if ($this->finished) {
676
            throw new ConnectionFinished;
677
        }
678
679
        if ($this->state !== self::CONN_STATE_HANDSHAKED_OK) {
680
            return;
681
        }
682
683
        $actionId = Daemon::uniqid();
684
685
        if (!is_callable($cb, true)) {
686
            $cb = false;
687
        }
688
689
        $this->callbacks[$actionId] = CallbackWrapper::wrap($cb);
0 ignored issues
show
Security Bug introduced by
It seems like $cb defined by false on line 686 can also be of type false; however, PHPDaemon\Core\CallbackWrapper::wrap() does only seem to accept callable, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
690
691
        if ($assertion !== null) {
692
            $this->assertions[$actionId] = $assertion;
693
        }
694
695
        $this->write($packet);
696
        $this->write('ActionID: ' . $actionId . "\r\n\r\n");
697
    }
698
699
    /**
700
     * Generate AMI packet string from associative array provided
701
     * @param  array $params
702
     * @return string
703
     */
704
    protected function implodeParams(array $params)
705
    {
706
        $s = '';
707
708
        foreach ($params as $header => $value) {
709
            $s .= trim($header) . ": " . trim($value) . "\r\n";
710
        }
711
712
        return $s;
713
    }
714
}
715