Browse Source

Merge pull request #40 from EvilFreelancer/development

Update to version 1.3
tags/1.3.0 1.3.0
Paul Zloi 6 years ago
committed by GitHub
parent
commit
2f822d8be6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 2
      .travis.yml
  3. 165
      README.md
  4. 15
      composer.json
  5. 44
      configs/routeros-api.php
  6. 2
      examples/bridge_hosts.php
  7. 6
      examples/different_queries.php
  8. 30
      examples/export.php
  9. 2
      examples/interface_print.php
  10. 2
      examples/ip_address_print.php
  11. 2
      examples/ip_filrewall_address-list_print.php
  12. 2
      examples/queue_simple_print.php
  13. 2
      examples/queue_simple_print_v2.php
  14. 4
      examples/queue_simple_write.php
  15. 14
      examples/remove_security_profile.php
  16. 2
      examples/system_package_print.php
  17. 6
      examples/vlans_bridge.php
  18. 6
      examples/vlans_bridge_v2.php
  19. 6
      examples/vlans_bridge_v3.php
  20. 14
      phpunit.xml
  21. 2
      preconf.tcl
  22. 15
      src/APIConnector.php
  23. 8
      src/APILengthCoDec.php
  24. 174
      src/Client.php
  25. 106
      src/Config.php
  26. 14
      src/Helpers/ArrayHelper.php
  27. 81
      src/Interfaces/ClientInterface.php
  28. 34
      src/Interfaces/ConfigInterface.php
  29. 2
      src/Interfaces/QueryInterface.php
  30. 2
      src/Interfaces/StreamInterface.php
  31. 24
      src/Laravel/ClientWrapper.php
  32. 6
      src/Laravel/Facade.php
  33. 4
      src/Laravel/ServiceProvider.php
  34. 63
      src/Laravel/Wrapper.php
  35. 10
      src/Query.php
  36. 27
      src/ResponseIterator.php
  37. 87
      src/ShortsTrait.php
  38. 37
      src/SocketTrait.php
  39. 27
      src/Streams/ResourceStream.php
  40. 24
      src/Streams/StringStream.php
  41. 8
      tests/APIConnectorTest.php
  42. 25
      tests/APILengthCoDecTest.php
  43. 230
      tests/ClientTest.php
  44. 55
      tests/ConfigTest.php
  45. 4
      tests/Helpers/ArrayHelperTest.php
  46. 5
      tests/Helpers/BinaryStringHelperTest.php
  47. 2
      tests/Helpers/TypeHelperTest.php
  48. 62
      tests/Laravel/ServiceProviderTests.php
  49. 35
      tests/Laravel/TestCase.php
  50. 30
      tests/QueryTest.php
  51. 39
      tests/ResponseIteratorTest.php
  52. 66
      tests/Streams/ResourceStreamTest.php
  53. 43
      tests/Streams/StringStreamTest.php

2
.gitignore

@ -2,4 +2,4 @@
/vendor/
/composer.lock
/clover.xml
/.phpunit.result.cache
/.phpunit.result.cache

2
.travis.yml

@ -27,7 +27,7 @@ before_script:
- ./preconf.tcl 12223 > /dev/null || true
- ./preconf.tcl 22223 > /dev/null || true
- composer self-update
- composer install --prefer-source --no-interaction --dev
- composer install --no-interaction --dev
script:
- vendor/bin/phpunit --coverage-clover=coverage.clover

165
README.md

