Skip to content

Commit 4a4a25c

Browse files
committed
first commit
0 parents  commit 4a4a25c

File tree

9 files changed

+656
-0
lines changed

9 files changed

+656
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/vendor
2+
/composer.lock
3+
/.idea
4+
/nbproject

bin/cli.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/php7.3
2+
<?php
3+
4+
include __DIR__ . '/../vendor/autoload.php';
5+
6+
$logger = new \Psr\Log\NullLogger();
7+
$agi = new \TryAGI\Client($logger);
8+
9+
$agi->init();
10+
$agi->send('VERBOSE "test verbose message"');

composer.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "userator/tryagi",
3+
"description": "asterisk gateway interface library",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "userator",
9+
"email": "userator@ya.ru"
10+
}
11+
],
12+
"require": {
13+
"php": "~7.3.0",
14+
"psr/log": "~1.1.0"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"TryAGI\\": "src/"
19+
}
20+
},
21+
"config": {
22+
"platform": {
23+
"php": "7.3"
24+
}
25+
}
26+
}

example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

src/Client.php

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<?php
2+
3+
namespace TryAGI;
4+
5+
use Psr\Log\LoggerInterface;
6+
use TryAGI\Exceptions\InvalidArgumentException;
7+
use TryAGI\Exceptions\RuntimeException;
8+
use TryAGI\Result;
9+
10+
/**
11+
* @author userator
12+
*/
13+
class Client
14+
{
15+
const CODE_200 = 200;
16+
const CODE_510 = 510;
17+
const CODE_511 = 511;
18+
const CODE_520 = 520;
19+
20+
/**
21+
* @var array
22+
*/
23+
private $channelVariables = [];
24+
25+
/**
26+
* @var resource
27+
*/
28+
private $stdin = STDIN;
29+
30+
/**
31+
* @var resource
32+
*/
33+
private $stdout = STDOUT;
34+
35+
/**
36+
* @var int
37+
*/
38+
private $timeout = 1000000;
39+
40+
/**
41+
* @var int
42+
*/
43+
private $chunk = 512;
44+
45+
/**
46+
* @var LoggerInterface
47+
*/
48+
private $logger;
49+
50+
public function __construct(LoggerInterface $logger)
51+
{
52+
$this->logger = $logger;
53+
}
54+
55+
function __destruct()
56+
{
57+
fclose($this->stdin);
58+
fclose($this->stdout);
59+
}
60+
61+
// mutators
62+
63+
public function getChannelVariables(): array
64+
{
65+
return $this->channelVariables;
66+
}
67+
68+
public function getStdin()
69+
{
70+
return $this->stdin;
71+
}
72+
73+
public function getStdout()
74+
{
75+
return $this->stdout;
76+
}
77+
78+
public function getTimeout(): int
79+
{
80+
return $this->timeout;
81+
}
82+
83+
public function getChunk(): int
84+
{
85+
return $this->chunk;
86+
}
87+
88+
public function setChannelVariables(array $channelVariables)
89+
{
90+
$this->channelVariables = $channelVariables;
91+
}
92+
93+
public function setStdin($stdin)
94+
{
95+
$this->stdin = $stdin;
96+
}
97+
98+
public function setStdout($stdout)
99+
{
100+
$this->stdout = $stdout;
101+
}
102+
103+
public function setTimeout(int $timeout)
104+
{
105+
$this->timeout = $timeout;
106+
}
107+
108+
public function setChunk(int $chunk)
109+
{
110+
$this->chunk = $chunk;
111+
}
112+
113+
public function getLogger(): LoggerInterface
114+
{
115+
return $this->logger;
116+
}
117+
118+
public function setLogger(LoggerInterface $logger)
119+
{
120+
$this->logger = $logger;
121+
}
122+
123+
// business logic
124+
125+
public function init()
126+
{
127+
$this->channelVariables = $this->readChannelVariables($this->read());
128+
}
129+
130+
/**
131+
* @param string $command
132+
* @return \Response
133+
* @throws InvalidArgumentException
134+
*/
135+
public function send(string $command): Result
136+
{
137+
$this->write($command);
138+
$message = $this->read();
139+
return Result::fromArray($this->readResult($message));
140+
}
141+
142+
// tools
143+
144+
private function readChannelVariables(string $message): array
145+
{
146+
$lines = explode("\n", $message);
147+
148+
if (!$lines) throw new RuntimeException('lines do not exist');
149+
150+
$output = [];
151+
152+
foreach ($lines as $line) {
153+
if (!$this->isChannelVariableLine($line)) continue;
154+
$parsed = $this->parseChannelVariableLine($line);
155+
$output[$parsed['key']] = $parsed['value'];
156+
}
157+
158+
return $output;
159+
}
160+
161+
private function readResult(string $message): array
162+
{
163+
$lines = explode("\n", $message);
164+
165+
if (!$lines) throw new RuntimeException('lines do not exist');
166+
167+
$lines = array_filter($lines, function ($line) {
168+
return $this->isSingleLine($line) || $this->isMultiLine($line);
169+
});
170+
171+
$singleLines = array_filter($lines, function ($line) {
172+
return $this->isSingleLine($line);
173+
});
174+
175+
if (count($singleLines) > 1) throw new RuntimeException('detect many single-line');
176+
177+
$parsedLines = array_map(function($line) {
178+
if ($this->isMultiLine($line)) return $this->parseMultiLine($line);
179+
if ($this->isSingleLine($line)) return $this->parseSingleLine($line);
180+
}, $lines);
181+
182+
return $this->collapseLines($parsedLines);
183+
}
184+
185+
private function write(string $text): int
186+
{
187+
$output = fwrite($this->stdout, $text);
188+
if (false === $output) throw new RuntimeException('Stream write');
189+
return $output;
190+
}
191+
192+
private function read(): string
193+
{
194+
$stdin = [$this->stdin];
195+
$stdout = [];
196+
$stderr = [];
197+
198+
$output = '';
199+
while (is_resource($this->stdin) && !feof($this->stdin)) {
200+
$ready = stream_select($stdin, $stdout, $stderr, 0, $this->timeout);
201+
if (false === $ready) throw new RuntimeException('Stream error');
202+
if (0 === $ready) throw new RuntimeException('Stream timeout');
203+
$output .= stream_get_contents($this->stdin, $this->chunk);
204+
}
205+
206+
return $output;
207+
}
208+
209+
private function isSeparatorLine(string $line): bool
210+
{
211+
return $line === '';
212+
}
213+
214+
private function isChannelVariableLine(string $line): bool
215+
{
216+
$matches = [];
217+
if (false === preg_match('/^agi_.+$/', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
218+
return (bool) $matches;
219+
}
220+
221+
private function isSingleLine(string $line): bool
222+
{
223+
$matches = [];
224+
if (false === preg_match('/^\d{3} /', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
225+
return (bool) $matches;
226+
}
227+
228+
private function isMultiLine(string $line): bool
229+
{
230+
$matches = [];
231+
if (false === preg_match('/^\d{3}-/', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
232+
return (bool) $matches;
233+
}
234+
235+
private function parseChannelVariableLine(string $line): array
236+
{
237+
$matches = [];
238+
if (false === preg_match('/^(agi_.+): (.*)$/', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
239+
return [
240+
'key' => $matches[1],
241+
'value' => $matches[2],
242+
];
243+
}
244+
245+
private function parseMultiLine(string $line): array
246+
{
247+
$matches = [];
248+
if (false === preg_match('/^(\d{3})-(.*)$/', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
249+
return [
250+
'code' => $matches[1],
251+
'result' => '',
252+
'data' => $matches[2],
253+
];
254+
}
255+
256+
private function parseSingleLine(string $line): array
257+
{
258+
$matches = [];
259+
if (false === preg_match('/^(\d{3}) result=(-?\d+)(?: \(?(.*)\)?)?$/', preg_quote($line), $matches)) throw new RuntimeException('Regexp error');
260+
return [
261+
'code' => $matches[1],
262+
'result' => $matches[2],
263+
'data' => isset($matches[3]) ? $matches[3] : '',
264+
];
265+
}
266+
267+
private function collapseLines(array $lines): array
268+
{
269+
return array_reduce($lines, function ($carry, $item) {
270+
return [
271+
'code' => $item['code'],
272+
'result' => $item['result'],
273+
'data' => $carry['data'] . $item['data'],
274+
];
275+
}, [
276+
'code' => '',
277+
'result' => '',
278+
'data' => '',
279+
]);
280+
}
281+
282+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace TryAGI\Exceptions;
4+
5+
/**
6+
* @author kurshin
7+
*/
8+
class InvalidArgumentException extends \InvalidArgumentException
9+
{
10+
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace TryAGI\Exceptions;
4+
5+
/**
6+
* @author kurshin
7+
*/
8+
class RuntimeException extends \RuntimeException
9+
{
10+
11+
}

0 commit comments

Comments
 (0)