From 10ce563c2c0d335a3f18a480515b68f26ad18eae Mon Sep 17 00:00:00 2001 From: Nejc Cotic Date: Tue, 22 Oct 2024 14:19:27 +0200 Subject: [PATCH 1/2] update stringArray --- Tests/StringArrayTest.php | 113 +++++++++++++++++++++++++++ src/Composite/Arrays/StringArray.php | 103 +++++++++++++++++++++++- 2 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 Tests/StringArrayTest.php diff --git a/Tests/StringArrayTest.php b/Tests/StringArrayTest.php new file mode 100644 index 0000000..0c3df62 --- /dev/null +++ b/Tests/StringArrayTest.php @@ -0,0 +1,113 @@ +expectException(InvalidArgumentException::class); + new StringArray(['validString', 123]); // Invalid, second element is an integer + } + + /** + * Test that the constructor correctly assigns the array of strings. + */ + public function testConstructorAssignsValidValue() + { + $stringArray = new StringArray(['string1', 'string2']); + $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + } + + /** + * Test that adding a valid string works. + */ + public function testAddString() + { + $stringArray = new StringArray(['string1']); + $stringArray->add('string2'); + $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + } + + /** + * Test that adding a non-string value throws a TypeError. + */ + public function testAddNonStringThrowsException() + { + $stringArray = new StringArray(['string1']); + + $this->expectException(TypeError::class); // Expect TypeError instead of InvalidArgumentException + $stringArray->add(123); // Invalid, not a string + } + + /** + * Test that removing an existing string works. + */ + public function testRemoveString() + { + $stringArray = new StringArray(['string1', 'string2']); + $removed = $stringArray->remove('string1'); + + $this->assertTrue($removed); + $this->assertEquals(['string2'], $stringArray->getValue()); + } + + /** + * Test that removing a non-existing string returns false. + */ + public function testRemoveNonExistingString() + { + $stringArray = new StringArray(['string1', 'string2']); + $removed = $stringArray->remove('string3'); + + $this->assertFalse($removed); + $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + } + + /** + * Test that contains() works correctly. + */ + public function testContainsString() + { + $stringArray = new StringArray(['string1', 'string2']); + $this->assertTrue($stringArray->contains('string1')); + $this->assertFalse($stringArray->contains('string3')); + } + + /** + * Test that count() returns the correct number of elements. + */ + public function testCountStrings() + { + $stringArray = new StringArray(['string1', 'string2']); + $this->assertEquals(2, $stringArray->count()); + } + + /** + * Test that toString() returns the correct comma-separated string. + */ + public function testToString() + { + $stringArray = new StringArray(['string1', 'string2']); + $this->assertEquals('string1, string2', $stringArray->toString()); + } + + /** + * Test that clear() empties the array. + */ + public function testClearArray() + { + $stringArray = new StringArray(['string1', 'string2']); + $stringArray->clear(); + $this->assertEquals([], $stringArray->getValue()); + } +} diff --git a/src/Composite/Arrays/StringArray.php b/src/Composite/Arrays/StringArray.php index f11cd11..d4da046 100644 --- a/src/Composite/Arrays/StringArray.php +++ b/src/Composite/Arrays/StringArray.php @@ -1,20 +1,115 @@ value = $value; } - public function getValue(): array { + /** + * Get the array of string values. + * + * @return array + */ + public function getValue(): array + { return $this->value; } + + /** + * Add a new string to the array. + * + * @param string $string + * @return void + * @throws InvalidArgumentException + */ + public function add(string $string): void + { + $this->value[] = $string; + } + + /** + * Remove a string from the array. + * + * @param string $string + * @return bool True if the string was found and removed, false otherwise. + */ + public function remove(string $string): bool + { + $index = array_search($string, $this->value, true); + + if ($index !== false) { + unset($this->value[$index]); + $this->value = array_values($this->value); // Re-index array + return true; + } + + return false; + } + + /** + * Check if a string exists in the array. + * + * @param string $string + * @return bool + */ + public function contains(string $string): bool + { + return in_array($string, $this->value, true); + } + + /** + * Get the count of strings in the array. + * + * @return int + */ + public function count(): int + { + return count($this->value); + } + + /** + * Get the array as a comma-separated string. + * + * @return string + */ + public function toString(): string + { + return implode(', ', $this->value); + } + + /** + * Clear the array. + * + * @return void + */ + public function clear(): void + { + $this->value = []; + } } From 9a86f1cecfdca5275dd1c5edd15947ac4ba343c5 Mon Sep 17 00:00:00 2001 From: Nejc Cotic Date: Wed, 23 Oct 2024 08:24:01 +0200 Subject: [PATCH 2/2] update and add new tests --- Tests/ByteSliceTest.php | 131 +++++++++++++ Tests/FloatArrayTest.php | 160 ++++++++++++++++ Tests/StringArrayTest.php | 174 +++++++++++------- examples/byteslice.php | 213 ++++++++++++++++++++++ examples/stringarray.php | 171 +++++++++++++++++ src/Composite/Arrays/ByteSlice.php | 159 +++++++++++++++- src/Composite/Arrays/FloatArray.php | 181 +++++++++++++++++- src/Composite/Arrays/StringArray.php | 211 +++++++++++++++++---- src/Exceptions/InvalidByteException.php | 31 ++++ src/Exceptions/InvalidFloatException.php | 31 ++++ src/Exceptions/InvalidStringException.php | 52 ++++++ 11 files changed, 1395 insertions(+), 119 deletions(-) create mode 100644 Tests/ByteSliceTest.php create mode 100644 Tests/FloatArrayTest.php create mode 100644 examples/byteslice.php create mode 100644 examples/stringarray.php create mode 100644 src/Exceptions/InvalidByteException.php create mode 100644 src/Exceptions/InvalidFloatException.php create mode 100644 src/Exceptions/InvalidStringException.php diff --git a/Tests/ByteSliceTest.php b/Tests/ByteSliceTest.php new file mode 100644 index 0000000..e80050e --- /dev/null +++ b/Tests/ByteSliceTest.php @@ -0,0 +1,131 @@ +assertSame([10, 20, 255], $byteSlice->getValue()); + } + + /** + * Test creating a ByteSlice with invalid byte values. + */ + public function testCreateInvalidByteSlice(): void + { + $this->expectException(InvalidByteException::class); + $this->expectExceptionMessage("All elements must be valid bytes (0-255). Invalid value: -1"); + + new ByteSlice([10, -1, 255]); // -1 is out of valid byte range + } + + /** + * Test converting ByteSlice to hexadecimal representation. + */ + public function testConvertToHexadecimal(): void + { + $byteSlice = new ByteSlice([10, 20, 255]); + $this->assertSame("0A14FF", $byteSlice->toHex()); + } + + /** + * Test slicing a portion of ByteSlice. + */ + public function testSliceByteSlice(): void + { + $byteSlice = new ByteSlice([10, 20, 255, 30, 40]); + $sliced = $byteSlice->slice(1, 3); + $this->assertSame([20, 255, 30], $sliced->getValue()); + } + + /** + * Test merging two ByteSlices. + */ + public function testMergeByteSlices(): void + { + $byteSlice1 = new ByteSlice([10, 20, 255]); + $byteSlice2 = new ByteSlice([1, 2, 3]); + $merged = $byteSlice1->merge($byteSlice2); + + $this->assertSame([10, 20, 255, 1, 2, 3], $merged->getValue()); + } + + /** + * Test getting the count of bytes in a ByteSlice. + */ + public function testGetByteCount(): void + { + $byteSlice = new ByteSlice([10, 20, 255]); + $this->assertCount(3, $byteSlice); + } + + /** + * Test ArrayAccess implementation for accessing a byte at a specific index. + */ + public function testArrayAccessGet(): void + { + $byteSlice = new ByteSlice([10, 20, 255]); + $this->assertSame(20, $byteSlice[1]); + $this->assertNull($byteSlice[999]); // Accessing an invalid index returns null + } + + /** + * Test ArrayAccess prevents modification of ByteSlice. + */ + public function testArrayAccessSetThrowsException(): void + { + $this->expectException(InvalidByteException::class); + $this->expectExceptionMessage("Cannot modify an immutable ByteSlice."); + + $byteSlice = new ByteSlice([10, 20, 255]); + $byteSlice[1] = 100; // Should throw an exception + } + + /** + * Test ArrayAccess prevents unsetting a byte in ByteSlice. + */ + public function testArrayAccessUnsetThrowsException(): void + { + $this->expectException(InvalidByteException::class); + $this->expectExceptionMessage("Cannot unset a value in an immutable ByteSlice."); + + $byteSlice = new ByteSlice([10, 20, 255]); + unset($byteSlice[1]); // Should throw an exception + } + + /** + * Test iterating over ByteSlice with IteratorAggregate. + */ + public function testIterationOverByteSlice(): void + { + $byteSlice = new ByteSlice([10, 20, 255]); + $result = []; + + foreach ($byteSlice as $byte) { + $result[] = $byte; + } + + $this->assertSame([10, 20, 255], $result); + } + + /** + * Test an empty ByteSlice. + */ + public function testEmptyByteSlice(): void + { + $byteSlice = new ByteSlice([]); + $this->assertSame([], $byteSlice->getValue()); + $this->assertCount(0, $byteSlice); + } +} + diff --git a/Tests/FloatArrayTest.php b/Tests/FloatArrayTest.php new file mode 100644 index 0000000..79b68ec --- /dev/null +++ b/Tests/FloatArrayTest.php @@ -0,0 +1,160 @@ +assertSame([10.5, 20.1, 30.7], $floatArray->getValue()); + } + + /** + * Test creating a FloatArray with invalid float values. + */ + public function testCreateInvalidFloatArray(): void + { + $this->expectException(InvalidFloatException::class); + $this->expectExceptionMessage("All elements must be floats. Invalid value: 1"); + + new FloatArray([10.5, 1, 'not a float']); // Invalid non-float values + } + + /** + * Test adding floats to a FloatArray. + * @throws InvalidFloatException + */ + public function testAddFloats(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $newFloatArray = $floatArray->add(40.2, 50.3); + + $this->assertSame([10.5, 20.1, 30.7, 40.2, 50.3], $newFloatArray->getValue()); + } + + /** + * Test removing floats from a FloatArray. + * @throws InvalidFloatException + */ + public function testRemoveFloats(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7, 40.2]); + $modifiedFloatArray = $floatArray->remove(20.1, 30.7); + + $this->assertSame([10.5, 40.2], $modifiedFloatArray->getValue()); + } + + /** + * Test calculating the sum of floats. + * @throws InvalidFloatException + */ + public function testSumOfFloats(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $this->assertSame(61.3, $floatArray->sum()); + } + + /** + * Test calculating the average of floats. + * @throws InvalidFloatException + */ + public function testAverageOfFloats(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $this->assertSame(20.433333333333334, $floatArray->average()); + } + + /** + * Test calculating the average of an empty FloatArray. + */ + public function testAverageOfEmptyFloatArray(): void + { + $this->expectException(InvalidFloatException::class); + $this->expectExceptionMessage("Cannot calculate average of an empty array."); + + $floatArray = new FloatArray([]); + $floatArray->average(); // Should throw exception + } + + /** + * Test getting the count of floats in a FloatArray. + * @throws InvalidFloatException + */ + public function testGetFloatCount(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $this->assertCount(3, $floatArray); + } + + /** + * Test ArrayAccess implementation for accessing a float at a specific index. + * @throws InvalidFloatException + */ + public function testArrayAccessGet(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $this->assertSame(20.1, $floatArray[1]); + $this->assertNull($floatArray[999]); // Accessing an invalid index returns null + } + + /** + * Test ArrayAccess prevents modification of FloatArray. + */ + public function testArrayAccessSetThrowsException(): void + { + $this->expectException(InvalidFloatException::class); + $this->expectExceptionMessage("Cannot modify an immutable FloatArray."); + + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $floatArray[1] = 50.3; // Should throw exception + } + + /** + * Test ArrayAccess prevents unsetting a float in FloatArray. + */ + public function testArrayAccessUnsetThrowsException(): void + { + $this->expectException(InvalidFloatException::class); + $this->expectExceptionMessage("Cannot unset a value in an immutable FloatArray."); + + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + unset($floatArray[1]); // Should throw exception + } + + /** + * Test iterating over FloatArray with IteratorAggregate. + * @throws InvalidFloatException + */ + public function testIterationOverFloatArray(): void + { + $floatArray = new FloatArray([10.5, 20.1, 30.7]); + $result = []; + + foreach ($floatArray as $float) { + $result[] = $float; + } + + $this->assertSame([10.5, 20.1, 30.7], $result); + } + + /** + * Test creating an empty FloatArray. + * @throws InvalidFloatException + */ + public function testEmptyFloatArray(): void + { + $floatArray = new FloatArray([]); + $this->assertSame([], $floatArray->getValue()); + $this->assertCount(0, $floatArray); + } +} diff --git a/Tests/StringArrayTest.php b/Tests/StringArrayTest.php index 0c3df62..b275786 100644 --- a/Tests/StringArrayTest.php +++ b/Tests/StringArrayTest.php @@ -3,111 +3,145 @@ namespace Nejcc\PhpDatatypes\Tests; -use InvalidArgumentException; use Nejcc\PhpDatatypes\Composite\Arrays\StringArray; +use Nejcc\PhpDatatypes\Exceptions\InvalidStringException; use PHPUnit\Framework\TestCase; -use TypeError; class StringArrayTest extends TestCase { - /** - * Test that the constructor throws an exception if any element is not a string. - */ - public function testConstructorThrowsExceptionForInvalidType() + public function testCreateValidStringArray(): void { - $this->expectException(InvalidArgumentException::class); - new StringArray(['validString', 123]); // Invalid, second element is an integer + $array = new StringArray(['apple', 'banana', 'cherry']); + $this->assertEquals(['apple', 'banana', 'cherry'], $array->getValue()); } - /** - * Test that the constructor correctly assigns the array of strings. - */ - public function testConstructorAssignsValidValue() + public function testInvalidStringArrayThrowsException(): void { - $stringArray = new StringArray(['string1', 'string2']); - $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + $this->expectException(InvalidStringException::class); + new StringArray(['apple', 123, 'cherry']); // Integer value should throw an exception } - /** - * Test that adding a valid string works. - */ - public function testAddString() + public function testAddStringToNewInstance(): void { - $stringArray = new StringArray(['string1']); - $stringArray->add('string2'); - $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + $array = new StringArray(['apple', 'banana']); + $newArray = $array->add('cherry'); + + $this->assertNotSame($array, $newArray); // Ensure immutability + $this->assertEquals(['apple', 'banana'], $array->getValue()); + $this->assertEquals(['apple', 'banana', 'cherry'], $newArray->getValue()); + } + + public function testRemoveStringFromArray(): void + { + $array = new StringArray(['apple', 'banana', 'cherry']); + $newArray = $array->remove('banana'); + + $this->assertNotSame($array, $newArray); // Ensure immutability + $this->assertEquals(['apple', 'banana', 'cherry'], $array->getValue()); + $this->assertEquals(['apple', 'cherry'], $newArray->getValue()); + } + + public function testRemoveNonExistentStringDoesNothing(): void + { + $array = new StringArray(['apple', 'banana', 'cherry']); + $newArray = $array->remove('pear'); + + // Check that the values are still the same (immutability) + $this->assertEquals($array->getValue(), $newArray->getValue()); + } + + + public function testContains(): void + { + $array = new StringArray(['apple', 'banana', 'cherry']); + + $this->assertTrue($array->contains('banana')); + $this->assertFalse($array->contains('pear')); + } + + public function testCountStrings(): void + { + $array = new StringArray(['apple', 'banana', 'cherry']); + $this->assertEquals(3, $array->count()); + } + + public function testToString(): void + { + $array = new StringArray(['apple', 'banana', 'cherry']); + $this->assertEquals('apple, banana, cherry', $array->toString()); + $this->assertEquals('apple|banana|cherry', $array->toString('|')); + } + + public function testFilterByPrefix(): void + { + $array = new StringArray(['apple', 'banana', 'apricot']); + $filtered = $array->filterByPrefix('ap'); + + $this->assertEquals(['apple', 'apricot'], $filtered); + } + + public function testFilterBySubstring(): void + { + $array = new StringArray(['apple', 'banana', 'pineapple']); + $filtered = $array->filterBySubstring('apple'); + + $this->assertEquals(['apple', 'pineapple'], $filtered); } - /** - * Test that adding a non-string value throws a TypeError. - */ - public function testAddNonStringThrowsException() + public function testToUpperCase(): void { - $stringArray = new StringArray(['string1']); + $array = new StringArray(['apple', 'banana']); + $newArray = $array->toUpperCase(); - $this->expectException(TypeError::class); // Expect TypeError instead of InvalidArgumentException - $stringArray->add(123); // Invalid, not a string + $this->assertNotSame($array, $newArray); // Ensure immutability + $this->assertEquals(['APPLE', 'BANANA'], $newArray->getValue()); } - /** - * Test that removing an existing string works. - */ - public function testRemoveString() + public function testToLowerCase(): void { - $stringArray = new StringArray(['string1', 'string2']); - $removed = $stringArray->remove('string1'); + $array = new StringArray(['APPLE', 'BANANA']); + $newArray = $array->toLowerCase(); - $this->assertTrue($removed); - $this->assertEquals(['string2'], $stringArray->getValue()); + $this->assertNotSame($array, $newArray); // Ensure immutability + $this->assertEquals(['apple', 'banana'], $newArray->getValue()); } - /** - * Test that removing a non-existing string returns false. - */ - public function testRemoveNonExistingString() + public function testClearArray(): void { - $stringArray = new StringArray(['string1', 'string2']); - $removed = $stringArray->remove('string3'); + $array = new StringArray(['apple', 'banana']); + $clearedArray = $array->clear(); - $this->assertFalse($removed); - $this->assertEquals(['string1', 'string2'], $stringArray->getValue()); + $this->assertNotSame($array, $clearedArray); // Ensure immutability + $this->assertEquals([], $clearedArray->getValue()); } - /** - * Test that contains() works correctly. - */ - public function testContainsString() + public function testArrayAccess(): void { - $stringArray = new StringArray(['string1', 'string2']); - $this->assertTrue($stringArray->contains('string1')); - $this->assertFalse($stringArray->contains('string3')); + $array = new StringArray(['apple', 'banana']); + $this->assertEquals('apple', $array[0]); + $this->assertEquals('banana', $array[1]); + $this->assertNull($array[2]); // Non-existent index } - /** - * Test that count() returns the correct number of elements. - */ - public function testCountStrings() + public function testOffsetSetThrowsException(): void { - $stringArray = new StringArray(['string1', 'string2']); - $this->assertEquals(2, $stringArray->count()); + $this->expectException(InvalidStringException::class); + $array = new StringArray(['apple', 'banana']); + $array[0] = 'orange'; // Should throw an exception } - /** - * Test that toString() returns the correct comma-separated string. - */ - public function testToString() + public function testOffsetUnsetThrowsException(): void { - $stringArray = new StringArray(['string1', 'string2']); - $this->assertEquals('string1, string2', $stringArray->toString()); + $this->expectException(InvalidStringException::class); + $array = new StringArray(['apple', 'banana']); + unset($array[0]); // Should throw an exception } - /** - * Test that clear() empties the array. - */ - public function testClearArray() + public function testIterator(): void { - $stringArray = new StringArray(['string1', 'string2']); - $stringArray->clear(); - $this->assertEquals([], $stringArray->getValue()); + $array = new StringArray(['apple', 'banana']); + foreach ($array as $key => $value) { + $this->assertEquals($array->get($key), $value); + } } } diff --git a/examples/byteslice.php b/examples/byteslice.php new file mode 100644 index 0000000..c49635f --- /dev/null +++ b/examples/byteslice.php @@ -0,0 +1,213 @@ +getValue()); +} catch (InvalidByteException $e) { + echo $e->getMessage(); +} +CODE; + ob_start(); + $byteSlice = new ByteSlice([10, 20, 255]); + print_r($byteSlice->getValue()); + $output1 = ob_get_clean(); + + $examples[] = [ + 'title' => 'Create a ByteSlice', + 'description' => 'We create a ByteSlice with some initial byte values.', + 'code' => $fullCode1, + 'output' => $output1, + ]; + + // Example 2: Convert to Hexadecimal + $fullCode2 = <<<'CODE' +toHex(); + echo "Hexadecimal: " . $hex; +} catch (InvalidByteException $e) { + echo $e->getMessage(); +} +CODE; + ob_start(); + $hex = $byteSlice->toHex(); + echo "Hexadecimal: " . $hex; + $output2 = ob_get_clean(); + + $examples[] = [ + 'title' => 'Convert to Hexadecimal', + 'description' => 'We convert the byte slice to its hexadecimal representation.', + 'code' => $fullCode2, + 'output' => $output2, + ]; + + // Example 3: Slice the ByteSlice + $fullCode3 = <<<'CODE' +slice(1, 2); + print_r($sliced->getValue()); +} catch (InvalidByteException $e) { + echo $e->getMessage(); +} +CODE; + ob_start(); + $sliced = $byteSlice->slice(1, 2); + print_r($sliced->getValue()); + $output3 = ob_get_clean(); + + $examples[] = [ + 'title' => 'Slice the ByteSlice', + 'description' => 'We slice a portion of the byte array, starting at index 1.', + 'code' => $fullCode3, + 'output' => $output3, + ]; + + // Example 4: Merge ByteSlices + $fullCode4 = <<<'CODE' +merge($otherByteSlice); + print_r($merged->getValue()); +} catch (InvalidByteException $e) { + echo $e->getMessage(); +} +CODE; + ob_start(); + $otherByteSlice = new ByteSlice([1, 2, 3]); + $merged = $byteSlice->merge($otherByteSlice); + print_r($merged->getValue()); + $output4 = ob_get_clean(); + + $examples[] = [ + 'title' => 'Merge with Another ByteSlice', + 'description' => 'We merge the current byte slice with another byte slice [1, 2, 3].', + 'code' => $fullCode4, + 'output' => $output4, + ]; + + // Example 5: Get Count of Bytes + $fullCode5 = <<<'CODE' +count(); +} catch (InvalidByteException $e) { + echo $e->getMessage(); +} +CODE; + ob_start(); + echo "Count of bytes: " . $byteSlice->count(); + $output5 = ob_get_clean(); + + $examples[] = [ + 'title' => 'Get the Count of Bytes', + 'description' => 'We get the number of bytes currently in the byte slice.', + 'code' => $fullCode5, + 'output' => $output5, + ]; + +} catch (InvalidByteException $e) { + $errorMessage = $e->getMessage(); +} + +?> + + + + + + + ByteSlice Example with Tailwind and Prism.js + + + + + + + + + + + + + + +
+