@ -6,7 +6,7 @@
[![Code Coverage](https://scrutinizer-ci.com/g/EvilFreelancer/routeros-api-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/EvilFreelancer/routeros-api-php/?branch=master)
[![Scrutinizer CQ](https://scrutinizer-ci.com/g/evilfreelancer/routeros-api-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/evilfreelancer/routeros-api-php/)
# RouterOS PHP7 API Client
# RouterOS API Client
composer require evilfreelancer/routeros-api-php
@ -17,6 +17,11 @@ to work with PHP7 in accordance with the PSR standards.
You can use this library with pre-6.43 and post-6.43 versions of
RouterOS firmware, it will be detected automatically on connection stage.
## Minimum requirements
* `php` >= 7.2
* `ext-sockets`
## Laravel framework support
RouterOS API client is optimized for usage as normal Laravel package, all functional is available via `\RouterOS` facade,
@ -32,10 +37,10 @@ $config = new \RouterOS\Config([
$client = new \RouterOS\Client($config);
```
Call facade and pass array of parameters to `getClient` method:
Call facade and pass array of parameters to `client` method:
```php
$client = \RouterOS::getClient([
$client = \RouterOS::client([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin',
@ -43,26 +48,37 @@ $client = \RouterOS::getClient([
]);
```
### Laravel installation
You also may get array with all configs which was obtained from `routeros-api.php` file:
```php
$config = \RouterOS::config([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin',
'port' => 8728,
]);
Install the package via Composer:
dump($config);
composer require evilfreelancer/routeros-api-php
$client = \RouterOS::client($config);
```
By default the package will automatically register its service provider, but
if you are a happy owner of Laravel version less than 5.3, then in a project, which is using your package
### Laravel installation
By default, the package will automatically register its service provider, but
if you are a happy owner of Laravel version less than 5.5, then in a project, which is using your package
(after composer require is done, of course), add into`providers` block of your `config/app.php`:
```php
'providers' => [
// ...
RouterOS\Laravel\ClientServiceProvider::class,
RouterOS\Laravel\ServiceProvider::class,
],
```
Optionally, publish the configuration file if you want to change any defaults:
php artisan vendor:publish --provider="RouterOS\\Laravel\\ClientServiceProvider"
php artisan vendor:publish --provider="RouterOS\\Laravel\\ServiceProvider"
## How to use
@ -88,18 +104,53 @@ $query =
$response = $client->query($query)->read();
var_dump($response);
```
Basic example for update/create/delete types of queries:
// Send "equal" query
```php
use \RouterOS\Client;
use \RouterOS\Query;
// Initiate client with config object
$client = new Client([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin'
]);
// Send "equal" query with details about IP address which should be created
$query =
(new Query('/ip/hotspot/ip-binding/add'))
->equal('mac-address', '00:00:00:00:40:29')
->equal('type', 'bypassed')
->equal('comment', 'testcomment');
// Send query and read response from RouterOS (ordinary answer to update/create/delete queries has empty body)
// Send query and read response from RouterOS (ordinary answer from update/create/delete queries has empty body)
$response = $client->query($query)->read();
var_dump($response);
```
If you need export all settings from router:
```php
use \RouterOS\Client;
// Initiate client with config object
$client = new Client([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin',
'ssh_port' => 22222,
]);
// Execute export command via ssh, because API /export method has a bug
$response = $client->export();
print_r($response);
```
Examples with "where" conditions, "operations" and "tag":
```php
@ -257,10 +308,10 @@ need to create a "Query" object whose first argument is the
required command, after this you can add the attributes of the
command to "Query" object.
More about attributes and "words" from which this attributes
More about attributes and "words" from which these attributes
should be created [here](https://wiki.mikrotik.com/wiki/Manual:API#Command_word).
More about "expressions", "where" and other filters/modificators
More about "expressions", "where", "equal" and other filters/modifications
of your query you can find [here](https://wiki.mikrotik.com/wiki/Manual:API#Queries).
Simple usage examples of Query class:
@ -271,6 +322,13 @@ use \RouterOS\Query;
// Get all installed packages (it may be enabled or disabled)
$query = new Query('/system/package/getall');
// Send "equal" query with details about IP address which should be created
$query =
(new Query('/ip/hotspot/ip-binding/add'))
->equal('mac-address', '00:00:00:00:40:29')
->equal('type', 'bypassed')
->equal('comment', 'testcomment');
// Set where interface is disabled and ID is ether1 (with tag 4)
$query =
(new Query('/interface/set'))
@ -285,7 +343,7 @@ $query =
->where('type', 'vlan')
->operations('|');
/// Get all routes that have non-empty comment
// Get all routes that have non-empty comment
$query =
(new Query('/ip/route/print'))
->where('comment', '>', null);
@ -357,8 +415,8 @@ $query->operations('|');
// Enable interface (tag is 4)
$query = new Query('/interface/set');
$query->where('disabled', 'no');
$query->where('.id', 'ether1');
$query->equal('disabled', 'no');
$query->equal('.id', 'ether1');
$query->tag(4);
// Or
@ -396,8 +454,8 @@ $response = $client->query($query)->read();
## Read response as Iterator
By default original solution of this client is not optimized for
work with large amount of results, only for small count of lines
By default, original solution of this client is not optimized for
work with a large amount of results, only for small count of lines
in response from RouterOS API.
But some routers may have (for example) 30000+ records in
@ -468,6 +526,75 @@ $client->query($query1)->query($query2)->query($query3);
$client->q($query1)->q($query2)->q($query3);
```
## Known issues
### Unable to establish socket session, Operation timed out
This error means that the library cannot connect to your router,
it may mean router turned off (then need turn on), or the API service not enabled.
Go to `Mikrotik Router OS -> IP -> Services` and enable `api` service.
Or via command line:
```shell script
/ip service enable api
```
### How to update/remove/create something via API?
Instead of `->where()` method of `Query` class you need to
use `->equal()` method:
```php
// Create query which should remove security profile
$query = new \RouterOS\Query('/interface/wireless/security-profiles/remove');
// It will generate queries, which stared from "?" symbol:
$query->where('.id', '*1');
/*
// Sample with ->where() method
RouterOS\Query Object
(
[_attributes:RouterOS\Query:private] => Array
(
[0] => ?.id=*1
)
[_operations:RouterOS\Query:private] =>
[_tag:RouterOS\Query:private] =>
[_endpoint:RouterOS\Query:private] => /interface/wireless/security-profiles/remove
)
*/
// So, as you can see, instead of `->where()` need to use `->equal()`
// It will generate queries, which stared from "=" symbol:
$query->equal('.id', '*1');
/*
// Sample with ->equal() method
RouterOS\Query Object
(
[_attributes:RouterOS\Query:private] => Array
(
[0] => =.id=*1
)
[_operations:RouterOS\Query:private] =>
[_tag:RouterOS\Query:private] =>
[_endpoint:RouterOS\Query:private] => /interface/wireless/security-profiles/remove
)
*/
```
### Undefined character (any non-English languages)
RouterOS does not support national languages, only English (and API of RouterOS too).
You can try to reproduce it via web, for example add the comment to any
element of your system, then save and reload the page, you will see unreadable characters.
## Testing
You can use my [other project](https://github.com/EvilFreelancer/docker-routeros)

15
composer.json

@ -33,22 +33,25 @@
"extra": {
"laravel": {
"providers": [
"RouterOS\\Laravel\\ClientServiceProvider"
"RouterOS\\Laravel\\ServiceProvider"
],
"aliases": {
"RouterOS": "RouterOS\\Laravel\\ClientFacade"
"RouterOS": "RouterOS\\Laravel\\Facade"
}
}
},
"require": {
"php": "^7.2",
"ext-sockets": "*"
"ext-sockets": "*",
"divineomega/php-ssh-connection": "^2.2"
},
"require-dev": {
"phpunit/phpunit": "^7.0",
"orchestra/testbench": "^3.0",
"limedeck/phpunit-detailed-printer": "^5.0",
"orchestra/testbench": "^4.0|^5.0",
"phpunit/phpunit": "^8.0",
"roave/security-advisories": "dev-master",
"squizlabs/php_codesniffer": "^3.5"
"squizlabs/php_codesniffer": "^3.5",
"larapack/dd": "^1.1"
},
"scripts": {
"test": "phpunit --coverage-clover clover.xml",

44
configs/routeros-api.php

@ -1,13 +1,39 @@
<?php
return [
// 'host' => null,
// 'user' => null,
// 'pass' => null,
// 'port' => null,
'ssl' => false,
'legacy' => false,
'timeout' => 10,
'attempts' => 10,
'delay' => 1,
/*
|--------------------------------------------------------------------------
| Connection details
|--------------------------------------------------------------------------
|
| Here you may specify different information about your router, like
| hostname (or ip-address), username, password, port and ssl mode.
|
| SSH port should be set if you want to use "/export" command.
|
*/
'host' => '192.168.88.1', // Address of Mikrotik RouterOS
'user' => 'admin', // Username
'pass' => null, // Password
'port' => 8728, // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'ssl' => false, // Enable ssl support (if port is not set this parameter must change default port to ssl port)
'ssh_port' => 22, // Number of SSH port
/*
|--------------------------------------------------------------------------
| Optional connection settings of client
|--------------------------------------------------------------------------
|
| Settings bellow need to advanced tune of your connection, for example
| you may enable legacy mode by default, or change timeout of connection.
|
*/
'legacy' => false, // Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'timeout' => 10, // Max timeout for answer from RouterOS
'attempts' => 10, // Count of attempts to establish TCP session
'delay' => 1, // Delay between attempts in seconds
];

2
examples/bridge_hosts.php

@ -22,5 +22,5 @@ $client = new Client($config);
$query = new Query('/interface/bridge/host/print');
// Send query to RouterOS
$response = $client->write($query)->read();
$response = $client->query($query)->read();
print_r($response);

6
examples/different_queries.php

@ -14,12 +14,12 @@ $client = new Client([
]);
for ($i = 0; $i < 10; $i++) {
$response = $client->wr('/ip/address/print');
$response = $client->qr('/ip/address/print');
print_r($response);
$response = $client->wr('/ip/arp/print');
$response = $client->qr('/ip/arp/print');
print_r($response);
$response = $client->wr('/interface/print');
$response = $client->qr('/interface/print');
print_r($response);
}

30
examples/export.php

@ -12,14 +12,34 @@ $config =
(new Config())
->set('host', '127.0.0.1')
->set('pass', 'admin')
->set('user', 'admin');
->set('user', 'admin')
->set('ssh_port', 22222);
// Initiate client with config object
$client = new Client($config);
// Build query
// Execute export command via ssh
$response = $client->export();
dump($response);
/*
// In results you will see something like this
# jun/28/2020 16:31:21 by RouterOS 6.47
# software id =
#
#
#
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip dhcp-client
add disabled=no interface=ether1
*/
// But here is another example
$query = new Query('/export');
// Send query and read answer from RouterOS
$response = $client->write($query)->read(false);
print_r($response);
// Execute export command via ssh but in style of library
$response = $client->query($query)->read();
dump($response);

2
examples/interface_print.php

@ -21,7 +21,7 @@ $client = new Client($config);
$query = new Query('/interface/getall');
// Send query to RouterOS
$request = $client->write($query);
$request = $client->query($query);
// Read answer from RouterOS
$response = $client->read();

2
examples/ip_address_print.php

@ -18,5 +18,5 @@ $client = new Client([
$query = new Query('/ip/address/print');
// Send query to RouterOS
$response = $client->write($query)->read();
$response = $client->query($query)->read();
print_r($response);

2
examples/ip_filrewall_address-list_print.php

@ -14,7 +14,7 @@ $client = new Client([
]);
// Send query to RouterOS and parse response
$response = $client->write('/ip/firewall/address-list/print')->read();
$response = $client->query('/ip/firewall/address-list/print')->read();
// You could treat response as an array except using array_* function

2
examples/queue_simple_print.php

@ -25,6 +25,6 @@ $ips = [
foreach ($ips as $ip) {
$query = new Query('/queue/simple/print', ['?target=' . $ip . '/32']);
$response = $client->wr($query);
$response = $client->qr($query);
print_r($response);
}

2
examples/queue_simple_print_v2.php

@ -24,7 +24,7 @@ $ips = [
];
foreach ($ips as $ip) {
$response = $client->wr([
$response = $client->qr([
'/queue/simple/print',
'?target=' . $ip . '/32'
]);

4
examples/queue_simple_write.php

@ -12,8 +12,8 @@ $client = new Client([
'pass' => 'admin'
]);
$out = $client->write(['/queue/simple/add', '=name=test'])->read();
$out = $client->query(['/queue/simple/add', '=name=test'])->read();
print_r($out);
$out = $client->write(['/queue/simple/add', '=name=test'])->read();
$out = $client->query(['/queue/simple/add', '=name=test'])->read();
print_r($out);

14
examples/remove_security_profile.php

@ -0,0 +1,14 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
error_reporting(E_ALL);
// Create query which should remove security profile
$query = new \RouterOS\Query('/interface/wireless/security-profiles/remove');
// Here, instead of `->where()` need to use `->equal()`, it will generate queries,
// which stared from "=" symbol:
$query->where('.id', '*1');
$client = new \RouterOS\Client(['host' => '192.168.88.1', 'user' => 'admin', 'pass' => 'password']);
$response = $client->query($query)->read();

2
examples/system_package_print.php

@ -21,7 +21,7 @@ $client = new Client($config);
$query = new Query('/system/package/print');
// Send query to RouterOS
$request = $client->write($query);
$request = $client->query($query);
// Read answer from RouterOS
$response = $client->read();

6
examples/vlans_bridge.php

@ -35,14 +35,14 @@ foreach ($vlans as $vlanId => $ports) {
// Add bridges
$query = new Query('/interface/bridge/add');
$query->add("=name=vlan$vlanId-bridge")->add('vlan-filtering=no');
$response = $client->write($query)->read();
$response = $client->query($query)->read();
print_r($response);
// Add ports to bridge
foreach ($ports as $port) {
$bridgePort = new Query('/interface/bridge/port/add');
$bridgePort->add("=bridge=vlan$vlanId-bridge")->add("=pvid=$vlanId")->add("=interface=ether$port");
$response = $client->write($bridgePort)->read();
$response = $client->query($bridgePort)->read();
print_r($response);
}
@ -50,7 +50,7 @@ foreach ($vlans as $vlanId => $ports) {
foreach ($ports as $port) {
$vlan = new Query('/interface/bridge/vlan/add');
$vlan->add("=bridge=vlan$vlanId-bridge")->add("=untagged=ether$port")->add("=vlan-ids=$vlanId");
$response = $client->write($vlan)->read(false);
$response = $client->query($vlan)->read(false);
print_r($response);
}

6
examples/vlans_bridge_v2.php

@ -34,7 +34,7 @@ foreach ($vlans as $vlanId => $ports) {
'vlan-filtering=no'
]);
$response = $client->wr($query);
$response = $client->qr($query);
print_r($response);
// Add ports to bridge
@ -45,7 +45,7 @@ foreach ($vlans as $vlanId => $ports) {
"=interface=ether$port"
]);
$response = $client->wr($bridgePort);
$response = $client->qr($bridgePort);
print_r($response);
}
@ -57,7 +57,7 @@ foreach ($vlans as $vlanId => $ports) {
"=vlan-ids=$vlanId"
]);
$response = $client->wr($vlan);
$response = $client->qr($vlan);
print_r($response);
}

6
examples/vlans_bridge_v3.php

@ -28,7 +28,7 @@ $vlans = [
foreach ($vlans as $vlanId => $ports) {
// Add bridges
$response = $client->wr([
$response = $client->qr([
'/interface/bridge/add',
"=name=vlan$vlanId-bridge",
'vlan-filtering=no'
@ -37,7 +37,7 @@ foreach ($vlans as $vlanId => $ports) {
// Add ports to bridge
foreach ($ports as $port) {
$response = $client->wr([
$response = $client->qr([
'/interface/bridge/port/add',
"=bridge=vlan$vlanId-bridge",
"=pvid=$vlanId",
@ -48,7 +48,7 @@ foreach ($vlans as $vlanId => $ports) {
// Add untagged ports to bridge with tagging
foreach ($ports as $port) {
$response = $client->wr([
$response = $client->qr([
'/interface/bridge/vlan/add',
"=bridge=vlan$vlanId-bridge",
"=untagged=ether$port",

14
phpunit.xml

@ -1,10 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php" colors="true">
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
printerClass="LimeDeck\Testing\Printer"
processIsolation="false"
stopOnFailure="true">
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
<exclude>
<directory suffix=".php">./tests</directory>
<directory suffix="Test.php">./tests</directory>
</exclude>
</whitelist>
</filter>
@ -22,5 +31,6 @@
<env name="ROS_PASS" value="admin"/>
<env name="ROS_PORT_MODERN" value="8728"/>
<env name="ROS_PORT_LEGACY" value="18728"/>
<env name="ROS_SSH_PORT" value="22222"/>
</php>
</phpunit>

2
preconf.tcl

@ -7,7 +7,7 @@ set port [lindex $argv 0]
spawn telnet localhost $port
expect "Login: "
send "admin+c\n"
send "admin+etc\n"
expect "Password: "
send "\n"
expect "]:"

15
src/APIConnector.php

@ -20,17 +20,26 @@ class APIConnector
protected $stream;
/**
* Constructor
* APIConnector constructor.
*
* @param StreamInterface $stream
* @param \RouterOS\Interfaces\StreamInterface $stream
*/
public function __construct(StreamInterface $stream)
{
$this->stream = $stream;
}
/**
* Close stream connection
*
* @return void
*/
public function close(): void
{
$this->stream->close();
}
/**
* Reads a WORD from the stream
*
* WORDs are part of SENTENCE. Each WORD has to be encoded in certain way - length of the WORD followed by WORD content.

8
src/APILengthCoDec.php

@ -3,8 +3,10 @@
namespace RouterOS;
use DomainException;
use OverflowException;
use RouterOS\Interfaces\StreamInterface;
use RouterOS\Helpers\BinaryStringHelper;
use UnexpectedValueException;
/**
* class APILengthCoDec
@ -51,7 +53,7 @@ class APILengthCoDec
*
* @param int|float $length
*
* @return string
* @return string
*/
public static function encodeLength($length): string
{
@ -169,7 +171,7 @@ class APILengthCoDec
// How can we test it ?
// @codeCoverageIgnoreStart
throw new \OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system");
throw new OverflowException("Your system is using 32 bits integers, cannot decode this value ($firstByte) on this system");
// @codeCoverageIgnoreEnd
}
@ -189,6 +191,6 @@ class APILengthCoDec
// Now the only solution is 5 most significance bits are set to 1 (11111xxx)
// This is a control word, not implemented by Mikrotik for the moment
throw new \UnexpectedValueException('Control Word found');
throw new UnexpectedValueException('Control Word found');
}
}

174
src/Client.php

@ -2,18 +2,17 @@
namespace RouterOS;
use DivineOmega\SSHConnection\SSHConnection;
use RouterOS\Exceptions\ClientException;
use RouterOS\Exceptions\ConfigException;
use RouterOS\Exceptions\QueryException;
use RouterOS\Helpers\ArrayHelper;
use RouterOS\Interfaces\ClientInterface;
use RouterOS\Interfaces\QueryInterface;
use RouterOS\Helpers\ArrayHelper;
use function array_keys;
use function array_shift;
use function chr;
use function count;
use function is_array;
use function is_string;
use function md5;
use function pack;
use function preg_match_all;
@ -35,26 +34,33 @@ class Client implements Interfaces\ClientInterface
*
* @var \RouterOS\Config
*/
private $_config;
private $config;
/**
* API communication object
*
* @var \RouterOS\APIConnector
*/
private $connector;
private $_connector;
/**
* Some strings with custom output
*
* @var string
*/
private $customOutput;
/**
* Client constructor.
*
* @param array|\RouterOS\Interfaces\ConfigInterface $config
* @param array|\RouterOS\Interfaces\ConfigInterface $config Array with configuration or Config object
* @param bool $autoConnect If false it will skip auto-connect stage if not need to instantiate connection
*
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
*/
public function __construct($config)
public function __construct($config, bool $autoConnect = true)
{
// If array then need create object
if (is_array($config)) {
@ -67,7 +73,12 @@ class Client implements Interfaces\ClientInterface
}
// Save config if everything is okay
$this->_config = $config;
$this->config = $config;
// Skip next step if not need to instantiate connection
if (false === $autoConnect) {
return;
}
// Throw error if cannot to connect
if (false === $this->connect()) {
@ -85,49 +96,24 @@ class Client implements Interfaces\ClientInterface
*/
private function config(string $parameter)
{
return $this->_config->get($parameter);
}
/**
* Send write query to RouterOS
*
* @param string|array|\RouterOS\Query $query
*
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\QueryException
* @deprecated
*/
public function write($query): Client
{
if (is_string($query)) {
$query = new Query($query);
} elseif (is_array($query)) {
$endpoint = array_shift($query);
$query = new Query($endpoint, $query);
}
if (!$query instanceof Query) {
throw new QueryException('Parameters cannot be processed');
}
// Submit query to RouterOS
return $this->writeRAW($query);
return $this->config->get($parameter);
}
/**
* Send write query to RouterOS (modern version of write)
*
* @param string|\RouterOS\Query $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
*
* @return \RouterOS\Client
* @return \RouterOS\Interfaces\ClientInterface
* @throws \RouterOS\Exceptions\QueryException
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @since 1.0.0
*/
public function query($endpoint, array $where = null, string $operations = null, string $tag = null): Client
public function query($endpoint, array $where = null, string $operations = null, string $tag = null): ClientInterface
{
// If endpoint is string then build Query object
$query = ($endpoint instanceof Query)
@ -169,10 +155,10 @@ class Client implements Interfaces\ClientInterface
* @param \RouterOS\Interfaces\QueryInterface $query
*
* @return \RouterOS\Query
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\QueryException
* @throws \RouterOS\Exceptions\ClientException
*/
private function preQuery(array $item, Query $query): Query
private function preQuery(array $item, QueryInterface $query): QueryInterface
{
// Null by default
$key = null;
@ -199,41 +185,66 @@ class Client implements Interfaces\ClientInterface
/**
* Send write query object to RouterOS
*
* @param \RouterOS\Query $query
* @param \RouterOS\Interfaces\QueryInterface $query
*
* @return \RouterOS\Client
* @return \RouterOS\Interfaces\ClientInterface
* @throws \RouterOS\Exceptions\QueryException
* @throws \RouterOS\Exceptions\ConfigException
* @since 1.0.0
*/
private function writeRAW(Query $query): Client
private function writeRAW(QueryInterface $query): ClientInterface
{
$commands = $query->getQuery();
// Check if first command is export
if (strpos($commands[0], '/export') === 0) {
// Convert export command with all arguments to valid SSH command
$arguments = explode('/', $commands[0]);
unset($arguments[1]);
$arguments = implode(' ', $arguments);
// Call the router via ssh and store output of export
$this->customOutput = $this->export($arguments);
// Return current object
return $this;
}
// Send commands via loop to router
foreach ($query->getQuery() as $command) {
$this->_connector->writeWord(trim($command));
foreach ($commands as $command) {
$this->connector->writeWord(trim($command));
}
// Write zero-terminator (empty string)
$this->_connector->writeWord('');
$this->connector->writeWord('');
// Return current object
return $this;
}
/**
* Read RAW response from RouterOS
* Read RAW response from RouterOS, it can be /export command results also, not only array from API
*
* @return array
* @return array|string
* @since 1.0.0
*/
private function readRAW(): array
private function readRAW()
{
// By default response is empty
$response = [];
// We have to wait a !done or !fatal
$lastReply = false;
// Convert strings to array and return results
if ($this->isCustomOutput()) {
// Return RAW configuration
return $this->customOutput;
}
// Read answer from socket in loop
while (true) {
$word = $this->_connector->readWord();
$word = $this->connector->readWord();
if ('' === $word) {
if ($lastReply) {
@ -270,7 +281,7 @@ class Client implements Interfaces\ClientInterface
* Reply ends with a complete !done or !fatal block (ended with 'empty line')
* A !fatal block precedes TCP connexion close
*
* @param bool $parse
* @param bool $parse If need parse output to array
*
* @return mixed
*/
@ -279,6 +290,12 @@ class Client implements Interfaces\ClientInterface
// Read RAW response
$response = $this->readRAW();
// Return RAW configuration if custom output is set
if ($this->isCustomOutput()) {
$this->customOutput = null;
return $response;
}
// Parse results and return
return $parse ? $this->rosario($response) : $response;
}
@ -447,7 +464,7 @@ class Client implements Interfaces\ClientInterface
// => problem with legacy version, swap it and retry
// Only tested with ROS pre 6.43, will test with post 6.43 => this could make legacy parameter obsolete?
if ($legacyRetry && $this->isLegacy($response)) {
$this->_config->set('legacy', true);
$this->config->set('legacy', true);
return $this->login();
}
@ -468,7 +485,7 @@ class Client implements Interfaces\ClientInterface
* @return bool
* @throws \RouterOS\Exceptions\ConfigException
*/
private function isLegacy(array &$response): bool
private function isLegacy(array $response): bool
{
return count($response) > 1 && $response[0] === '!done' && !$this->config('legacy');
}
@ -481,7 +498,7 @@ class Client implements Interfaces\ClientInterface
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
*/
private function connect(): bool
public function connect(): bool
{
// By default we not connected
$connected = false;
@ -494,7 +511,7 @@ class Client implements Interfaces\ClientInterface
// If socket is active
if (null !== $this->getSocket()) {
$this->_connector = new APIConnector(new Streams\ResourceStream($this->getSocket()));
$this->connector = new APIConnector(new Streams\ResourceStream($this->getSocket()));
// If we logged in then exit from loop
if (true === $this->login()) {
$connected = true;
@ -512,4 +529,43 @@ class Client implements Interfaces\ClientInterface
// Return status of connection
return $connected;
}
/**
* Check if custom output is not empty
*
* @return bool
*/
private function isCustomOutput(): bool
{
return $this->customOutput !== null;
}
/**
* Execute export command on remote host, it also will be used
* if "/export" command passed to query.
*
* @param string|null $arguments String with arguments which should be passed to export command
*
* @return string
* @throws \RouterOS\Exceptions\ConfigException
* @since 1.3.0
*/
public function export(string $arguments = null): string
{
// Connect to remote host
$connection =
(new SSHConnection())
->timeout($this->config('timeout'))
->to($this->config('host'))
->onPort($this->config('ssh_port'))
->as($this->config('user') . '+etc')
->withPassword($this->config('pass'))
->connect();
// Run export command
$command = $connection->run('/export' . ' ' . $arguments);
// Return the output
return $command->getOutput();
}
}

106
src/Config.php

@ -6,6 +6,7 @@ use RouterOS\Exceptions\ConfigException;
use RouterOS\Helpers\ArrayHelper;
use RouterOS\Helpers\TypeHelper;
use RouterOS\Interfaces\ConfigInterface;
use function gettype;
/**
* Class Config with array of parameters
@ -16,16 +17,73 @@ use RouterOS\Interfaces\ConfigInterface;
class Config implements ConfigInterface
{
/**
* By default legacy login on RouterOS pre-6.43 is not supported
*/
public const LEGACY = false;
/**
* Default port number
*/
public const PORT = 8728;
/**
* Default ssl port number
*/
public const PORT_SSL = 8729;
/**
* Do not use SSL by default
*/
public const SSL = false;
/**
* Max timeout for answer from router
*/
public const TIMEOUT = 10;
/**
* Count of reconnect attempts
*/
public const ATTEMPTS = 10;
/**
* Delay between attempts in seconds
*/
public const ATTEMPTS_DELAY = 1;
/**
* Delay between attempts in seconds
*/
public const SSH_PORT = 22;
/**
* List of allowed parameters of config
*/
public const ALLOWED = [
'host' => 'string', // Address of Mikrotik RouterOS
'user' => 'string', // Username
'pass' => 'string', // Password
'port' => 'integer', // RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'ssl' => 'boolean', // Enable ssl support (if port is not set this parameter must change default port to ssl port)
'legacy' => 'boolean', // Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'timeout' => 'integer', // Max timeout for answer from RouterOS
'attempts' => 'integer', // Count of attempts to establish TCP session
'delay' => 'integer', // Delay between attempts in seconds
'ssh_port' => 'integer', // Number of SSH port
];
/**
* Array of parameters (with some default values)
*
* @var array
*/
private $_parameters = [
'legacy' => Client::LEGACY,
'ssl' => Client::SSL,
'timeout' => Client::TIMEOUT,
'attempts' => Client::ATTEMPTS,
'delay' => Client::ATTEMPTS_DELAY
'legacy' => self::LEGACY,
'ssl' => self::SSL,
'timeout' => self::TIMEOUT,
'attempts' => self::ATTEMPTS,
'delay' => self::ATTEMPTS_DELAY,
'ssh_port' => self::SSH_PORT,
];
/**
@ -44,15 +102,11 @@ class Config implements ConfigInterface
}
/**
* Set parameter into array
*
* @param string $name
* @param mixed $value
* @inheritDoc
*
* @return \RouterOS\Config
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\ConfigException when name of configuration key is invalid or not allowed
*/
public function set(string $name, $value): Config
public function set(string $name, $value): ConfigInterface
{
// Check of key in array
if (ArrayHelper::checkIfKeyNotExist($name, self::ALLOWED)) {
@ -60,8 +114,8 @@ class Config implements ConfigInterface
}
// Check what type has this value
if (TypeHelper::checkIfTypeMismatch(\gettype($value), self::ALLOWED[$name])) {
throw new ConfigException("Parameter '$name' has wrong type '" . \gettype($value) . "' but should be '" . self::ALLOWED[$name] . "'");
if (TypeHelper::checkIfTypeMismatch(gettype($value), self::ALLOWED[$name])) {
throw new ConfigException("Parameter '$name' has wrong type '" . gettype($value) . "' but should be '" . self::ALLOWED[$name] . "'");
}
// Save value to array
@ -83,21 +137,18 @@ class Config implements ConfigInterface
if ($parameter === 'port' && (!isset($this->_parameters['port']) || null === $this->_parameters['port'])) {
// then use default with or without ssl encryption
return (isset($this->_parameters['ssl']) && $this->_parameters['ssl'])
? Client::PORT_SSL
: Client::PORT;
? self::PORT_SSL
: self::PORT;
}
return null;
}
/**
* Remove parameter from array by name
*
* @param string $name
* @inheritDoc
*
* @return \RouterOS\Config
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\ConfigException when parameter is not allowed
*/
public function delete(string $name): Config
public function delete(string $name): ConfigInterface
{
// Check of key in array
if (ArrayHelper::checkIfKeyNotExist($name, self::ALLOWED)) {
@ -111,12 +162,9 @@ class Config implements ConfigInterface
}
/**
* Return parameter of current config by name
*
* @param string $name
* @inheritDoc
*
* @return mixed
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\ConfigException when parameter is not allowed
*/
public function get(string $name)
{
@ -129,9 +177,7 @@ class Config implements ConfigInterface
}
/**
* Return array with all parameters of configuration
*
* @return array
* @inheritDoc
*/
public function getParameters(): array
{

14
src/Helpers/ArrayHelper.php

@ -13,9 +13,10 @@ class ArrayHelper
/**
* Check if required single key in array of parameters
*
* @param string $key
* @param array $array
* @return bool
* @param string $key
* @param array $array
*
* @return bool
*/
public static function checkIfKeyNotExist(string $key, array $array): bool
{
@ -25,9 +26,10 @@ class ArrayHelper
/**
* Check if required keys in array of parameters
*
* @param array $keys
* @param array $array
* @return array|bool Return true if all fine, and string with name of key which was not found
* @param array $keys
* @param array $array
*
* @return array|bool Return true if all fine, and string with name of key which was not found
*/
public static function checkIfKeysNotExist(array $keys, array $array)
{

81
src/Interfaces/ClientInterface.php

@ -2,9 +2,6 @@
namespace RouterOS\Interfaces;
use RouterOS\Client;
use RouterOS\Query;
/**
* Interface ClientInterface
*
@ -14,41 +11,6 @@ use RouterOS\Query;
interface ClientInterface
{
/**
* By default legacy login on RouterOS pre-6.43 is not supported
*/
public const LEGACY = false;
/**
* Default port number
*/
public const PORT = 8728;
/**
* Default ssl port number
*/
public const PORT_SSL = 8729;
/**
* Do not use SSL by default
*/
public const SSL = false;
/**
* Max timeout for answer from router
*/
public const TIMEOUT = 10;
/**
* Count of reconnect attempts
*/
public const ATTEMPTS = 10;
/**
* Delay between attempts in seconds
*/
public const ATTEMPTS_DELAY = 1;
/**
* Return socket resource if is exist
*
* @return resource
@ -58,29 +20,42 @@ interface ClientInterface
/**
* Read answer from server after query was executed
*
* @param bool $parse
* A Mikrotik reply is formed of blocks
* Each block starts with a word, one of ('!re', '!trap', '!done', '!fatal')
* Each block end with an zero byte (empty line)
* Reply ends with a complete !done or !fatal block (ended with 'empty line')
* A !fatal block precedes TCP connexion close
*
* @param bool $parse If need parse output to array
*
* @return mixed
*/
public function read(bool $parse);
public function read(bool $parse = true);
/**
* Send write query to RouterOS
* Send write query to RouterOS (modern version of write)
*
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
*
* @param string|array|\RouterOS\Query $query
* @return \RouterOS\Client
* @return \RouterOS\Interfaces\ClientInterface
* @throws \RouterOS\Exceptions\QueryException
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @since 1.0.0
*/
public function write($query): Client;
public function query($endpoint, array $where, string $operations, string $tag): ClientInterface;
/**
* Send write query to RouterOS (modern version of write)
* Execute export command on remote host
*
* @param string|Query $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\QueryException
* @since 1.0.0
* @return string
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RuntimeException
*
* @since 1.3.0
*/
public function query($endpoint, array $where, string $operations, string $tag): Client;
public function export(): string;
}

34
src/Interfaces/ConfigInterface.php

@ -2,8 +2,6 @@
namespace RouterOS\Interfaces;
use RouterOS\Config;
/**
* Interface ConfigInterface
*
@ -13,47 +11,23 @@ use RouterOS\Config;
interface ConfigInterface
{
/**
* List of allowed parameters of config
*/
public const ALLOWED = [
// Address of Mikrotik RouterOS
'host' => 'string',
// Username
'user' => 'string',
// Password
'pass' => 'string',
// RouterOS API port number for access (if not set use default or default with SSL if SSL enabled)
'port' => 'integer',
// Enable ssl support (if port is not set this parameter must change default port to ssl port)
'ssl' => 'boolean',
// Support of legacy login scheme (true - pre 6.43, false - post 6.43)
'legacy' => 'boolean',
// Max timeout for answer from RouterOS
'timeout' => 'integer',
// Count of attempts to establish TCP session
'attempts' => 'integer',
// Delay between attempts in seconds
'delay' => 'integer',
];
/**
* Set parameter into array
*
* @param string $name
* @param mixed $value
*
* @return \RouterOS\Config
* @return \RouterOS\Interfaces\ConfigInterface
*/
public function set(string $name, $value): Config;
public function set(string $name, $value): ConfigInterface;
/**
* Remove parameter from array by name
*
* @param string $parameter
*
* @return \RouterOS\Config
* @return \RouterOS\Interfaces\ConfigInterface
*/
public function delete(string $parameter): Config;
public function delete(string $parameter): ConfigInterface;
/**
* Return parameter of current config by name

2
src/Interfaces/QueryInterface.php

@ -18,7 +18,7 @@ interface QueryInterface
* @param bool|string|int $operator It may be one from list [-,=,>,<]
*
* @return \RouterOS\Interfaces\QueryInterface
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\QueryException
* @since 1.0.0
*/
public function where(string $key, $operator = '=', $value = null): QueryInterface;

2
src/Interfaces/StreamInterface.php

@ -44,5 +44,5 @@ interface StreamInterface
*
* @return void
*/
public function close();
public function close(): void;
}

24
src/Laravel/ClientWrapper.php

@ -1,24 +0,0 @@
<?php
namespace RouterOS\Laravel;
use RouterOS\Client;
class ClientWrapper
{
/**
* @param array $params
*
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
*/
public function getClient(array $params = []): Client
{
$configs = config('routeros-api');
$configs = array_merge($configs, $params);
return new Client($configs);
}
}

6
src/Laravel/ClientFacade.php → src/Laravel/Facade.php

@ -2,9 +2,9 @@
namespace RouterOS\Laravel;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\Facade as BaseFacade;
class ClientFacade extends Facade
class Facade extends BaseFacade
{
/**
* Get the registered name of the component.
@ -13,6 +13,6 @@ class ClientFacade extends Facade
*/
protected static function getFacadeAccessor(): string
{
return ClientWrapper::class;
return Wrapper::class;
}
}

4
src/Laravel/ClientServiceProvider.php → src/Laravel/ServiceProvider.php

@ -4,7 +4,7 @@ namespace RouterOS\Laravel;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
class ClientServiceProvider extends BaseServiceProvider
class ServiceProvider extends BaseServiceProvider
{
/**
* Bootstrap any application services.
@ -29,6 +29,6 @@ class ClientServiceProvider extends BaseServiceProvider
__DIR__ . '/../../configs/routeros-api.php', 'routeros-api'
);
$this->app->bind(ClientWrapper::class);
$this->app->bind(Wrapper::class);
}
}

63
src/Laravel/Wrapper.php

@ -0,0 +1,63 @@
<?php
namespace RouterOS\Laravel;
use RouterOS\Client;
use RouterOS\Config;
use RouterOS\Interfaces\ClientInterface;
use RouterOS\Interfaces\ConfigInterface;
class Wrapper
{
/**
* Alias for \RouterOS::client() method
*
* @param array $params
*
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
* @deprecated
* @codeCoverageIgnore
*/
public function getClient(array $params = []): ClientInterface
{
return $this->client($params);
}
/**
* Get configs of library
*
* @param array $params
*
* @return \RouterOS\Interfaces\ConfigInterface
* @throws \RouterOS\Exceptions\ConfigException
*/
public function config(array $params = []): ConfigInterface
{
$config = config('routeros-api');
$config = array_merge($config, $params);
$config = new Config($config);
return $config;
}
/**
* Instantiate client object
*
* @param array $params
* @param bool $autoConnect
*
* @return \RouterOS\Interfaces\ClientInterface
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
*/
public function client(array $params = [], bool $autoConnect = true): ClientInterface
{
$config = $this->config($params);
return new Client($config, $autoConnect);
}
}

10
src/Query.php

@ -2,9 +2,11 @@
namespace RouterOS;
use RouterOS\Exceptions\ClientException;
use RouterOS\Exceptions\QueryException;
use RouterOS\Interfaces\QueryInterface;
use function in_array;
use function is_array;
use function is_string;
/**
* Class Query for building queries
@ -62,10 +64,10 @@ class Query implements QueryInterface
*/
public function __construct($endpoint, array $attributes = [])
{
if (\is_string($endpoint)) {
if (is_string($endpoint)) {
$this->setEndpoint($endpoint);
$this->setAttributes($attributes);
} elseif (\is_array($endpoint)) {
} elseif (is_array($endpoint)) {
$query = array_shift($endpoint);
$this->setEndpoint($query);
$this->setAttributes($endpoint);
@ -128,7 +130,7 @@ class Query implements QueryInterface
if (null !== $operator && null !== $value) {
// If operator is available in list
if (\in_array($operator, self::AVAILABLE_OPERATORS, true)) {
if (in_array($operator, self::AVAILABLE_OPERATORS, true)) {
$key = $operator . $key;
} else {
throw new QueryException('Operator "' . $operator . '" in not in allowed list [' . implode(',', self::AVAILABLE_OPERATORS) . ']');

27
src/ResponseIterator.php

@ -2,10 +2,15 @@
namespace RouterOS;
use \Iterator,
\ArrayAccess,
\Countable,
\Serializable;
use Iterator;
use ArrayAccess;
use Countable;
use Serializable;
use function array_keys;
use function array_slice;
use function count;
use function serialize;
use function unserialize;
/**
* This class was created by memory save reasons, it convert response
@ -35,7 +40,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
*
* @var array
*/
private $raw = [];
private $raw;
/**
* Initial value of array position
@ -95,7 +100,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
/**
* Move forward to next element
*/
public function next()
public function next(): void
{
++$this->current;
}
@ -103,7 +108,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
/**
* Previous value
*/
public function prev()
public function prev(): void
{
--$this->current;
}
@ -165,7 +170,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
/**
* Rewind the Iterator to the first element
*/
public function rewind()
public function rewind(): void
{
$this->current = 0;
}
@ -176,7 +181,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
if (null === $offset) {
$this->parsed[] = $value;
@ -202,7 +207,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
*
* @param mixed $offset
*/
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
unset($this->parsed[$offset], $this->raw[$offset]);
}
@ -245,7 +250,7 @@ class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable
*
* @param string $serialized
*/
public function unserialize($serialized)
public function unserialize($serialized): void
{
$this->raw = unserialize($serialized, null);
}

87
src/ShortsTrait.php

@ -14,30 +14,17 @@ namespace RouterOS;
trait ShortsTrait
{
/**
* Alias for ->write() method
*
* @param string|array|\RouterOS\Query $query
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\QueryException
* @deprecated
*/
public function w($query): Client
{
return $this->write($query);
}
/**
* Alias for ->query() method
*
* @param string $endpoint Path of API query
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
*
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\QueryException
* @since 1.0.0
*/
public function q(string $endpoint, array $where = null, string $operations = null, string $tag = null): Client
public function q($endpoint, array $where = null, string $operations = null, string $tag = null): Client
{
return $this->query($endpoint, $where, $operations, $tag);
}
@ -45,7 +32,8 @@ trait ShortsTrait
/**
* Alias for ->read() method
*
* @param bool $parse
* @param bool $parse If need parse output to array
*
* @return mixed
* @since 0.7
*/
@ -66,65 +54,34 @@ trait ShortsTrait
}
/**
* Alias for ->write()->read() combination of methods
* Alias for ->query()->read() combination of methods
*
* @param string|array|\RouterOS\Query $query
* @param bool $parse
* @return array
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\QueryException
* @since 0.6
* @deprecated
*/
public function wr($query, bool $parse = true): array
{
return $this->write($query)->read($parse);
}
/**
* Alias for ->write()->read() combination of methods
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @param bool $parse If need parse output to array
*
* @param string $endpoint Path of API query
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @param bool $parse
* @return \RouterOS\Client
* @throws \RouterOS\Exceptions\QueryException
* @return array
* @since 1.0.0
*/
public function qr(string $endpoint, array $where = null, string $operations = null, string $tag = null, bool $parse = true): array
public function qr($endpoint, array $where = null, string $operations = null, string $tag = null, bool $parse = true): array
{
return $this->query($endpoint, $where, $operations, $tag)->read($parse);
}
/**
* Alias for ->write()->readAsIterator() combination of methods
* Alias for ->query()->readAsIterator() combination of methods
*
* @param string|array|\RouterOS\Query $query
* @return \RouterOS\ResponseIterator
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\QueryException
* @since 1.0.0
* @deprecated
*/
public function wri($query): ResponseIterator
{
return $this->write($query)->readAsIterator();
}
/**
* Alias for ->write()->read() combination of methods
* @param array|string|\RouterOS\Interfaces\QueryInterface $endpoint Path of API query or Query object
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
*
* @param string $endpoint Path of API query
* @param array|null $where List of where filters
* @param string|null $operations Some operations which need make on response
* @param string|null $tag Mark query with tag
* @return \RouterOS\ResponseIterator
* @throws \RouterOS\Exceptions\QueryException
* @since 1.0.0
*/
public function qri(string $endpoint, array $where = null, string $operations = null, string $tag = null): ResponseIterator
public function qri($endpoint, array $where = null, string $operations = null, string $tag = null): ResponseIterator
{
return $this->query($endpoint, $where, $operations, $tag)->readAsIterator();
}

37
src/SocketTrait.php

@ -11,30 +11,30 @@ trait SocketTrait
*
* @var resource|null
*/
private $_socket;
private $socket;
/**
* Code of error
*
* @var int
*/
private $_socket_err_num;
private $socket_err_num;
/**
* Description of socket error
*
* @var string
*/
private $_socket_err_str;
private $socket_err_str;
/**
* Initiate socket session
*
* @return void
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @return void
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
*/
private function openSocket()
private function openSocket(): void
{
// Default: Context for ssl
$context = stream_context_create([
@ -51,8 +51,8 @@ trait SocketTrait
// Initiate socket client
$socket = @stream_socket_client(
$proto . $this->config('host') . ':' . $this->config('port'),
$this->_socket_err_num,
$this->_socket_err_str,
$this->socket_err_num,
$this->socket_err_str,
$this->config('timeout'),
STREAM_CLIENT_CONNECT,
$context
@ -60,12 +60,12 @@ trait SocketTrait
// Throw error is socket is not initiated
if (false === $socket) {
throw new ClientException('Unable to establish socket session, ' . $this->_socket_err_str);
throw new ClientException('Unable to establish socket session, ' . $this->socket_err_str);
}
//Timeout read
stream_set_timeout($socket, $this->config('timeout'));
// Save socket to static variable
$this->setSocket($socket);
}
@ -77,27 +77,28 @@ trait SocketTrait
*/
private function closeSocket(): bool
{
return fclose($this->_socket);
return fclose($this->socket);
}
/**
* Save socket resource to static variable
*
* @param resource $socket
* @param resource $socket
*
* @return void
*/
private function setSocket($socket)
private function setSocket($socket): void
{
$this->_socket = $socket;
$this->socket = $socket;
}
/**
* Return socket resource if is exist
*
* @return resource
* @return resource
*/
public function getSocket()
{
return $this->_socket;
return $this->socket;
}
}

27
src/Streams/ResourceStream.php

@ -38,10 +38,10 @@ class ResourceStream implements StreamInterface
}
/**
* @param int $length
* @return string
* @throws \RouterOS\Exceptions\StreamException
* @throws \InvalidArgumentException
* @inheritDoc
*
* @throws \RouterOS\Exceptions\StreamException when length parameter is invalid
* @throws \InvalidArgumentException when the stream have been totally read and read method is called again
*/
public function read(int $length): string
{
@ -60,17 +60,9 @@ class ResourceStream implements StreamInterface
}
/**
* Writes a string to a stream
*
* Write $length bytes of string, if not mentioned, write all the string
* Must be binary safe (as fread).
* if $length is greater than string length, write all string and return number of writen bytes
* if $length os smaller than string length, remaining bytes are losts.
* @inheritDoc
*
* @param string $string
* @param int|null $length the numer of bytes to read
* @return int the number of written bytes
* @throws \RouterOS\Exceptions\StreamException
* @throws \RouterOS\Exceptions\StreamException when not possible to write bytes
*/
public function write(string $string, int $length = null): int
{
@ -89,12 +81,11 @@ class ResourceStream implements StreamInterface
}
/**
* Close stream connection
* @inheritDoc
*
* @return void
* @throws \RouterOS\Exceptions\StreamException
* @throws \RouterOS\Exceptions\StreamException when not possible to close the stream
*/
public function close()
public function close(): void
{
$hasBeenClosed = false;

24
src/Streams/StringStream.php

@ -2,6 +2,7 @@
namespace RouterOS\Streams;
use InvalidArgumentException;
use RouterOS\Interfaces\StreamInterface;
use RouterOS\Exceptions\StreamException;
@ -31,18 +32,18 @@ class StringStream implements StreamInterface
$this->buffer = $string;
}
/**
* {@inheritDoc}
* @inheritDoc
*
* @throws \InvalidArgumentException when length parameter is invalid
* @throws StreamException when the stream have been tatly red and read methd is called again
* @throws \RouterOS\Exceptions\StreamException
*/
public function read(int $length): string
{
$remaining = strlen($this->buffer);
if ($length < 0) {
throw new \InvalidArgumentException('Cannot read a negative count of bytes from a stream');
throw new InvalidArgumentException('Cannot read a negative count of bytes from a stream');
}
if (0 === $remaining) {
@ -65,12 +66,9 @@ class StringStream implements StreamInterface
}
/**
* Fake write method, do nothing except return the "writen" length
* @inheritDoc
*
* @param string $string The string to write
* @param int|null $length the number of characters to write
* @return int number of "writen" bytes
* @throws \InvalidArgumentException on invalid length
* @throws \InvalidArgumentException on invalid length
*/
public function write(string $string, int $length = null): int
{
@ -79,18 +77,16 @@ class StringStream implements StreamInterface
}
if ($length < 0) {
throw new \InvalidArgumentException('Cannot write a negative count of bytes');
throw new InvalidArgumentException('Cannot write a negative count of bytes');
}
return min($length, strlen($string));
}
/**
* Close stream connection
*
* @return void
* @inheritDoc
*/
public function close()
public function close(): void
{
$this->buffer = '';
}

8
tests/APIConnectorTest.php

@ -25,7 +25,7 @@ class APIConnectorTest extends TestCase
* @param StreamInterface $stream Cannot typehint, PHP refuse it
* @param bool $closeResource shall we close the resource ?
*/
public function test_construct(StreamInterface $stream, bool $closeResource = false)
public function testConstruct(StreamInterface $stream, bool $closeResource = false): void
{
$apiStream = new APIConnector($stream);
$this->assertInstanceOf(APIConnector::class, $apiStream);
@ -38,7 +38,7 @@ class APIConnectorTest extends TestCase
{
return [
[new ResourceStream(fopen(__FILE__, 'rb')),], // Myself, sure I exists
[new ResourceStream(fsockopen('tcp://' . getenv('ROS_HOST'), getenv('ROS_PORT_MODERN'))),], // Socket
[new ResourceStream(fsockopen('tcp://' . getenv('ROS_HOST'), getenv('ROS_PORT_MODERN')))], // Socket
[new ResourceStream(STDIN), false], // Try it, but do not close STDIN please !!!
[new StringStream('Hello World !!!')], // Try it, but do not close STDIN please !!!
[new StringStream('')], // Try it, but do not close STDIN please !!!
@ -53,7 +53,7 @@ class APIConnectorTest extends TestCase
* @param APIConnector $connector
* @param string $expected
*/
public function test__readWord(APIConnector $connector, string $expected)
public function testReadWord(APIConnector $connector, string $expected): void
{
$this->assertSame($expected, $connector->readWord());
}
@ -79,7 +79,7 @@ class APIConnectorTest extends TestCase
* @param string $toWrite
* @param int $expected
*/
public function test_writeWord(APIConnector $connector, string $toWrite, int $expected)
public function testWriteWord(APIConnector $connector, string $toWrite, int $expected): void
{
$this->assertEquals($expected, $connector->writeWord($toWrite));
}

25
tests/APILengthCoDecTest.php

@ -2,9 +2,8 @@
namespace RouterOS\Tests;
use DomainException;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Constraint\IsType;
use RouterOS\APILengthCoDec;
use RouterOS\Streams\StringStream;
use RouterOS\Helpers\BinaryStringHelper;
@ -18,11 +17,13 @@ class APILengthCoDecTest extends TestCase
{
/**
* @dataProvider encodeLengthNegativeProvider
* @expectedException \DomainException
* @covers ::encodeLength
*
* @param $length
*/
public function test__encodeLengthNegative($length)
public function testEncodeLengthNegative($length): void
{
$this->expectException(DomainException::class);
APILengthCoDec::encodeLength($length);
}
@ -37,8 +38,11 @@ class APILengthCoDecTest extends TestCase
/**
* @dataProvider encodedLengthProvider
* @covers ::encodeLength
*
* @param $expected
* @param $length
*/
public function test__encodeLength($expected, $length)
public function testEncodeLength($expected, $length): void
{
$this->assertEquals(BinaryStringHelper::IntegerToNBOBinaryString((int) $expected), APILengthCoDec::encodeLength($length));
}
@ -76,8 +80,11 @@ class APILengthCoDecTest extends TestCase
/**
* @dataProvider encodedLengthProvider
* @covers ::decodeLength
*
* @param $encodedLength
* @param $expected
*/
public function test__decodeLength($encodedLength, $expected)
public function testDecodeLength($encodedLength, $expected): void
{
// We have to provide $encodedLength as a "bytes" stream
$stream = new StringStream(BinaryStringHelper::IntegerToNBOBinaryString($encodedLength));
@ -87,10 +94,12 @@ class APILengthCoDecTest extends TestCase
/**
* @dataProvider decodeLengthControlWordProvider
* @covers ::decodeLength
* @expectedException \UnexpectedValueException
*
* @param string $encodedLength
*/
public function test_decodeLengthControlWord(string $encodedLength)
public function testDecodeLengthControlWord(string $encodedLength): void
{
$this->expectException(\UnexpectedValueException::class);
APILengthCoDec::decodeLength(new StringStream($encodedLength));
}

230
tests/ClientTest.php

@ -2,6 +2,7 @@
namespace RouterOS\Tests;
use Exception;
use PHPUnit\Framework\TestCase;
use RouterOS\Client;
use RouterOS\Exceptions\ConfigException;
@ -15,7 +16,12 @@ class ClientTest extends TestCase
/**
* @var array
*/
public $router;
public $config;
/**
* @var \RouterOS\Client
*/
public $client;
/**
* @var int
@ -27,86 +33,100 @@ class ClientTest extends TestCase
*/
public $port_legacy;
public function setUp()
public function setUp(): void
{
parent::setUp();
$this->router = [
'user' => getenv('ROS_USER'),
'pass' => getenv('ROS_PASS'),
'host' => getenv('ROS_HOST'),
$this->config = [
'user' => getenv('ROS_USER'),
'pass' => getenv('ROS_PASS'),
'host' => getenv('ROS_HOST'),
'ssh_port' => (int) getenv('ROS_SSH_PORT'),
];
$this->client = new Client($this->config);
$this->port_modern = (int) getenv('ROS_PORT_MODERN');
$this->port_legacy = (int) getenv('ROS_PORT_LEGACY');
}
public function test__construct(): void
public function testConstruct(): void
{
try {
$config = new Config();
$config
->set('user', $this->router['user'])
->set('pass', $this->router['pass'])
->set('host', $this->router['host']);
->set('user', $this->config['user'])
->set('pass', $this->config['pass'])
->set('host', $this->config['host']);
$obj = new Client($config);
$this->assertIsObject($obj);
$socket = $obj->getSocket();
$this->assertIsResource($socket);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
} catch (Exception $e) {
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__construct2(): void
public function testConstruct2(): void
{
try {
$config = new Config($this->router);
$config = new Config($this->config);
$obj = new Client($config);
$this->assertIsObject($obj);
$socket = $obj->getSocket();
$this->assertIsResource($socket);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
} catch (Exception $e) {
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__construct3(): void
public function testConstruct3(): void
{
try {
$obj = new Client($this->router);
$obj = new Client($this->config);
$this->assertIsObject($obj);
$socket = $obj->getSocket();
$this->assertIsResource($socket);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
} catch (Exception $e) {
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__constructEx(): void
public function testConstructException(): void
{
$this->expectException(ConfigException::class);
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
new Client([
'user' => $this->config['user'],
'pass' => $this->config['pass'],
]);
}
public function testConstructExceptionBadHost(): void
{
$this->expectException(ClientException::class);
new Client([
'host' => '127.0.0.1',
'port' => 123456,
'attempts' => 0,
'user' => $this->config['user'],
'pass' => $this->config['pass'],
]);
}
public function test__constructLegacy(): void
public function testConstructLegacy(): void
{
try {
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
'user' => $this->config['user'],
'pass' => $this->config['pass'],
'host' => $this->config['host'],
'port' => $this->port_legacy,
'legacy' => true
]);
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
} catch (Exception $e) {
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
@ -115,46 +135,42 @@ class ClientTest extends TestCase
*
* login() method recognise legacy router response and swap to legacy mode
*/
public function test__constructLegacy2(): void
public function testConstructLegacy2(): void
{
try {
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
'user' => $this->config['user'],
'pass' => $this->config['pass'],
'host' => $this->config['host'],
'port' => $this->port_legacy,
'legacy' => false
]);
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
} catch (Exception $e) {
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__constructWrongPass(): void
public function testConstructWrongPass(): void
{
$this->expectException(ClientException::class);
$obj = new Client([
'user' => $this->router['user'],
new Client([
'user' => $this->config['user'],
'pass' => 'admin2',
'host' => $this->router['host'],
'host' => $this->config['host'],
'attempts' => 2
]);
}
/**
* @expectedException ClientException
*/
public function test__constructWrongNet(): void
public function testConstructWrongNet(): void
{
$this->expectException(ClientException::class);
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
new Client([
'user' => $this->config['user'],
'pass' => $this->config['pass'],
'host' => $this->config['host'],
'port' => 11111,
'attempts' => 2
]);
@ -162,41 +178,33 @@ class ClientTest extends TestCase
public function testQueryRead(): void
{
$config = new Config();
$config
->set('user', $this->router['user'])
->set('pass', $this->router['pass'])
->set('host', $this->router['host']);
$obj = new Client($config);
/*
* Build query with where
*/
$read = $obj->query('/system/package/print', ['name'])->read();
$this->assertCount(13, $read);
$read = $this->client->query('/system/package/print', ['name'])->read();
$this->assertNotEmpty($read);
$read = $obj->query('/system/package/print', ['.id', '*1'])->read();
$read = $this->client->query('/system/package/print', ['.id', '*1'])->read();
$this->assertCount(1, $read);
$read = $obj->query('/system/package/print', ['.id', '=', '*1'])->read();
$read = $this->client->query('/system/package/print', ['.id', '=', '*1'])->read();
$this->assertCount(1, $read);
$read = $obj->query('/system/package/print', [['name']])->read();
$this->assertCount(13, $read);
$read = $this->client->query('/system/package/print', [['name']])->read();
$this->assertNotEmpty($read);
$read = $obj->query('/system/package/print', [['.id', '*1']])->read();
$read = $this->client->query('/system/package/print', [['.id', '*1']])->read();
$this->assertCount(1, $read);
$read = $obj->query('/system/package/print', [['.id', '=', '*1']])->read();
$read = $this->client->query('/system/package/print', [['.id', '=', '*1']])->read();
$this->assertCount(1, $read);
/*
* Build query with operations
*/
$read = $obj->query('/interface/print', [
$read = $this->client->query('/interface/print', [
['type', 'ether'],
['type', 'vlan']
], '|')->read();
@ -207,68 +215,80 @@ class ClientTest extends TestCase
* Build query with tag
*/
$read = $obj->query('/system/package/print', null, null, 'zzzz')->read();
$this->assertCount(13, $read);
$read = $this->client->query('/system/package/print', null, null, 'zzzz')->read();
// $this->assertCount(13, $read);
$this->assertEquals('zzzz', $read[0]['tag']);
}
public function testReadAsIterator(): void
{
$obj = new Client($this->router);
$obj = $obj->write('/system/package/print')->readAsIterator();
$this->assertIsObject($obj);
$result = $this->client->query('/system/package/print')->readAsIterator();
$this->assertIsObject($result);
}
public function testWriteReadString(): void
{
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
]);
$readTrap = $obj->wr('/interface', false);
$readTrap = $this->client->query('/interface')->read(false);
$this->assertCount(3, $readTrap);
$this->assertEquals('!trap', $readTrap[0]);
}
public function testFatal(): void
{
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
]);
$readTrap = $obj->query('/quit')->read();
$readTrap = $this->client->query('/quit')->read();
$this->assertCount(2, $readTrap);
$this->assertEquals('!fatal', $readTrap[0]);
}
public function testQueryEx1(): void
public function queryExceptionDataProvider(): array
{
$this->expectException(ClientException::class);
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
]);
return [
// Wrong amount of parameters
['exception' => ClientException::class, 'endpoint' => '/quiet', 'attributes' => [[]]],
['exception' => ClientException::class, 'endpoint' => '/quiet', 'attributes' => [[], ['a', 'b', 'c']]],
['exception' => ClientException::class, 'endpoint' => '/quiet', 'attributes' => ['a', 'b', 'c', 'd']],
['exception' => ClientException::class, 'endpoint' => '/quiet', 'attributes' => [['a', 'b', 'c', 'd']]],
['exception' => ClientException::class, 'endpoint' => '/quiet', 'attributes' => [['a', 'b', 'c', 'd'], ['a', 'b', 'c']]],
// Wrong type of endpoint
['exception' => QueryException::class, 'endpoint' => 1, 'attributes' => null],
];
}
$obj->query('/quiet', ['a', 'b', 'c', 'd']);
/**
* @dataProvider queryExceptionDataProvider
*
* @param string $exception
* @param mixed $endpoint
* @param mixed $attributes
*
* @throws \RouterOS\Exceptions\ClientException
* @throws \RouterOS\Exceptions\ConfigException
* @throws \RouterOS\Exceptions\QueryException
*/
public function testQueryException(string $exception, $endpoint, $attributes): void
{
$this->expectException($exception);
$this->client->query($endpoint, $attributes);
}
public function testQueryEx2(): void
public function testExportMethod(): void
{
$this->expectException(ClientException::class);
if (!in_array(gethostname(), ['pasha-lt', 'pasha-pc'])) {
$this->markTestSkipped('Travis does not allow to use SSH protocol on testing stage');
}
$obj = new Client([
'user' => $this->router['user'],
'pass' => $this->router['pass'],
'host' => $this->router['host'],
]);
$result = $this->client->export();
$this->assertNotEmpty($result);
}
public function testExportQuery(): void
{
if (!in_array(gethostname(), ['pasha-lt', 'pasha-pc'])) {
$this->markTestSkipped('Travis does not allow to use SSH protocol on testing stage');
}
$obj->query('/quiet', [[]]);
$result = $this->client->query('/export');
$this->assertNotEmpty($result);
}
}

55
tests/ConfigTest.php

@ -8,58 +8,58 @@ use RouterOS\Exceptions\ConfigException;
class ConfigTest extends TestCase
{
public function test__construct()
public function testConstruct(): void
{
try {
$obj = new Config();
$this->assertInternalType('object', $obj);
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function testGetParameters()
public function testGetParameters(): void
{
$obj = new Config();
$params = $obj->getParameters();
$this->assertCount(5, $params);
$this->assertEquals($params['legacy'], false);
$this->assertEquals($params['ssl'], false);
$this->assertEquals($params['timeout'], 10);
$this->assertEquals($params['attempts'], 10);
$this->assertEquals($params['delay'], 1);
$this->assertCount(6, $params);
$this->assertEquals(false, $params['legacy']);
$this->assertEquals(false, $params['ssl']);
$this->assertEquals(10, $params['timeout']);
$this->assertEquals(10, $params['attempts']);
$this->assertEquals(1, $params['delay']);
}
public function testGetParameters2()
public function testGetParameters2(): void
{
$obj = new Config(['timeout' => 100]);
$params = $obj->getParameters();
$this->assertCount(5, $params);
$this->assertEquals($params['timeout'], 100);
$this->assertCount(6, $params);
$this->assertEquals(100, $params['timeout']);
}
public function testSet()
public function testSet(): void
{
$obj = new Config();
$obj->set('timeout', 111);
$params = $obj->getParameters();
$this->assertEquals($params['timeout'], 111);
$this->assertEquals(111, $params['timeout']);
}
public function testSetArr()
public function testSetArr(): void
{
$obj = new Config([
'timeout' => 111
]);
$params = $obj->getParameters();
$this->assertEquals($params['timeout'], 111);
$this->assertEquals(111, $params['timeout']);
}
public function testDelete()
public function testDelete(): void
{
$obj = new Config();
$obj->delete('timeout');
@ -68,7 +68,7 @@ class ConfigTest extends TestCase
$this->assertArrayNotHasKey('timeout', $params);
}
public function testDeleteEx()
public function testDeleteEx(): void
{
$this->expectException(ConfigException::class);
@ -76,7 +76,7 @@ class ConfigTest extends TestCase
$obj->delete('wrong');
}
public function testSetEx1()
public function testSetExceptionWrongType(): void
{
$this->expectException(ConfigException::class);
@ -84,7 +84,7 @@ class ConfigTest extends TestCase
$obj->set('delay', 'some string');
}
public function testSetEx2()
public function testSetExceptionWrongKey(): void
{
$this->expectException(ConfigException::class);
@ -92,27 +92,26 @@ class ConfigTest extends TestCase
$obj->set('wrong', 'some string');
}
public function testGet()
public function testGet(): void
{
$obj = new Config();
$test1 = $obj->get('legacy');
$this->assertEquals($test1, false);
$this->assertEquals(false, $test1);
$test2 = $obj->get('port');
$this->assertEquals($test2, 8728);
$this->assertEquals(8728, $test2);
$obj->set('port', 10000);
$test3 = $obj->get('port');
$this->assertEquals($test3, 10000);
$this->assertEquals(10000, $test3);
$obj->delete('port');
$obj->set('ssl', true);
$test3 = $obj->get('port');
$this->assertEquals($test3, 8729);
$this->assertEquals(8729, $test3);
}
public function testGetEx()
public function testGetEx(): void
{
$this->expectException(ConfigException::class);

4
tests/Helpers/ArrayHelperTest.php

@ -7,7 +7,7 @@ use RouterOS\Helpers\ArrayHelper;
class ArrayHelperTest extends TestCase
{
public function testCheckIfKeyNotExist()
public function testCheckIfKeyNotExist(): void
{
$test1 = ArrayHelper::checkIfKeyNotExist(1, [0 => 'a', 1 => 'b', 2 => 'c']);
$this->assertFalse($test1);
@ -16,7 +16,7 @@ class ArrayHelperTest extends TestCase
$this->assertTrue($test2);
}
public function testCheckIfKeysNotExist()
public function testCheckIfKeysNotExist(): void
{
$test1 = ArrayHelper::checkIfKeysNotExist([1, 2], [0 => 'a', 1 => 'b', 2 => 'c']);
$this->assertTrue($test1);

5
tests/Helpers/BinaryStringHelperTest.php

@ -16,8 +16,11 @@ class BinaryStringHelperTest extends TestCase
/**
* @dataProvider IntegerToNBOBinaryStringProvider
* @covers ::IntegerToNBOBinaryString
*
* @param $value
* @param $expected
*/
public function test__IntegerToNBOBinaryString($value, $expected)
public function testIntegerToNBOBinaryString($value, $expected): void
{
$this->assertEquals($expected, BinaryStringHelper::IntegerToNBOBinaryString($value));
}

2
tests/Helpers/TypeHelperTest.php

@ -7,7 +7,7 @@ use RouterOS\Helpers\TypeHelper;
class TypeHelperTest extends TestCase
{
public function testCheckIfTypeMismatch()
public function testCheckIfTypeMismatch(): void
{
$test1 = TypeHelper::checkIfTypeMismatch(gettype(true), gettype(false));
$this->assertFalse($test1);

62
tests/Laravel/ServiceProviderTests.php

@ -0,0 +1,62 @@
<?php
namespace RouterOS\Tests\Laravel;
use RouterOS\Config;
use RouterOS\Laravel\Wrapper;
class ServiceProviderTests extends TestCase
{
private $client = [
"__construct",
"query",
"read",
"readAsIterator",
"parseResponse",
"connect",
"export",
"getSocket",
"q",
"r",
"ri",
"qr",
"qri",
];
public function testAbstractsAreLoaded(): void
{
$manager = app(Wrapper::class);
$this->assertInstanceOf(Wrapper::class, $manager);
}
public function testConfig(): void
{
$config = \RouterOS::config([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin'
]);
$this->assertInstanceOf(Config::class, $config);
$params = $config->getParameters();
$this->assertArrayHasKey('host', $params);
$this->assertArrayHasKey('user', $params);
$this->assertArrayHasKey('pass', $params);
$this->assertArrayHasKey('ssl', $params);
$this->assertArrayHasKey('legacy', $params);
$this->assertArrayHasKey('timeout', $params);
$this->assertArrayHasKey('attempts', $params);
$this->assertArrayHasKey('delay', $params);
}
public function testClient(): void
{
$client = \RouterOS::client([
'host' => '192.168.1.3',
'user' => 'admin',
'pass' => 'admin'
], false);
$this->assertEquals(get_class_methods($client), $this->client);
}
}

35
tests/Laravel/TestCase.php

@ -0,0 +1,35 @@
<?php
namespace RouterOS\Tests\Laravel;
use RouterOS\Laravel\Facade;
use RouterOS\Laravel\ServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
/**
* Class TestCase
*
* @package Tests
*/
abstract class TestCase extends Orchestra
{
/**
* @inheritdoc
*/
protected function getPackageProviders($app): array
{
return [
ServiceProvider::class,
];
}
/**
* @inheritdoc
*/
protected function getPackageAliases($app): array
{
return [
'RouterOS' => Facade::class,
];
}
}

30
tests/QueryTest.php

@ -8,33 +8,33 @@ use RouterOS\Query;
class QueryTest extends TestCase
{
public function test__construct(): void
public function testConstruct(): void
{
try {
$obj = new Query('test');
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__construct_arr(): void
public function testConstructArr(): void
{
try {
$obj = new Query('test', ['line1', 'line2', 'line3']);
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
public function test__construct_arr2(): void
public function testConstructArr2(): void
{
try {
$obj = new Query(['test', 'line1', 'line2', 'line3']);
$this->assertIsObject($obj);
} catch (\Exception $e) {
$this->assertContains('Must be initialized ', $e->getMessage());
$this->assertStringContainsString('Must be initialized ', $e->getMessage());
}
}
@ -42,14 +42,14 @@ class QueryTest extends TestCase
{
$obj = new Query('test');
$test = $obj->getEndpoint();
$this->assertEquals($test, 'test');
$this->assertEquals('test', $test);
}
public function testGetEndpoint2(): void
{
$obj = new Query(['zzz', 'line1', 'line2', 'line3']);
$test = $obj->getEndpoint();
$this->assertEquals($test, 'zzz');
$this->assertEquals('zzz', $test);
}
public function testGetEndpointEx(): void
@ -65,7 +65,7 @@ class QueryTest extends TestCase
$obj = new Query('test');
$obj->setEndpoint('zzz');
$test = $obj->getEndpoint();
$this->assertEquals($test, 'zzz');
$this->assertEquals('zzz', $test);
}
public function testGetAttributes(): void
@ -104,6 +104,18 @@ class QueryTest extends TestCase
$this->assertEquals($attrs[1], '?key2=value2');
}
public function testEqual(): void
{
$obj = new Query('test');
$obj->equal('key1', 'value1');
$obj->equal('key2', 'value2');
$attrs = $obj->getAttributes();
$this->assertCount(2, $attrs);
$this->assertEquals($attrs[1], '=key2=value2');
}
public function testTag(): void
{
$obj = new Query('/test/test');

39
tests/ResponseIteratorTest.php

@ -4,38 +4,34 @@ namespace RouterOS\Tests;
use PHPUnit\Framework\TestCase;
use RouterOS\Client;
use RouterOS\ResponseIterator;
class ResponseIteratorTest extends TestCase
{
public function test__construct()
/**
* @var \RouterOS\Client
*/
private $client;
public function setUp(): void
{
$obj = new Client([
$this->client = new Client([
'user' => getenv('ROS_USER'),
'pass' => getenv('ROS_PASS'),
'host' => getenv('ROS_HOST'),
]);
$obj = $obj->write('/system/package/print')->readAsIterator();
$this->assertIsObject($obj);
}
public function testReadWrite()
public function testReadWrite(): void
{
$obj = new Client([
'user' => getenv('ROS_USER'),
'pass' => getenv('ROS_PASS'),
'host' => getenv('ROS_HOST'),
]);
$readTrap = $obj->write('/system/package/print')->readAsIterator();
// Read from RAW
$this->assertCount(13, $readTrap);
$readTrap = $this->client->query('/system/logging/print')->readAsIterator();
$this->assertNotEmpty($readTrap);
$readTrap = $obj->write('/ip/address/print')->readAsIterator();
$readTrap = $this->client->query('/ip/address/print')->readAsIterator();
$this->assertCount(1, $readTrap);
$this->assertEquals('ether1', $readTrap[0]['interface']);
$readTrap = $obj->write('/system/package/print')->readAsIterator();
$readTrap = $this->client->query('/system/logging/print')->readAsIterator();
$key = $readTrap->key();
$this->assertEquals(0, $key);
$current = $readTrap->current();
@ -62,14 +58,9 @@ class ResponseIteratorTest extends TestCase
public function testSerialize(): void
{
$obj = new Client([
'user' => getenv('ROS_USER'),
'pass' => getenv('ROS_PASS'),
'host' => getenv('ROS_HOST'),
]);
$read = $obj->write('/queue/simple/print')->readAsIterator();
$read = $this->client->query('/queue/simple/print')->readAsIterator();
$serialize = $read->serialize();
$this->assertEquals('a:1:{i:0;a:1:{i:0;s:5:"!done";}}', $serialize);
}

66
tests/Streams/ResourceStreamTest.php

@ -2,8 +2,10 @@
namespace RouterOS\Tests\Streams;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Constraint\IsType;
use RouterOS\Exceptions\StreamException;
use RouterOS\Streams\ResourceStream;
/**
@ -17,14 +19,14 @@ class ResourceStreamTest extends TestCase
* Test that constructor throws an InvalidArgumentException on bad parameter type
*
* @covers ::__construct
* @expectedException \InvalidArgumentException
* @dataProvider constructNotResourceProvider
*
* @param $notResource
*/
public function test__constructNotResource($notResource)
public function testConstructNotResource($notResource): void
{
$this->expectException(InvalidArgumentException::class);
new ResourceStream($notResource);
}
@ -56,12 +58,17 @@ class ResourceStreamTest extends TestCase
* @param resource $resource Cannot typehint, PHP refuse it
* @param bool $closeResource shall we close the resource ?
*/
public function test_construct($resource, bool $closeResource = true)
public function testConstruct($resource, bool $closeResource = true): void
{
$resourceStream = new ResourceStream($resource);
$resourceStream = new class($resource) extends ResourceStream {
public function getStream()
{
return $this->stream;
}
};
$stream = $this->getObjectAttribute($resourceStream, 'stream');
$this->assertInternalType(IsType::TYPE_RESOURCE, $stream);
$stream = $resourceStream->getStream();
$this->assertIsResource($stream);
if ($closeResource) {
fclose($resource);
@ -89,12 +96,13 @@ class ResourceStreamTest extends TestCase
* @covers ::read
* @dataProvider readProvider
*
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param string $expected the result we should have
* @throws \RouterOS\Exceptions\StreamException
* @throws \InvalidArgumentException
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param string $expected the result we should have
*
* @throws \RouterOS\Exceptions\StreamException
* @throws \InvalidArgumentException
*/
public function test__read(ResourceStream $stream, string $expected)
public function testRead(ResourceStream $stream, string $expected): void
{
$this->assertSame($expected, $stream->read(strlen($expected)));
}
@ -115,15 +123,16 @@ class ResourceStreamTest extends TestCase
*
* @covers ::read
* @dataProvider readBadLengthProvider
* @expectedException \InvalidArgumentException
*
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param int $length
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param int $length
*
* @throws \RouterOS\Exceptions\StreamException
* @throws \InvalidArgumentException
*/
public function test__readBadLength(ResourceStream $stream, int $length)
public function testReadBadLength(ResourceStream $stream, int $length): void
{
$this->expectException(InvalidArgumentException::class);
$stream->read($length);
}
@ -143,13 +152,13 @@ class ResourceStreamTest extends TestCase
*
* @covers ::read
* @dataProvider readBadResourceProvider
* @expectedException \RouterOS\Exceptions\StreamException
*
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param int $length
* @param ResourceStream $stream Cannot typehint, PHP refuse it
* @param int $length
*/
public function test__readBadResource(ResourceStream $stream, int $length)
public function testReadBadResource(ResourceStream $stream, int $length): void
{
$this->expectException(StreamException::class);
$stream->read($length);
}
@ -169,11 +178,12 @@ class ResourceStreamTest extends TestCase
* @covers ::write
* @dataProvider writeProvider
*
* @param ResourceStream $stream to test
* @param string $toWrite the writed string
* @param ResourceStream $stream to test
* @param string $toWrite the writed string
*
* @throws \RouterOS\Exceptions\StreamException
*/
public function test__write(ResourceStream $stream, string $toWrite)
public function testWrite(ResourceStream $stream, string $toWrite): void
{
$this->assertEquals(strlen($toWrite), $stream->write($toWrite));
}
@ -193,13 +203,13 @@ class ResourceStreamTest extends TestCase
*
* @covers ::write
* @dataProvider writeBadResourceProvider
* @expectedException \RouterOS\Exceptions\StreamException
*
* @param ResourceStream $stream to test
* @param string $toWrite the written string
*/
public function test__writeBadResource(ResourceStream $stream, string $toWrite)
public function testWriteBadResource(ResourceStream $stream, string $toWrite): void
{
$this->expectException(StreamException::class);
$stream->write($toWrite);
}
@ -219,12 +229,12 @@ class ResourceStreamTest extends TestCase
*
* @covers ::close
* @dataProvider doubleCloseProvider
* @expectedException \RouterOS\Exceptions\StreamException
*
* @param ResourceStream $stream to test
*/
public function test_doubleClose(ResourceStream $stream)
public function testDoubleClose(ResourceStream $stream): void
{
$this->expectException(StreamException::class);
$stream->close();
$stream->close();
}
@ -242,13 +252,13 @@ class ResourceStreamTest extends TestCase
* @covers ::close
* @covers ::write
* @dataProvider writeClosedResourceProvider
* @expectedException \RouterOS\Exceptions\StreamException
*
* @param ResourceStream $stream to test
* @param string $toWrite the written string
*/
public function test_close(ResourceStream $stream, string $toWrite)
public function testClose(ResourceStream $stream, string $toWrite)
{
$this->expectException(StreamException::class);
$stream->close();
$stream->write($toWrite);
}

43
tests/Streams/StringStreamTest.php

@ -19,9 +19,9 @@ class StringStreamTest extends TestCase
* @covers ::__construct
* @dataProvider constructProvider
*
* @param string $string
* @param string $string
*/
public function test__construct(string $string)
public function testConstruct(string $string): void
{
$this->assertInstanceOf(StringStream::class, new StringStream($string));
}
@ -36,19 +36,18 @@ class StringStreamTest extends TestCase
];
}
/**
* Test that write function returns the effective written bytes
*
* @covers ::write
* @dataProvider writeProvider
*
* @param string $string the string to write
* @param int|null $length the count if bytes to write
* @param int $expected the number of bytes that must be writen
* @param string $string the string to write
* @param int|null $length the count if bytes to write
* @param int $expected the number of bytes that must be writen
*/
public function test__write(string $string, $length, int $expected)
public function testWrite(string $string, $length, int $expected): void
{
$stream = new StringStream('Does not matters');
if (null === $length) {
@ -79,10 +78,10 @@ class StringStreamTest extends TestCase
/**
* @covers ::write
* @expectedException \InvalidArgumentException
*/
public function test__writeWithNegativeLength()
public function testWriteWithNegativeLength(): void
{
$this->expectException(\InvalidArgumentException::class);
$stream = new StringStream('Does not matters');
$stream->write('PLOP', -1);
}
@ -92,7 +91,7 @@ class StringStreamTest extends TestCase
*
* @throws \RouterOS\Exceptions\StreamException
*/
public function test__read()
public function testRead(): void
{
$stream = new StringStream('123456789');
@ -105,12 +104,11 @@ class StringStreamTest extends TestCase
}
/**
* @expectedException \InvalidArgumentException
*
* @throws \RouterOS\Exceptions\StreamException
*/
public function test__readBadLength()
public function testReadBadLength(): void
{
$this->expectException(\InvalidArgumentException::class);
$stream = new StringStream('123456789');
$stream->read(-1);
}
@ -118,14 +116,15 @@ class StringStreamTest extends TestCase
/**
* @covers ::read
* @dataProvider readWhileEmptyProvider
* @expectedException \RouterOS\Exceptions\StreamException
*
* @param StringStream $stream
* @param int $length
* @throws \RouterOS\Exceptions\StreamException
* @param StringStream $stream
* @param int $length
*
* @throws \RouterOS\Exceptions\StreamException
*/
public function test__readWhileEmpty(StringStream $stream, int $length)
public function testReadWhileEmpty(StringStream $stream, int $length): void
{
$this->expectException(\RouterOS\Exceptions\StreamException::class);
$stream->read($length);
}
@ -133,7 +132,7 @@ class StringStreamTest extends TestCase
* @return \Generator
* @throws StreamException
*/
public function readWhileEmptyProvider()
public function readWhileEmptyProvider(): ?\Generator
{
$stream = new StringStream('123456789');
$stream->read(9);
@ -148,11 +147,9 @@ class StringStreamTest extends TestCase
yield [$stream, 1];
}
/**
* @expectedException \RouterOS\Exceptions\StreamException
*/
public function testReadClosed()
public function testReadClosed(): void
{
$this->expectException(\RouterOS\Exceptions\StreamException::class);
$stream = new StringStream('123456789');
$stream->close();
$stream->read(1);

Loading…
Cancel
Save