From cd16888df38893b531d0874c20aa33fc96511262 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Sun, 9 Mar 2025 23:08:17 +0300 Subject: [PATCH 1/4] feat: Add `Parameters` for working with global variables --- system/HTTP/Parameters/InputParameters.php | 62 +++++++++++++ system/HTTP/Parameters/Parameters.php | 86 +++++++++++++++++++ .../HTTP/Parameters/ParametersInterface.php | 65 ++++++++++++++ system/HTTP/Parameters/ServerParameters.php | 18 ++++ 4 files changed, 231 insertions(+) create mode 100644 system/HTTP/Parameters/InputParameters.php create mode 100644 system/HTTP/Parameters/Parameters.php create mode 100644 system/HTTP/Parameters/ParametersInterface.php create mode 100644 system/HTTP/Parameters/ServerParameters.php diff --git a/system/HTTP/Parameters/InputParameters.php b/system/HTTP/Parameters/InputParameters.php new file mode 100644 index 000000000000..331885983183 --- /dev/null +++ b/system/HTTP/Parameters/InputParameters.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +use CodeIgniter\Exceptions\InvalidArgumentException; +use CodeIgniter\Exceptions\RuntimeException; + +/** + * @template TKey of string + * @template TValue of bool|float|int|string|array + * + * @see \CodeIgniter\HTTP\Parameters\InputParametersTest + */ +class InputParameters extends Parameters +{ + public function override(array $parameters = []): void + { + $this->parameters = []; + + foreach ($parameters as $key => $value) { + $this->set($key, $value); + } + } + + public function get(string $key, $default = null) + { + if ($default !== null && ! is_scalar($default)) { + throw new InvalidArgumentException(sprintf('The default value for the InputParameters must be a scalar type, "%s" given.', gettype($default))); + } + + // TODO: We need to check that the default value is set. Let's check the unique string + $tempDefault = bin2hex(random_bytes(8)); + + $value = parent::get($key, $tempDefault); + + if ($value !== null && $value !== $tempDefault && ! is_scalar($value)) { + throw new RuntimeException(sprintf('The value of the key "%s" InputParameters does not contain a scalar value, "%s" given.', $key, gettype($value))); + } + + return $value === $tempDefault ? $default : $value; + } + + public function set(string $key, $value): void + { + if (! is_scalar($value) && ! is_array($value)) { + throw new InvalidArgumentException(sprintf('The value for the InputParameters must be a scalar type, "%s" given.', gettype($value))); + } + + $this->parameters[$key] = $value; + } +} diff --git a/system/HTTP/Parameters/Parameters.php b/system/HTTP/Parameters/Parameters.php new file mode 100644 index 000000000000..a136113746ed --- /dev/null +++ b/system/HTTP/Parameters/Parameters.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +use ArrayIterator; +use CodeIgniter\Exceptions\RuntimeException; + +/** + * @template TKey of string + * @template TValue of mixed + * + * @property array $parameters + * + * @see \CodeIgniter\HTTP\Parameters\ParametersTest + */ +class Parameters implements ParametersInterface +{ + public function __construct( + protected array $parameters = [], + ) { + } + + public function override(array $parameters = []): void + { + $this->parameters = $parameters; + } + + public function has(string $key): bool + { + return array_key_exists($key, $this->parameters); + } + + public function get(string $key, $default = null) + { + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + public function set(string $key, $value): void + { + $this->parameters[$key] = $value; + } + + public function remove(string $key): void + { + unset($this->parameters[$key]); + } + + public function all(?string $key = null): array + { + if ($key === null) { + return $this->parameters; + } + + if (! isset($this->parameters[$key]) || ! is_array($this->parameters[$key])) { + throw new RuntimeException(sprintf('The key "%s" value for Parameters is not an array or was not found.', $key)); + } + + return $this->parameters[$key]; + } + + public function keys(): array + { + return array_keys($this->parameters); + } + + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->parameters); + } + + public function count(): int + { + return count($this->parameters); + } +} diff --git a/system/HTTP/Parameters/ParametersInterface.php b/system/HTTP/Parameters/ParametersInterface.php new file mode 100644 index 000000000000..6f52c3cce1b1 --- /dev/null +++ b/system/HTTP/Parameters/ParametersInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +use Countable; +use IteratorAggregate; + +/** + * @template TKey of string + * @template TValue of mixed + */ +interface ParametersInterface extends IteratorAggregate, Countable +{ + /** + * @param array $parameters + */ + public function __construct(array $parameters = []); + + /** + * @param array $parameters + */ + public function override(array $parameters = []): void; + + /** + * @param TKey $key + */ + public function has(string $key): bool; + + /** + * @param TKey $key + * @param TValue $default + * + * @return ?TValue + */ + public function get(string $key, $default = null); + + /** + * @param TKey $key + * @param TValue $value + */ + public function set(string $key, $value): void; + + /** + * @param TKey $key + * + * @return array + */ + public function all(?string $key = null): array; + + /** + * @return array + */ + public function keys(): array; +} diff --git a/system/HTTP/Parameters/ServerParameters.php b/system/HTTP/Parameters/ServerParameters.php new file mode 100644 index 000000000000..4d610f558d43 --- /dev/null +++ b/system/HTTP/Parameters/ServerParameters.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +class ServerParameters extends Parameters +{ +} From 839366165d872f4020a6f4cb10d4c30c7062fc86 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Mon, 10 Mar 2025 00:04:35 +0300 Subject: [PATCH 2/4] tests: Add tests for `Parameters` --- .../HTTP/Parameters/InputParametersTest.php | 107 +++++++++++ .../system/HTTP/Parameters/ParametersTest.php | 167 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 tests/system/HTTP/Parameters/InputParametersTest.php create mode 100644 tests/system/HTTP/Parameters/ParametersTest.php diff --git a/tests/system/HTTP/Parameters/InputParametersTest.php b/tests/system/HTTP/Parameters/InputParametersTest.php new file mode 100644 index 000000000000..6aa5c4c8aaaf --- /dev/null +++ b/tests/system/HTTP/Parameters/InputParametersTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +use CodeIgniter\Exceptions\InvalidArgumentException; +use CodeIgniter\Test\CIUnitTestCase; +use PHPUnit\Framework\Attributes\Group; +use stdClass; + +/** + * @internal + */ +#[Group('Others')] +final class InputParametersTest extends CIUnitTestCase +{ + /** + * @var array + */ + private array $original = [ + 'title' => '', + 'toolbar' => '1', + 'path' => 'public/index.php', + 'current_time' => 1741522635.661474, + 'debug' => true, + 'pages' => 15, + 'filters' => [ + 0 => 'name', + 1 => 'sum', + ], + 'sort' => [ + 'date' => 'ASC', + 'age' => 'DESC', + ], + ]; + + public function testGetNonScalarValues(): void + { + $parameters = new InputParameters($this->original); + + $this->assertNull($parameters->get('undefined_or_null', null)); + + $this->expectException(InvalidArgumentException::class); + + $parameters->get('undefined_throw', new stdClass()); + } + + public function testAttemptSetNullValues(): void + { + $parameters = new InputParameters($this->original); + + $this->expectException(InvalidArgumentException::class); + + $parameters->set('nullable', null); + } + + public function testAttemptSetNonScalarValues(): void + { + $parameters = new InputParameters($this->original); + + $this->expectException(InvalidArgumentException::class); + + $parameters->set('nullable', null); + } + + public function testUpdateAndSetNewValues(): void + { + /** + * @var array + */ + $expected = [ + 'title' => 'CodeIgniter', + 'toolbar' => '0', + 'path' => '', + 'current_time' => 1741522888.661434, + 'debug' => false, + 'pages' => 10, + 'filters' => [ + 0 => 'sum', + 1 => 'name', + ], + 'sort' => [ + 'age' => 'ASC', + 'date' => 'DESC', + ], + 'slug' => 'Ben-i-need-help', + ]; + + $parameters = new InputParameters($this->original); + + foreach (array_keys($expected) as $key) { + $parameters->set($key, $expected[$key]); + } + + $this->assertSame($expected, $parameters->all()); + } +} diff --git a/tests/system/HTTP/Parameters/ParametersTest.php b/tests/system/HTTP/Parameters/ParametersTest.php new file mode 100644 index 000000000000..71bd3e5915ca --- /dev/null +++ b/tests/system/HTTP/Parameters/ParametersTest.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP\Parameters; + +use CodeIgniter\Test\CIUnitTestCase; +use PHPUnit\Framework\Attributes\Group; +use stdClass; + +/** + * @internal + */ +#[Group('Others')] +final class ParametersTest extends CIUnitTestCase +{ + /** + * @var array + */ + private array $original = []; + + protected function setUp(): void + { + $this->original = [ + 'DOCUMENT_ROOT' => '', + 'DISPLAY' => '1', + 'SCRIPT_NAME' => 'vendor/bin/phpunit', + 'REQUEST_TIME_FLOAT' => 1741522635.661474, + 'IS_HTTPS' => true, + 'PHP_NULLABLE_VAR' => null, + 'OBJECT' => new stdClass(), + 'argc' => 3, + 'argv' => [ + 0 => 'vendor/bin/phpunit', + 1 => './tests/ParametersTest.php', + 2 => '--no-coverage', + ], + ]; + } + + public function testCreateParametersAndCompareIdentity(): void + { + $parameters = new Parameters($this->original); + + $this->assertSame($this->original, $parameters->all()); + $this->assertSame($this->original, iterator_to_array($parameters->getIterator())); + } + + public function testCreateEmptyParameters(): void + { + $parameters = new Parameters(); + + $this->assertSame([], $parameters->all()); + } + + public function testGetValues(): void + { + $parameters = new Parameters($this->original); + + foreach ($parameters->keys() as $key) { + $this->assertSame($this->original[$key], $parameters->get($key)); + } + } + + public function testUpdateAndSetNewValues(): void + { + /** + * @var array + */ + $expected = [ + 'DOCUMENT_ROOT' => '/www', + 'DISPLAY' => '0', + 'SCRIPT_NAME' => '', + 'REQUEST_TIME_FLOAT' => 1741522600.661400, + 'IS_HTTPS' => false, + 'PHP_NULLABLE_VAR' => null, + 'OBJECT' => new stdClass(), + 'argc' => 2, + 'argv' => [ + 0 => 'bin/phpunit', + 1 => './ParametersTest.php', + ], + 'XDEBUG' => 'enabled', + ]; + + $parameters = new Parameters($this->original); + + foreach (array_keys($expected) as $key) { + $parameters->set($key, $expected[$key]); + } + + $this->assertSame($expected, $parameters->all()); + } + + public function testOverrideParameters(): void + { + /** + * @var array + */ + $expected = [ + 'XDEBUG' => 'enabled', + 'DOCUMENT_ROOT' => '/www', + 'DISPLAY' => '0', + 'SCRIPT_NAME' => '', + 'REQUEST_TIME_FLOAT' => 1741522600.661400, + 'IS_HTTPS' => false, + 'PHP_NULLABLE_VAR' => null, + 'OBJECT' => new stdClass(), + 'argc' => 2, + 'argv' => [ + 0 => 'bin/phpunit', + 1 => './ParametersTest.php', + ], + ]; + + $parameters = new Parameters($this->original); + + $parameters->override($expected); + + $this->assertSame($expected, $parameters->all()); + } + + public function testGetUndefinedParametersWithDefaultValueReturn(): void + { + $parameters = new Parameters([]); + + $this->assertNull($parameters->get('undefined')); + $this->assertInstanceOf(stdClass::class, $parameters->get('undefined', new stdClass())); + $this->assertSame('', $parameters->get('undefined', '')); + $this->assertSame(1000, $parameters->get('undefined', 1000)); + $this->assertSame(['name' => 'Ivan'], $parameters->get('undefined', ['name' => 'Ivan'])); + $this->assertEqualsWithDelta(10.00, $parameters->get('undefined', 10.00), PHP_FLOAT_EPSILON); + } + + public function testRemoveKeys(): void + { + $parameters = new Parameters($this->original); + + $parameters->remove('argc'); + $parameters->remove('argv'); + + unset($this->original['argc'], $this->original['argv']); + + $this->assertSame($this->original, $parameters->all()); + } + + public function testCount(): void + { + $parameters = new Parameters($this->original); + + $this->assertCount(count($this->original), $parameters); + + $parameters->remove('DOCUMENT_ROOT'); + $parameters->remove('DISPLAY'); + + $this->assertCount(count($this->original) - 2, $parameters); + } +} From 6c5e6f2993873f381facaadea834860deb32e309 Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 11 Mar 2025 19:42:05 +0300 Subject: [PATCH 3/4] fix: Apply suggestion --- system/HTTP/Parameters/InputParameters.php | 8 +++++--- system/HTTP/Parameters/Parameters.php | 11 +++++++---- system/HTTP/Parameters/ParametersInterface.php | 12 +++++++----- system/HTTP/Parameters/ServerParameters.php | 6 ++++++ tests/system/HTTP/Parameters/InputParametersTest.php | 6 +++--- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/system/HTTP/Parameters/InputParameters.php b/system/HTTP/Parameters/InputParameters.php index 331885983183..8aa050807588 100644 --- a/system/HTTP/Parameters/InputParameters.php +++ b/system/HTTP/Parameters/InputParameters.php @@ -18,7 +18,9 @@ /** * @template TKey of string - * @template TValue of bool|float|int|string|array + * @template TValue of scalar|array<(int|string), mixed> + * + * @extends Parameters * * @see \CodeIgniter\HTTP\Parameters\InputParametersTest */ @@ -33,7 +35,7 @@ public function override(array $parameters = []): void } } - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { if ($default !== null && ! is_scalar($default)) { throw new InvalidArgumentException(sprintf('The default value for the InputParameters must be a scalar type, "%s" given.', gettype($default))); @@ -51,7 +53,7 @@ public function get(string $key, $default = null) return $value === $tempDefault ? $default : $value; } - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { if (! is_scalar($value) && ! is_array($value)) { throw new InvalidArgumentException(sprintf('The value for the InputParameters must be a scalar type, "%s" given.', gettype($value))); diff --git a/system/HTTP/Parameters/Parameters.php b/system/HTTP/Parameters/Parameters.php index a136113746ed..20f9b7d66c41 100644 --- a/system/HTTP/Parameters/Parameters.php +++ b/system/HTTP/Parameters/Parameters.php @@ -18,14 +18,17 @@ /** * @template TKey of string - * @template TValue of mixed + * @template TValue * - * @property array $parameters + * @implements ParametersInterface * * @see \CodeIgniter\HTTP\Parameters\ParametersTest */ class Parameters implements ParametersInterface { + /** + * @param array $parameters + */ public function __construct( protected array $parameters = [], ) { @@ -41,12 +44,12 @@ public function has(string $key): bool return array_key_exists($key, $this->parameters); } - public function get(string $key, $default = null) + public function get(string $key, mixed $default = null): mixed { return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } - public function set(string $key, $value): void + public function set(string $key, mixed $value): void { $this->parameters[$key] = $value; } diff --git a/system/HTTP/Parameters/ParametersInterface.php b/system/HTTP/Parameters/ParametersInterface.php index 6f52c3cce1b1..6873e78f7849 100644 --- a/system/HTTP/Parameters/ParametersInterface.php +++ b/system/HTTP/Parameters/ParametersInterface.php @@ -18,7 +18,9 @@ /** * @template TKey of string - * @template TValue of mixed + * @template TValue + * + * @extends IteratorAggregate */ interface ParametersInterface extends IteratorAggregate, Countable { @@ -41,15 +43,15 @@ public function has(string $key): bool; * @param TKey $key * @param TValue $default * - * @return ?TValue + * @return TValue|null */ - public function get(string $key, $default = null); + public function get(string $key, mixed $default = null): mixed; /** * @param TKey $key * @param TValue $value */ - public function set(string $key, $value): void; + public function set(string $key, mixed $value): void; /** * @param TKey $key @@ -59,7 +61,7 @@ public function set(string $key, $value): void; public function all(?string $key = null): array; /** - * @return array + * @return list */ public function keys(): array; } diff --git a/system/HTTP/Parameters/ServerParameters.php b/system/HTTP/Parameters/ServerParameters.php index 4d610f558d43..c6e87d49ae4e 100644 --- a/system/HTTP/Parameters/ServerParameters.php +++ b/system/HTTP/Parameters/ServerParameters.php @@ -13,6 +13,12 @@ namespace CodeIgniter\HTTP\Parameters; +/** + * @template TKey of string + * @template TValue + * + * @extends Parameters + */ class ServerParameters extends Parameters { } diff --git a/tests/system/HTTP/Parameters/InputParametersTest.php b/tests/system/HTTP/Parameters/InputParametersTest.php index 6aa5c4c8aaaf..414f8a7dd4e7 100644 --- a/tests/system/HTTP/Parameters/InputParametersTest.php +++ b/tests/system/HTTP/Parameters/InputParametersTest.php @@ -52,7 +52,7 @@ public function testGetNonScalarValues(): void $this->expectException(InvalidArgumentException::class); - $parameters->get('undefined_throw', new stdClass()); + $parameters->get('undefined_throw', new stdClass()); // @phpstan-ignore argument.type } public function testAttemptSetNullValues(): void @@ -61,7 +61,7 @@ public function testAttemptSetNullValues(): void $this->expectException(InvalidArgumentException::class); - $parameters->set('nullable', null); + $parameters->set('nullable', null); // @phpstan-ignore argument.type } public function testAttemptSetNonScalarValues(): void @@ -70,7 +70,7 @@ public function testAttemptSetNonScalarValues(): void $this->expectException(InvalidArgumentException::class); - $parameters->set('nullable', null); + $parameters->set('nullable', new stdClass()); // @phpstan-ignore argument.type } public function testUpdateAndSetNewValues(): void From dee9d30e3b12b5fd9fc141b12d8b880ba72e2dde Mon Sep 17 00:00:00 2001 From: neznaika0 Date: Tue, 11 Mar 2025 19:42:17 +0300 Subject: [PATCH 4/4] test: Add additional tests --- .../system/HTTP/Parameters/ParametersTest.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/system/HTTP/Parameters/ParametersTest.php b/tests/system/HTTP/Parameters/ParametersTest.php index 71bd3e5915ca..fb824145de0e 100644 --- a/tests/system/HTTP/Parameters/ParametersTest.php +++ b/tests/system/HTTP/Parameters/ParametersTest.php @@ -13,6 +13,7 @@ namespace CodeIgniter\HTTP\Parameters; +use CodeIgniter\Exceptions\RuntimeException; use CodeIgniter\Test\CIUnitTestCase; use PHPUnit\Framework\Attributes\Group; use stdClass; @@ -60,6 +61,7 @@ public function testCreateEmptyParameters(): void $parameters = new Parameters(); $this->assertSame([], $parameters->all()); + $this->assertSame([], $parameters->keys()); } public function testGetValues(): void @@ -164,4 +166,28 @@ public function testCount(): void $this->assertCount(count($this->original) - 2, $parameters); } + + public function testGetAll(): void + { + $parameters = new Parameters($this->original); + + $this->assertSame($this->original, $parameters->all()); + $this->assertSame( + [ + 'vendor/bin/phpunit', + './tests/ParametersTest.php', + '--no-coverage', + ], + $parameters->all('argv'), + ); + } + + public function testAttemptGetAllNonIterableValues(): void + { + $parameters = new Parameters($this->original); + + $this->expectException(RuntimeException::class); + + $parameters->all('argc'); + } }