PHP ByteSlice Example: Step by Step

+
+ +
+

+ Below are examples demonstrating how to create, modify, and interact with a ByteSlice object. + Each code block is followed by its corresponding output. +

+ + + +
+
+

+

+
+ +
+

Full Code

+
+
+ +
+

Output

+
+
+
+
+
+ + + + +
+

Error

+

+
+ +
+ + diff --git a/examples/stringarray.php b/examples/stringarray.php new file mode 100644 index 0000000..c204615 --- /dev/null +++ b/examples/stringarray.php @@ -0,0 +1,171 @@ + 'Create a StringArray', + 'description' => 'We create a StringArray with some initial values.', + 'code' => "\$stringArray = new StringArray(['apple', 'banana', 'cherry']);", + 'output' => print_r($stringArray->getValue(), true), + ]; + + // Add new elements + $newStringArray = $stringArray->add('date', 'elderberry'); + $examples[] = [ + 'title' => 'Add New Elements', + 'description' => "We add two new elements: 'date' and 'elderberry'.", + 'code' => "\$newStringArray = \$stringArray->add('date', 'elderberry');", + 'output' => print_r($newStringArray->getValue(), true), + ]; + + // Remove 'banana' + $modifiedArray = $newStringArray->remove('banana'); + $examples[] = [ + 'title' => 'Remove an Element', + 'description' => "We remove 'banana' from the array.", + 'code' => "\$modifiedArray = \$newStringArray->remove('banana');", + 'output' => print_r($modifiedArray->getValue(), true), + ]; + + // Check if 'apple' exists + $containsApple = $modifiedArray->contains('apple') ? "Yes" : "No"; + $examples[] = [ + 'title' => "Check if 'apple' Exists", + 'description' => "We check if the array contains 'apple'.", + 'code' => "\$containsApple = \$modifiedArray->contains('apple') ? 'Yes' : 'No';", + 'output' => "Contains 'apple': " . $containsApple, + ]; + + // Convert to uppercase + $upperCaseArray = $modifiedArray->toUpperCase(); + $examples[] = [ + 'title' => 'Convert to Uppercase', + 'description' => "We convert all strings in the array to uppercase.", + 'code' => "\$upperCaseArray = \$modifiedArray->toUpperCase();", + 'output' => print_r($upperCaseArray->getValue(), true), + ]; + + // Filter by prefix 'ch' + $filteredByPrefix = $stringArray->filterByPrefix('ch'); + $examples[] = [ + 'title' => "Filter by Prefix 'ch'", + 'description' => "We filter the array to only include items starting with 'ch'.", + 'code' => "\$filteredByPrefix = \$stringArray->filterByPrefix('ch');", + 'output' => print_r($filteredByPrefix, true), + ]; + + // Filter by substring 'err' + $filteredBySubstring = $stringArray->filterBySubstring('err'); + $examples[] = [ + 'title' => "Filter by Substring 'err'", + 'description' => "We filter the array to only include items that contain 'err'.", + 'code' => "\$filteredBySubstring = \$stringArray->filterBySubstring('err');", + 'output' => print_r($filteredBySubstring, true), + ]; + + // Get count + $count = $newStringArray->count(); + $examples[] = [ + 'title' => 'Get the Count of Strings', + 'description' => "We get the number of strings currently in the array.", + 'code' => "\$count = \$newStringArray->count();", + 'output' => "Count of strings: " . $count, + ]; + + // Get string representation + $stringRepresentation = $newStringArray->toString(); + $examples[] = [ + 'title' => 'Get String Representation', + 'description' => "We convert the array to a comma-separated string.", + 'code' => "\$stringRepresentation = \$newStringArray->toString();", + 'output' => "String representation: " . $stringRepresentation, + ]; + + // Clear the array + $clearedArray = $newStringArray->clear(); + $examples[] = [ + 'title' => 'Clear the Array', + 'description' => "We clear all elements from the array.", + 'code' => "\$clearedArray = \$newStringArray->clear();", + 'output' => print_r($clearedArray->getValue(), true), + ]; + +} catch (InvalidStringException $e) { + $errorMessage = $e->getMessage(); +} + +?> + + + + + + + StringArray Example with Tailwind and Prism.js + + + + + + + + + + + + + + +
+

