diff --git a/README.md b/README.md index 9641bea..9f8aae9 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ foreach ($stringy as $pos => $char) { // array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř') ``` -It also implements the `countable` interface, enabling the use of `count()` to +It also implements the `Countable` interface, enabling the use of `count()` to retrieve the number of characters in the string, given the encoding: ``` php diff --git a/src/Stringy/Stringy.php b/src/Stringy/Stringy.php index f8ca1dd..020f817 100644 --- a/src/Stringy/Stringy.php +++ b/src/Stringy/Stringy.php @@ -2,7 +2,7 @@ namespace Stringy; -class Stringy implements \Countable, \IteratorAggregate +class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess { private $str; @@ -67,7 +67,7 @@ class Stringy implements \Countable, \IteratorAggregate /** * Returns the length of the string, implementing the countable interface. * - * @return int The number of characters in the string, given the encoding + * @return int The number of characters in the string, given the encoding */ public function count() { @@ -80,13 +80,88 @@ class Stringy implements \Countable, \IteratorAggregate * in the multibyte string. This allows the use of foreach with instances * of Stringy\Stringy. * - * @return \ArrayIterator An iterator for the characters in the string + * @return \ArrayIterator An iterator for the characters in the string */ public function getIterator() { return new \ArrayIterator($this->chars()); } + /** + * Returns whether or not a character exists at the given index. Implements + * part of the ArrayAccess interface. + * + * @param mixed $offset The index to check + * @return boolean Whether or not the index exists + */ + public function offsetExists($offset) { + return ($this->length() > (int) $offset); + } + + /** + * Returns the character at the given index, otherwise null if none exists. + * Implements part of the ArrayAccess interface. + * + * @param mixed $offset The index from which to retrieve the char + * @return mixed The character if it exists, else null + */ + public function offsetGet($offset) { + $offset = (int) $offset; + if ($this->length() <= $offset) { + return null; + } + + return $this->at($offset); + } + + /** + * Sets the character at the given offset. The value to set is truncated + * to a single character. If the offset is null or exceeds the length + * of the string, it is appended to the end. Implements part of the + * ArrayAccess interface. + * + * @param mixed $offset The index at which to replace the character + * @param mixed $value Value to set + */ + public function offsetSet($offset, $value) { + $length = $this->length(); + if ($offset === null || $length <= (int) $offset) { + $this->str .= $value; + + return; + } + + $offset = (int) $offset; + $value = mb_substr($value, 0, 1, $this->encoding); + $start = mb_substr($this->str, 0, $offset, $this->encoding); + $end = mb_substr($this->str, $offset + 1, $length, $this->encoding); + + $this->str = $start . $value . $end; + } + + /** + * Deletes character at the given offset. Implements part of the + * ArrayAccess interface. + * + * @param mixed $offset The index at which to delete the character + */ + public function offsetUnset($offset) { + $length = $this->length(); + if ($offset === null || $length <= (int) $offset) { + return; + } + + $offset = (int) $offset; + if ($offset === $length - 1) { + $this->str = $this->substr(0, $length - 1)->str; + } + + $start = mb_substr($this->str, 0, $offset, $this->encoding); + $end = mb_substr($this->str, $offset + 1, $length, $this->encoding); + + $this->str = $start . $end; + } + /** * Returns an array consisting of the characters in the string. * diff --git a/tests/Stringy/StringyTest.php b/tests/Stringy/StringyTest.php index 2128e87..e888d0c 100644 --- a/tests/Stringy/StringyTest.php +++ b/tests/Stringy/StringyTest.php @@ -95,6 +95,81 @@ class StringyTestCase extends CommonTest $this->assertEquals(array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř'), $keyValResult); } + public function testOffsetExists() + { + $stringy = S::create('fòô', 'UTF-8'); + + $this->assertTrue($stringy->offsetExists(0)); + $this->assertTrue($stringy->offsetExists(2)); + $this->assertFalse($stringy->offsetExists(3)); + + $this->assertTrue(isset($stringy[2])); + $this->assertFalse(isset($stringy[3])); + } + + public function testOffsetGet() + { + $stringy = S::create('fòô', 'UTF-8'); + + $this->assertEquals('f', $stringy->offsetGet(0)); + $this->assertEquals('ô', $stringy->offsetGet(2)); + $this->assertEquals(null, $stringy->offsetGet(3)); + + $this->assertEquals('ô', $stringy[2]); + $this->assertEquals(null, $stringy[3]); + } + + /** + * @dataProvider offsetSetProvider() + */ + public function testOffsetSet($expected, $offset, $value) + { + $stringy = S::create('fòô', 'UTF-8'); + $stringy->offsetSet($offset, $value); + $this->assertEquals($expected, (string) $stringy); + + $stringy = S::create('fòô', 'UTF-8'); + $stringy[$offset] = $value; + $this->assertEquals($expected, (string) $stringy); + } + + public function offsetSetProvider() + { + return array( + array('ôòô', 0, 'ô'), + array('fòo', 2, 'o'), + array('fòôô', 3, 'ô'), + array('fòôô', 8, 'ô'), + array('fòôô', null, 'ô'), + array('fô', 1, ''), + array('fbô', 1, 'bar') + ); + } + + /** + * @dataProvider offsetUnsetProvider() + */ + public function testOffsetUnset($expected, $offset) + { + $stringy = S::create('fòô', 'UTF-8'); + $stringy->offsetUnset($offset); + $this->assertEquals($expected, (string) $stringy); + + $stringy = S::create('fòô', 'UTF-8'); + unset($stringy[$offset]); + $this->assertEquals($expected, (string) $stringy); + } + + public function offsetUnsetProvider() + { + return array( + array('òô', 0), + array('fô', 1), + array('fò', 2), + array('fòô', 3) + ); + } + /** * @dataProvider charsProvider() */