diff --git a/CLAUDE.md b/CLAUDE.md index d9695a8..193e024 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `bitapps/wp-validator` — a Composer library (PSR-4: `BitApps\WPValidator\` → [src/](src/)) providing Laravel-style validation and sanitization for WordPress. Public API and the full rule list are documented in [README.md](README.md); update it when adding a rule or sanitizer. -**PHP 7.2 is the floor.** No typed properties, no arrow functions, no null coalescing assignment, no union types. Return types on methods are used and fine. [rector.php](rector.php) pins `PhpVersion::PHP_72` and skips the typed-property rectors for this reason. +**PHP 7.4 is the floor.** No union types or other PHP 8+ syntax. Return types on methods are used and fine. [rector.php](rector.php) pins `PhpVersion::PHP_74` and skips the typed-property rectors. ## Commands @@ -14,7 +14,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co composer test:unit # full suite (Pest, --testdox, excludes group 'db') ./vendor/bin/pest tests/Rules/BetweenTest.php # single file ./vendor/bin/pest --filter='between accepts a length of zero' # single test by name -composer compat # PHPCompatibility check against PHP 7.2 +composer compat # PHPCompatibility check against PHP 7.4 composer rector # Rector dry-run (never auto-applies) ``` diff --git a/composer.json b/composer.json index 251ed5c..7956bd3 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ }, "require": { - "php": ">=7.2" + "php": ">=7.4" }, "require-dev": { "pestphp/pest": "^2.18", diff --git a/rector.php b/rector.php index f815ed8..a808ff6 100644 --- a/rector.php +++ b/rector.php @@ -13,7 +13,7 @@ ->withPaths([ __DIR__ . '/src', ]) - ->withPhpVersion(PhpVersion::PHP_72) + ->withPhpVersion(PhpVersion::PHP_74) ->withSkip([ UnwrapFutureCompatibleIfPhpVersionRector::class, TypedPropertyFromStrictConstructorRector::class, @@ -29,4 +29,4 @@ // strictBooleans: true, // naming: true, ) - ->withSets([SetList::PHP_72]); + ->withSets([SetList::PHP_74]); diff --git a/src/InputDataContainer.php b/src/InputDataContainer.php index 71d2b5c..1126e9c 100644 --- a/src/InputDataContainer.php +++ b/src/InputDataContainer.php @@ -7,7 +7,7 @@ class InputDataContainer private $data = []; - private $attributeKey; + private $attributeKey = ''; private $attributeLabel; @@ -18,7 +18,7 @@ public function __construct($data = []) public function setAttributeKey($key): void { - $this->attributeKey = $key; + $this->attributeKey = $key ?? ''; } public function getAttributeKey() @@ -28,22 +28,17 @@ public function getAttributeKey() public function getAttributeValue($key = null) { - $keys = explode('.', trim($this->attributeKey ?? '', '[]')); - - $data = $this->data; + $keys = explode('.', trim($this->attributeKey, '[]')); if (count($keys) > 1) { - $data = $this->getValueFromPath($keys, $data); - } elseif ($key && isset($data[$key])) { - return $data[$key]; - } elseif (isset($data[$this->attributeKey])) { - return $data[$this->attributeKey]; - } else { - return null; + return $this->getValueFromPath($keys, $this->data); } - return $data; + if ($key !== null && isset($this->data[$key])) { + return $this->data[$key]; + } + return $this->data[$this->attributeKey] ?? null; } public function setAttributeLabel($value): void @@ -63,7 +58,7 @@ public function getData() public function setAttributeValue($value): void { - $keys = explode('.', trim($this->attributeKey ?? '', '[]')); + $keys = explode('.', trim($this->attributeKey, '[]')); if (\count($keys) > 1) { $this->setNestedElement($this->data, $keys, $value); diff --git a/tests/InputDataContainerTest.php b/tests/InputDataContainerTest.php new file mode 100644 index 0000000..7cf3534 --- /dev/null +++ b/tests/InputDataContainerTest.php @@ -0,0 +1,104 @@ + 'John']); + $container->setAttributeKey('name'); + + $deprecations = collectDeprecations(function () use ($container) { + $container->getAttributeValue(); + }); + + expect($deprecations)->toBe([]); + expect($container->getAttributeValue())->toBe('John'); +}); + +test('getAttributeValue before an attribute key is set does not use null as an array offset', function () { + $container = new InputDataContainer(['name' => 'John']); + + $deprecations = collectDeprecations(function () use ($container) { + $container->getAttributeValue(); + }); + + expect($deprecations)->toBe([]); + expect($container->getAttributeValue())->toBeNull(); +}); + +test('setAttributeValue before an attribute key is set does not use null as an array offset', function () { + $container = new InputDataContainer(['name' => 'John']); + + $deprecations = collectDeprecations(function () use ($container) { + $container->setAttributeValue('Doe'); + }); + + expect($deprecations)->toBe([]); + expect($container->getData())->toBe(['name' => 'John', '' => 'Doe']); + expect($container->getAttributeValue())->toBe('Doe'); +}); + +test('a null attribute key does not use null as an array offset', function () { + $container = new InputDataContainer(['name' => 'John']); + $container->setAttributeKey(null); + + $deprecations = collectDeprecations(function () use ($container) { + $container->getAttributeValue(); + $container->setAttributeValue('Doe'); + }); + + expect($deprecations)->toBe([]); +}); + +test('present rule before an attribute key is set does not use null as an array offset', function () { + $rule = new PresentRule(); + $rule->setInputDataContainer(new InputDataContainer(['name' => 'John'])); + + $deprecations = collectDeprecations(function () use ($rule) { + $rule->validate('John'); + }); + + expect($deprecations)->toBe([]); +}); + +test('getAttributeValue does not read an empty string key when called without a key', function () { + $container = new InputDataContainer(['' => 'wrong', 'name' => 'John']); + $container->setAttributeKey('name'); + + expect($container->getAttributeValue())->toBe('John'); +}); + +test('getAttributeValue reads falsy explicit keys', function () { + $container = new InputDataContainer(['first', 'second']); + $container->setAttributeKey(1); + + expect($container->getAttributeValue(0))->toBe('first'); + expect($container->getAttributeValue('0'))->toBe('first'); +}); + +test('same rule compares against a field named 0', function () { + $validator = new Validator(); + + expect($validator->make(['0' => 'secret', 'confirm' => 'wrong'], ['confirm' => ['same:0']])->fails())->toBeTrue(); + expect($validator->make(['0' => 'secret', 'confirm' => 'secret'], ['confirm' => ['same:0']])->fails())->toBeFalse(); +});