PHP StringArray Example: Step by Step

+
+ +
+

+ Below are examples demonstrating how to create, modify, and interact with a StringArray object. + Each code block is followed by its corresponding output. +

+ + + +
+
+

+

+
+ +
+

Code

+ +
+
+
+ +
+

Output

+
+
+
+
+
+ + + + +
+

Error

+

+
+ +
+ + diff --git a/src/Composite/Arrays/ByteSlice.php b/src/Composite/Arrays/ByteSlice.php index c753020..939b036 100644 --- a/src/Composite/Arrays/ByteSlice.php +++ b/src/Composite/Arrays/ByteSlice.php @@ -1,20 +1,167 @@ The byte values (0-255). + */ private array $value; - public function __construct(array $value) { - foreach ($value as $item) { + /** + * Constructor for ByteSlice. + * + * @param array $value The array of byte values. + * @throws InvalidByteException If any value is not a valid byte. + */ + public function __construct(array $value) + { + $this->validateBytes($value); + $this->value = $value; + } + + /** + * Validate that all elements are valid bytes (0-255). + * + * @param array $array The array to validate. + * @throws InvalidByteException If any element is not a valid byte. + * @return void + */ + private function validateBytes(array $array): void + { + foreach ($array as $item) { if (!is_int($item) || $item < 0 || $item > 255) { - throw new \InvalidArgumentException("All elements must be valid bytes (0-255)."); + throw new InvalidByteException("All elements must be valid bytes (0-255). Invalid value: " . $item); } } - $this->value = $value; } - public function getValue(): array { + /** + * Get the array of byte values. + * + * @return array The byte array. + */ + public function getValue(): array + { return $this->value; } + + /** + * Get the byte at a specific index. + * + * @param int $index The index. + * @return int|null The byte value or null if index is out of bounds. + */ + public function getByte(int $index): ?int + { + return $this->value[$index] ?? null; + } + + /** + * Get the count of bytes in the array. + * + * @return int The number of bytes. + */ + public function count(): int + { + return count($this->value); + } + + /** + * Convert the byte slice to a hexadecimal string. + * + * @return string The hexadecimal representation. + */ + public function toHex(): string + { + return implode('', array_map(fn($byte) => sprintf('%02X', $byte), $this->value)); + } + + /** + * Slice a portion of the byte array. + * + * @param int $offset The start offset. + * @param int|null $length The length of the slice. + * @return ByteSlice The sliced byte array. + * @throws InvalidByteException + */ + public function slice(int $offset, ?int $length = null): self + { + return new self(array_slice($this->value, $offset, $length)); + } + + /** + * Merge the current byte array with another byte array. + * + * @param ByteSlice $other The other byte array to merge. + * @return ByteSlice A new ByteSlice instance containing the merged bytes. + * @throws InvalidByteException + */ + public function merge(ByteSlice $other): self + { + return new self(array_merge($this->value, $other->getValue())); + } + + /** + * ArrayAccess: Check if a byte exists at the given offset. + * + * @param int $offset The array offset. + * @return bool True if offset exists, false otherwise. + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->value[$offset]); + } + + /** + * ArrayAccess: Get the byte at the given offset. + * + * @param int $offset The array offset. + * @return mixed The byte value at the given offset. + */ + public function offsetGet(mixed $offset): mixed + { + return $this->value[$offset] ?? null; + } + + /** + * ArrayAccess: Prevent modification by throwing an exception. + * + * @param int $offset The array offset. + * @param mixed $value The value to set (not allowed). + * @throws InvalidByteException Always thrown since ByteSlice is immutable. + */ + public function offsetSet(mixed $offset, mixed $value): void + { + throw new InvalidByteException("Cannot modify an immutable ByteSlice."); + } + + /** + * ArrayAccess: Prevent unsetting by throwing an exception. + * + * @param int $offset The array offset. + * @throws InvalidByteException Always thrown since ByteSlice is immutable. + */ + public function offsetUnset(mixed $offset): void + { + throw new InvalidByteException("Cannot unset a value in an immutable ByteSlice."); + } + + /** + * Get an iterator for the byte array. + * + * @return Traversable An iterator for the byte array. + */ + public function getIterator(): Traversable + { + return new \ArrayIterator($this->value); + } } diff --git a/src/Composite/Arrays/FloatArray.php b/src/Composite/Arrays/FloatArray.php index 480d09d..50591d4 100644 --- a/src/Composite/Arrays/FloatArray.php +++ b/src/Composite/Arrays/FloatArray.php @@ -1,20 +1,189 @@ The float values. + */ private array $value; - public function __construct(array $value) { - foreach ($value as $item) { + /** + * Constructor for FloatArray. + * + * @param array $value The array of float values. + * @throws InvalidFloatException If any value is not a valid float. + */ + public function __construct(array $value) + { + $this->validateFloats($value); + $this->value = $value; + } + + /** + * Validate that all elements are floats. + * + * @param array $array The array to validate. + * @throws InvalidFloatException If any element is not a valid float. + * @return void + */ + private function validateFloats(array $array): void + { + foreach ($array as $item) { if (!is_float($item)) { - throw new \InvalidArgumentException("All elements must be floats."); + throw new InvalidFloatException("All elements must be floats. Invalid value: " . json_encode($item)); } } - $this->value = $value; } - public function getValue(): array { + /** + * Get the array of float values. + * + * @return array The float array. + */ + public function getValue(): array + { return $this->value; } + + /** + * Get the float at a specific index. + * + * @param int $index The index. + * @return float|null The float value or null if index is out of bounds. + */ + public function getFloat(int $index): ?float + { + return $this->value[$index] ?? null; + } + + /** + * Get the count of floats in the array. + * + * @return int The number of floats. + */ + public function count(): int + { + return count($this->value); + } + + /** + * Calculate the sum of the float array. + * + * @return float The sum of the floats. + */ + public function sum(): float + { + return array_sum($this->value); + } + + /** + * Calculate the average of the float array. + * + * @return float The average value of the floats. + * @throws InvalidFloatException If the array is empty. + */ + public function average(): float + { + if ($this->count() === 0) { + throw new InvalidFloatException("Cannot calculate average of an empty array."); + } + return $this->sum() / $this->count(); + } + + /** + * Add new floats to the array (returns a new instance). + * + * @param float ...$floats The floats to add. + * @return FloatArray The new FloatArray with added floats. + * @throws InvalidFloatException If any value is not a valid float. + */ + public function add(float ...$floats): self + { + $this->validateFloats($floats); + return new self(array_merge($this->value, $floats)); + } + + /** + * Remove specific floats from the array (returns a new instance). + * + * @param float ...$floats The floats to remove. + * @return FloatArray The new FloatArray with removed floats. + * @throws InvalidFloatException + */ + public function remove(float ...$floats): self + { + $newArray = $this->value; + foreach ($floats as $float) { + $index = array_search($float, $newArray, true); + if ($index !== false) { + unset($newArray[$index]); + } + } + return new self(array_values($newArray)); + } + + /** + * ArrayAccess: Check if a float exists at the given offset. + * + * @param int $offset The array offset. + * @return bool True if offset exists, false otherwise. + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->value[$offset]); + } + + /** + * ArrayAccess: Get the float at the given offset. + * + * @param int $offset The array offset. + * @return float|null The float value or null if index does not exist. + */ + public function offsetGet(mixed $offset): mixed + { + return $this->value[$offset] ?? null; + } + + /** + * ArrayAccess: Prevent modification by throwing an exception. + * + * @param mixed $offset The array offset. + * @param mixed $value The value to set (not allowed). + * @throws InvalidFloatException Always thrown since FloatArray is immutable. + */ + public function offsetSet(mixed $offset, mixed $value): void + { + throw new InvalidFloatException("Cannot modify an immutable FloatArray."); + } + + /** + * ArrayAccess: Prevent unsetting by throwing an exception. + * + * @param int $offset The array offset. + * @throws InvalidFloatException Always thrown since FloatArray is immutable. + */ + public function offsetUnset(mixed $offset): void + { + throw new InvalidFloatException("Cannot unset a value in an immutable FloatArray."); + } + + /** + * Get an iterator for the float array. + * + * @return Traversable An iterator for the float array. + */ + public function getIterator(): Traversable + { + return new \ArrayIterator($this->value); + } } diff --git a/src/Composite/Arrays/StringArray.php b/src/Composite/Arrays/StringArray.php index d4da046..52aa079 100644 --- a/src/Composite/Arrays/StringArray.php +++ b/src/Composite/Arrays/StringArray.php @@ -3,9 +3,13 @@ namespace Nejcc\PhpDatatypes\Composite\Arrays; -use InvalidArgumentException; +use ArrayAccess; +use Countable; +use IteratorAggregate; +use Nejcc\PhpDatatypes\Exceptions\InvalidStringException; +use Traversable; -class StringArray +readonly class StringArray implements ArrayAccess, Countable, IteratorAggregate { /** * The array of string values. @@ -18,17 +22,28 @@ class StringArray * Create a new StringArray instance. * * @param array $value - * @throws InvalidArgumentException + * @throws InvalidStringException */ - public function __construct(array $value) + public function __construct(array $value = []) { - foreach ($value as $item) { + $this->validateArray($value); + $this->value = $value; + } + + /** + * Validates that the array consists only of strings. + * + * @param array $array + * @return void + * @throws InvalidStringException + */ + private function validateArray(array $array): void + { + foreach ($array as $item) { if (!is_string($item)) { - throw new InvalidArgumentException("All elements must be strings."); + throw new InvalidStringException("All elements must be strings. Invalid value: " . json_encode($item)); } } - - $this->value = $value; } /** @@ -42,45 +57,51 @@ public function getValue(): array } /** - * Add a new string to the array. + * Add multiple strings to the array (returns a new instance). * - * @param string $string - * @return void - * @throws InvalidArgumentException + * @param string ...$strings + * @return self New instance with added values. + * @throws InvalidStringException */ - public function add(string $string): void + public function add(string ...$strings): self { - $this->value[] = $string; + $this->validateArray($strings); + return new self(array_merge($this->value, $strings)); } /** - * Remove a string from the array. + * Remove multiple strings from the array (returns a new instance). * - * @param string $string - * @return bool True if the string was found and removed, false otherwise. + * @param string ...$strings + * @return self New instance with removed values. + * @throws InvalidStringException */ - public function remove(string $string): bool + public function remove(string ...$strings): self { - $index = array_search($string, $this->value, true); - - if ($index !== false) { - unset($this->value[$index]); - $this->value = array_values($this->value); // Re-index array - return true; + $newArray = $this->value; + foreach ($strings as $string) { + $index = array_search($string, $newArray, true); + if ($index !== false) { + unset($newArray[$index]); + } } - - return false; + return new self(array_values($newArray)); // Re-index array } /** - * Check if a string exists in the array. + * Check if multiple strings exist in the array. * - * @param string $string - * @return bool + * @param string ...$strings + * @return bool True if all strings are found, false otherwise. */ - public function contains(string $string): bool + public function contains(string ...$strings): bool { - return in_array($string, $this->value, true); + foreach ($strings as $string) { + if (!in_array($string, $this->value, true)) { + return false; + } + } + return true; } /** @@ -94,22 +115,138 @@ public function count(): int } /** - * Get the array as a comma-separated string. + * Get the array as a comma-separated string or with custom separator. * + * @param string $separator Separator to use between strings (default: ", "). * @return string */ - public function toString(): string + public function toString(string $separator = ', '): string { - return implode(', ', $this->value); + return implode($separator, $this->value); } /** - * Clear the array. + * Find strings that start with a specific prefix. * + * @param string $prefix + * @return array Array of strings that start with the given prefix. + */ + public function filterByPrefix(string $prefix): array + { + return array_values(array_filter($this->value, fn($str) => str_starts_with($str, $prefix))); + } + + + /** + * Find strings that contain a specific substring. + * + * @param string $substring + * @return array Array of strings that contain the substring. + */ + public function filterBySubstring(string $substring): array + { + return array_values(array_filter($this->value, fn($str) => str_contains($str, $substring))); + } + + + /** + * Get a string at a specific index. + * + * @param int $index + * @return string|null + */ + public function get(int $index): ?string + { + return $this->value[$index] ?? null; + } + + /** + * Convert all strings to uppercase (returns a new instance). + * + * @return self + * @throws InvalidStringException + */ + public function toUpperCase(): self + { + return new self(array_map('strtoupper', $this->value)); + } + + /** + * Convert all strings to lowercase (returns a new instance). + * + * @return self + * @throws InvalidStringException + */ + public function toLowerCase(): self + { + return new self(array_map('strtolower', $this->value)); + } + + /** + * Clear the array (returns a new empty instance). + * + * @return self + * @throws InvalidStringException + */ + public function clear(): self + { + return new self([]); + } + + /** + * ArrayAccess method to check if an offset exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists(mixed $offset): bool + { + return isset($this->value[$offset]); + } + + /** + * ArrayAccess method to get an offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet(mixed $offset): mixed + { + return $this->value[$offset] ?? null; + } + + /** + * ArrayAccess method to set an offset (immutable, returns a new instance). + * + * @param mixed $offset + * @param mixed $value + * @return void + * @throws InvalidStringException + */ + public function offsetSet(mixed $offset, mixed $value): void + { + throw new InvalidStringException("Cannot modify immutable StringArray."); + } + + /** + * ArrayAccess method to unset an offset (immutable, returns a new instance). + * + * @param mixed $offset * @return void + * @throws InvalidStringException + */ + public function offsetUnset(mixed $offset): void + { + throw new InvalidStringException("Cannot modify immutable StringArray."); + } + + /** + * Returns an iterator for traversing the array. + * + * @return Traversable */ - public function clear(): void + public function getIterator(): Traversable { - $this->value = []; + return new \ArrayIterator($this->value); } } diff --git a/src/Exceptions/InvalidByteException.php b/src/Exceptions/InvalidByteException.php new file mode 100644 index 0000000..3b768ec --- /dev/null +++ b/src/Exceptions/InvalidByteException.php @@ -0,0 +1,31 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/Exceptions/InvalidFloatException.php b/src/Exceptions/InvalidFloatException.php new file mode 100644 index 0000000..9bcfff6 --- /dev/null +++ b/src/Exceptions/InvalidFloatException.php @@ -0,0 +1,31 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/Exceptions/InvalidStringException.php b/src/Exceptions/InvalidStringException.php new file mode 100644 index 0000000..60a8ef2 --- /dev/null +++ b/src/Exceptions/InvalidStringException.php @@ -0,0 +1,52 @@ +invalidValue = $invalidValue; + + // Generate a more detailed default message if none is provided. + $message = $message ?? "Invalid string value: " . json_encode($invalidValue); + + parent::__construct($message, $code, $previous); + } + + /** + * Get the invalid value that caused the exception. + * + * @return mixed The invalid value. + */ + public function getInvalidValue(): mixed + { + return $this->invalidValue; + } + + /** + * String representation of the exception, including the invalid value. + * + * @return string + */ + public function __toString(): string + { + return __CLASS__ . ": [{$this->code}]: {$this->message}. Invalid value: " . json_encode($this->invalidValue) . "\n"; + } +}