From 3f5b6a24137435febb08b106c7e83f3f3b521d15 Mon Sep 17 00:00:00 2001 From: "Daniel St. Jules" Date: Thu, 6 Feb 2014 18:27:21 -0500 Subject: [PATCH] Update ArrayAccess interface implementation, add details to readme --- README.md | 29 ++++++++----- src/Stringy/Stringy.php | 81 ++++++++++++++++------------------- tests/Stringy/StringyTest.php | 74 +++++++++++++------------------- 3 files changed, 83 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 9f8aae9..b1a121e 100644 --- a/README.md +++ b/README.md @@ -126,32 +126,39 @@ echo S::swapCase($string, 'UTF-8'); // 'fÒÔ bÀŘ' ## Implemented Interfaces -`Stringy\Stringy` implements the `IteratorAggregate` interface. This allows you -to use `foreach` with an instance of the class: +`Stringy\Stringy` implements the `IteratorAggregate` interface, meaning that +`foreach` can be used with an instance of the class: ``` php $stringy = S::create('Fòô Bàř', 'UTF-8'); - foreach ($stringy as $char) { echo $char; } // 'Fòô Bàř' - -$array = array(); -foreach ($stringy as $pos => $char) { - $array[$pos] = $char; -} -// array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř') ``` -It also implements the `Countable` interface, enabling the use of `count()` to -retrieve the number of characters in the string, given the encoding: +It implements the `Countable` interface, enabling the use of `count()` to +retrieve the number of characters in the string: ``` php $stringy = S::create('Fòô', 'UTF-8'); count($stringy); // 3 ``` +Furthermore, the `ArrayAccess` interface has been implemented. As a result, +`isset()` can be used to check if a character at a specific index exists. And +since `Stringy\Stringy` is immutable, any call to `offsetSet` or `offsetUnset` +will throw an exception. `offsetGet` has been implemented, however, and accepts +both positive and negative indexes. Invalid indexes result in an +`OutOfBoundsException`. + +``` php +$stringy = S::create('Bàř', 'UTF-8'); +echo $stringy[2]; // 'ř' +echo $stringy[-2]; // 'à' +isset($stringy[-4]); // false +``` + ## Methods In the list below, any static method other than S::create refers to a method in diff --git a/src/Stringy/Stringy.php b/src/Stringy/Stringy.php index 020f817..3fb33ab 100644 --- a/src/Stringy/Stringy.php +++ b/src/Stringy/Stringy.php @@ -88,78 +88,69 @@ class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess } /** - * Returns whether or not a character exists at the given index. Implements + * Returns whether or not a character exists at an index. Offsets may be + * negative to count from the last character in the string. 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); + $length = $this->length(); + $offset = (int) $offset; + + if ($offset >= 0) { + return ($length > $offset); + } + + return ($length >= abs($offset)); } /** - * Returns the character at the given index, otherwise null if none exists. - * Implements part of the ArrayAccess interface. + * Returns the character at the given index. Offsets may be negative to + * count from the last character in the string. Implements part of the + * ArrayAccess interface, and throws an OutOfBoundsException if the index + * does not exist. * - * @param mixed $offset The index from which to retrieve the char - * @return mixed The character if it exists, else null + * @param mixed $offset The index from which to retrieve the char + * @return mixed The character at the specified index + * @throws \OutOfBoundsException If the positive or negative offset does + * not exist */ public function offsetGet($offset) { $offset = (int) $offset; - if ($this->length() <= $offset) { - return null; + $length = $this->length(); + + if (($offset >= 0 && $length <= $offset) || $length < abs($offset)) { + throw new \OutOfBoundsException('No character exists at the index'); } - return $this->at($offset); + return mb_substr($this->str, $offset, 1, $this->encoding); } /** - * 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. + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. * - * @param mixed $offset The index at which to replace the character - * @param mixed $value Value to set + * @param mixed $offset The index of the character + * @param mixed $value Value to set + * @throws \Exception When called */ 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; + // Stringy is immutable, cannot directly set char + throw new \Exception('Stringy object is immutable, cannot modify char'); } /** - * Deletes character at the given offset. Implements part of the - * ArrayAccess interface. + * Implements part of the ArrayAccess interface, but throws an exception + * when called. This maintains the immutability of Stringy objects. * - * @param mixed $offset The index at which to delete the character + * @param mixed $offset The index of the character + * @throws \Exception When called */ 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; + // Don't allow directly modifying the string + throw new \Exception('Stringy object is immutable, cannot unset char'); } /** diff --git a/tests/Stringy/StringyTest.php b/tests/Stringy/StringyTest.php index e888d0c..5690370 100644 --- a/tests/Stringy/StringyTest.php +++ b/tests/Stringy/StringyTest.php @@ -95,16 +95,26 @@ class StringyTestCase extends CommonTest $this->assertEquals(array('F', 'ò', 'ô', ' ', 'B', 'à', 'ř'), $keyValResult); } - public function testOffsetExists() + /** + * @dataProvider offsetExistsProvider() + */ + public function testOffsetExists($expected, $offset) { $stringy = S::create('fòô', 'UTF-8'); + $this->assertEquals($expected, $stringy->offsetExists($offset)); + $this->assertEquals($expected, isset($stringy[$offset])); + } - $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 offsetExistsProvider() + { + return array( + array(true, 0), + array(true, 2), + array(false, 3), + array(true, -1), + array(true, -3), + array(false, -4) + ); } public function testOffsetGet() @@ -113,61 +123,35 @@ class StringyTestCase extends CommonTest $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() + * @expectedException \OutOfBoundsException */ - public function testOffsetSet($expected, $offset, $value) + public function testOffsetGetOutOfBounds() { $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') - ); + $test = $stringy[3]; } /** - * @dataProvider offsetUnsetProvider() + * @expectedException \Exception */ - public function testOffsetUnset($expected, $offset) + public function testOffsetSet() { $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); + $stringy[1] = 'invalid'; } - public function offsetUnsetProvider() + /** + * @expectedException \Exception + */ + public function testOffsetUnset() { - return array( - array('òô', 0), - array('fô', 1), - array('fò', 2), - array('fòô', 3) - ); + $stringy = S::create('fòô', 'UTF-8'); + unset($stringy[1]); } /**