diff --git a/.travis.yml b/.travis.yml index ef28219..41a6d8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ addons: - expect php: -- '7.0' - '7.1' - '7.2' - '7.3' diff --git a/README.md b/README.md index 5d04e81..11ae41a 100644 --- a/README.md +++ b/README.md @@ -116,16 +116,18 @@ var_dump($response); $response = $client->wri($query); var_dump($response); -// Export every row using foreach. -foreach ($response as $row){ - var_export($row); +// The following for loop allows you to skip elements for which +// $iterator->current() throws an exception, rather than breaking +// the loop. +for ($response->rewind(); $response->valid(); $response->next()) { + try { + $value = $response->current(); + } catch (Exception $exception) { + continue; + } + + # ... } - -// Work with resource as with ordinary array -end($response); -key($response); -current($response); -stat($response); ``` ### How to configure the client diff --git a/src/Client.php b/src/Client.php index b230a2e..d65077b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,7 +6,6 @@ use RouterOS\Exceptions\ClientException; use RouterOS\Exceptions\ConfigException; use RouterOS\Exceptions\QueryException; use RouterOS\Helpers\ArrayHelper; -use RouterOS\Interfaces\ClientInterface; /** * Class Client for RouterOS management diff --git a/src/ResponseIterator.php b/src/ResponseIterator.php index 8f8e4cc..6ff9028 100644 --- a/src/ResponseIterator.php +++ b/src/ResponseIterator.php @@ -2,6 +2,11 @@ namespace RouterOS; +use \Iterator, + \ArrayAccess, + \Countable, + \Serializable; + /** * This class was created by memory save reasons, it convert response * from RouterOS to readable array in safe way. @@ -15,7 +20,7 @@ namespace RouterOS; * @link https://github.com/arily/RouterOSResponseArray * @since 1.0.0 */ -class ResponseIterator implements \Iterator, \ArrayAccess, \Countable +class ResponseIterator implements Iterator, ArrayAccess, Countable, Serializable { /** * List of parser results from array @@ -36,7 +41,7 @@ class ResponseIterator implements \Iterator, \ArrayAccess, \Countable * * @var int */ - private $current; + private $current = 0; /** * Object of main client @@ -95,6 +100,14 @@ class ResponseIterator implements \Iterator, \ArrayAccess, \Countable } /** + * Previous value + */ + public function prev() + { + --$this->current; + } + + /** * Return the current element * * @return mixed @@ -105,8 +118,14 @@ class ResponseIterator implements \Iterator, \ArrayAccess, \Countable return $this->parsed[$this->current]; } - if (isset($this->raw[$this->current])) { - return $this->client->parseResponse($this->raw[$this->current])[0]; + if ($this->valid()) { + + if (!isset($this->parsed[$this->current])) { + $value = $this->client->parseResponse($this->raw[$this->current])[0]; + $this->offsetSet($this->current, $value); + } + + return $this->parsed[$this->current]; } return null; @@ -207,4 +226,24 @@ class ResponseIterator implements \Iterator, \ArrayAccess, \Countable return false; } + + /** + * String representation of object + * + * @return string + */ + public function serialize(): string + { + return serialize($this->raw); + } + + /** + * Constructs the object + * + * @param string $serialized + */ + public function unserialize($serialized) + { + $this->raw = unserialize($serialized, null); + } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index f6551c3..0895f8b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -12,21 +12,21 @@ use RouterOS\Exceptions\ClientException; class ClientTest extends TestCase { - public function test__construct() + public function test__construct(): void { try { $config = new Config(); $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); $obj = new Client($config); - $this->assertInternalType('object', $obj); + $this->assertIsObject($obj); $socket = $obj->getSocket(); - $this->assertInternalType('resource', $socket); + $this->assertIsResource($socket); } catch (\Exception $e) { $this->assertContains('Must be initialized ', $e->getMessage()); } } - public function test__construct2() + public function test__construct2(): void { try { $config = new Config([ @@ -35,15 +35,15 @@ class ClientTest extends TestCase 'host' => getenv('ROS_HOST') ]); $obj = new Client($config); - $this->assertInternalType('object', $obj); + $this->assertIsObject($obj); $socket = $obj->getSocket(); - $this->assertInternalType('resource', $socket); + $this->assertIsResource($socket); } catch (\Exception $e) { $this->assertContains('Must be initialized ', $e->getMessage()); } } - public function test__construct3() + public function test__construct3(): void { try { $obj = new Client([ @@ -51,15 +51,15 @@ class ClientTest extends TestCase 'pass' => getenv('ROS_PASS'), 'host' => getenv('ROS_HOST') ]); - $this->assertInternalType('object', $obj); + $this->assertIsObject($obj); $socket = $obj->getSocket(); - $this->assertInternalType('resource', $socket); + $this->assertIsResource($socket); } catch (\Exception $e) { $this->assertContains('Must be initialized ', $e->getMessage()); } } - public function test__constructEx() + public function test__constructEx(): void { $this->expectException(ConfigException::class); @@ -69,14 +69,17 @@ class ClientTest extends TestCase ]); } - public function test__constructLegacy() + public function test__constructLegacy(): void { try { - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS')) - ->set('host', getenv('ROS_HOST'))->set('port', (int) getenv('ROS_PORT_MODERN'))->set('legacy', true); - $obj = new Client($config); - $this->assertInternalType('object', $obj); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + 'port' => (int) getenv('ROS_PORT_MODERN'), + 'legacy' => true + ]); + $this->assertIsObject($obj); } catch (\Exception $e) { $this->assertContains('Must be initialized ', $e->getMessage()); } @@ -87,42 +90,52 @@ class ClientTest extends TestCase * * login() method recognise legacy router response and swap to legacy mode */ - public function test__constructLegacy2() + public function test__constructLegacy2(): void { try { - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS')) - ->set('host', getenv('ROS_HOST'))->set('port', (int) getenv('ROS_PORT_MODERN'))->set('legacy', false); - $obj = new Client($config); - $this->assertInternalType('object', $obj); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + 'port' => (int) getenv('ROS_PORT_MODERN'), + 'legacy' => false + ]); + $this->assertIsObject($obj); } catch (\Exception $e) { $this->assertContains('Must be initialized ', $e->getMessage()); } } - public function test__constructWrongPass() + public function test__constructWrongPass(): void { $this->expectException(ClientException::class); - $config = (new Config())->set('attempts', 2); - $config->set('user', getenv('ROS_USER'))->set('pass', 'admin2')->set('host', getenv('ROS_HOST')); - $obj = new Client($config); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => 'admin2', + 'host' => getenv('ROS_HOST'), + 'attempts' => 2 + ]); } /** * @expectedException ClientException */ - public function test__constructWrongNet() + public function test__constructWrongNet(): void { $this->expectException(ClientException::class); - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST'))->set('port', 11111); - $obj = new Client($config); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + 'port' => 11111, + 'attempts' => 2 + ]); } - public function testWriteRead() + public function testWriteRead(): void { $config = new Config(); $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); @@ -133,6 +146,11 @@ class ClientTest extends TestCase $this->assertCount(10, $readRaw); $this->assertEquals('=.id=*1', $readRaw[1]); + $query = new Query('/system/package/print'); + $read = $obj->write($query)->read(); + $this->assertCount(13, $read); + $this->assertEquals('advanced-tools', $read[12]['name']); + $query = new Query('/ip/address/print'); $readRaw = $obj->w($query)->read(false); $this->assertCount(10, $readRaw); @@ -154,47 +172,68 @@ class ClientTest extends TestCase $this->assertEquals('!trap', $readTrap[0]); } - public function testWriteReadString() + public function testReadAsIterator(): void { - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); - $obj = new Client($config); + $obj = 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 testWriteReadString(): void + { + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + ]); $readTrap = $obj->wr('/interface', false); $this->assertCount(3, $readTrap); $this->assertEquals('!trap', $readTrap[0]); } - public function testWriteReadArray() + public function testWriteReadArray(): void { - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); - $obj = new Client($config); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + ]); $readTrap = $obj->wr(['/interface'], false); $this->assertCount(3, $readTrap); $this->assertEquals('!trap', $readTrap[0]); } - public function testFatal() + public function testFatal(): void { - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); - $obj = new Client($config); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + ]); $readTrap = $obj->wr('/quit'); $this->assertCount(2, $readTrap); $this->assertEquals('!fatal', $readTrap[0]); } - public function testWriteEx() + public function testWriteEx(): void { $this->expectException(QueryException::class); - $config = new Config(); - $config->set('user', getenv('ROS_USER'))->set('pass', getenv('ROS_PASS'))->set('host', getenv('ROS_HOST')); - $obj = new Client($config); - $error = $obj->write($obj)->read(false); + $obj = new Client([ + 'user' => getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + ]); + + $obj->write($obj)->read(false); } } diff --git a/tests/ResponseIteratorTest.php b/tests/ResponseIteratorTest.php new file mode 100644 index 0000000..9ef7b5b --- /dev/null +++ b/tests/ResponseIteratorTest.php @@ -0,0 +1,80 @@ + getenv('ROS_USER'), + 'pass' => getenv('ROS_PASS'), + 'host' => getenv('ROS_HOST'), + ]); + + $obj = $obj->write('/system/package/print')->readAsIterator(); + $this->assertIsObject($obj); + } + + public function testReadWrite() + { + $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); + $this->assertEquals('advanced-tools', $readTrap[12]['name']); + // Read from parsed + $this->assertCount(13, $readTrap); + $this->assertEquals('advanced-tools', $readTrap[12]['name']); + + $readTrap = $obj->write('/ip/address/print')->readAsIterator(); + $this->assertCount(1, $readTrap); + $this->assertEquals('ether1', $readTrap[0]['interface']); + + $readTrap = $obj->write('/system/package/print')->readAsIterator(); + $key = $readTrap->key(); + $this->assertEquals(0, $key); + $current = $readTrap->current(); + $this->assertEquals('*1', $current['.id']); + + $readTrap->next(); + $key = $readTrap->key(); + $this->assertEquals(1, $key); + $current = $readTrap->current(); + $this->assertEquals('*2', $current['.id']); + + $readTrap->prev(); + $key = $readTrap->key(); + $this->assertEquals(0, $key); + $current = $readTrap->current(); + $this->assertEquals('*1', $current['.id']); + + $readTrap->prev(); // Check if key is not exist + $key = $readTrap->key(); + $this->assertEquals(-1, $key); + $current = $readTrap->current(); + $this->assertNull($current); + } + + 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(); + $serialize = $read->serialize(); + $this->assertEquals('a:1:{i:0;a:1:{i:0;s:5:"!done";}}', $serialize); + } + +}