From 7fda71aac5a26aa3e62e5870b75a2383f9ef7db7 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Sun, 6 Sep 2026 12:56:58 -0400 Subject: [PATCH] Grade code style against the shared config, not Pint's defaults fix-php-code-style-issues.yml ran aglipanci/laravel-pint-action with configPath: pint.json, a file this package does not ship. Pint handed a config path that does not exist falls back to its own defaults and exits 0, so the job rewrote each touched file to the wrong style and committed it - with contents: write and an auto-commit action, so it always won - while composer pint graded against the shared laranail config. The two had been disagreeing silently on every PR that touched a .php file. Replace the action with setup-php + composer install + composer pint-fix, the same command the lint gate checks, and reformat the 259 files that had drifted. The suite is unchanged across the reformat: 6276 tests, 10486 assertions, 121 skipped, before and after. Skip PostIncDecToPreIncDecRector while here. It rewrites $i++ to ++$i in 21 files and Pint's increment_style rewrites every one back, so rector and pint could not both pass. --- .../workflows/fix-php-code-style-issues.yml | 20 +- CHANGELOG.md | 17 +- benchmark.php | 208 +++++++-------- config/laranail-validation.php | 2 +- rector-migrate-1.0.php | 2 +- rector.php | 30 ++- resources/lang/en/validation.php | 192 +++++++------- src/Actions/CachedDnsResolver.php | 8 +- src/BatchDatabaseChecker.php | 89 ++++--- src/Builder/Concerns/HasEmbeddedRules.php | 4 +- src/Builder/Concerns/HasFieldModifiers.php | 116 ++++----- src/Builder/Concerns/SelfValidates.php | 18 +- src/Builder/Nodes/AcceptedRule.php | 10 +- src/Builder/Nodes/ArrayRule.php | 51 ++-- src/Builder/Nodes/BooleanRule.php | 12 +- src/Builder/Nodes/DateRule.php | 24 +- src/Builder/Nodes/DeclinedRule.php | 10 +- src/Builder/Nodes/EmailRule.php | 30 +-- src/Builder/Nodes/FieldRule.php | 26 +- src/Builder/Nodes/FileRule.php | 30 +-- src/Builder/Nodes/IpAddressRule.php | 16 +- src/Builder/Nodes/MacAddressRule.php | 14 +- src/Builder/Nodes/NumericRule.php | 48 ++-- src/Builder/Nodes/PasswordRule.php | 12 +- src/Builder/Nodes/PhoneRule.php | 20 +- src/Builder/Nodes/StringRule.php | 54 ++-- src/Builder/Nodes/UrlRule.php | 20 +- src/Builder/Nodes/UsernameRule.php | 14 +- src/Check.php | 30 +-- src/Commands/BenchmarkCommand.php | 6 +- src/Commands/DoctorCommand.php | 22 +- src/Commands/RulesCommand.php | 24 +- src/Concerns/SkipsPrecognition.php | 2 +- src/Contracts/FluentRuleContract.php | 26 +- src/Events/RuleSetCompiling.php | 8 +- src/Events/ValidationCompleted.php | 4 +- src/Events/ValidationFailed.php | 2 +- src/Exceptions/CannotExtendListShapedEach.php | 4 +- src/Exceptions/TypedBuilderHint.php | 47 ++-- src/FastCheck/ItemContextCompiler.php | 42 +-- src/FastCheck/PresenceConditionalCompiler.php | 14 +- src/FastCheck/RuleConfigBuilder.php | 114 +++++---- src/FastCheckCompiler.php | 2 +- src/FluentRule.php | 34 +-- src/FluentSchema.php | 28 +- src/FluentValidator.php | 10 +- src/HasFluentRules.php | 22 +- src/HasFluentValidation.php | 10 +- src/Internal/AbstractConditionalReducer.php | 16 +- src/Internal/BatchLimitRemap.php | 2 +- src/Internal/BatchPresenceQuery.php | 9 +- src/Internal/BatchRuleShape.php | 2 +- src/Internal/ConditionalEvaluationPhase.php | 17 +- src/Internal/ConditionalValueMatcher.php | 17 +- src/Internal/DatabaseClaimScanner.php | 9 +- src/Internal/ExcludeConditionExtractor.php | 9 +- src/Internal/ItemErrorCollector.php | 6 +- src/Internal/ItemRuleCompiler.php | 37 +-- src/Internal/ItemValidator.php | 33 +-- src/Internal/PreparesOptimizedRules.php | 39 +-- src/Internal/PresenceMatchFidelity.php | 4 +- src/Internal/RuleCacheKey.php | 10 +- src/Internal/ValidatorStateCopier.php | 2 +- src/Internal/ValueTypePredicates.php | 21 +- src/Internal/VanillaAfterRoute.php | 13 +- src/MemoizingValidator.php | 7 +- src/OptimizedValidator.php | 19 +- src/PrecomputedPresenceVerifier.php | 16 +- src/PreparedRules.php | 8 +- src/PresenceConditionalReducer.php | 30 +-- src/Providers/ValidationServiceProvider.php | 39 +-- src/Regex.php | 30 +-- src/RuleSet.php | 199 ++++++++------- src/Rules/AntiSpam/SubmissionTiming.php | 6 +- src/Rules/Banking/Iban.php | 4 +- src/Rules/Chrono/DateInterval.php | 2 +- src/Rules/Chrono/MaxDateDifference.php | 4 +- src/Rules/Chrono/MinimumAge.php | 4 +- src/Rules/Chrono/MinuteIn.php | 6 +- src/Rules/Chrono/Rfc3339.php | 4 +- src/Rules/Chrono/TimeOfDay.php | 4 +- src/Rules/Codes/Gtin.php | 4 +- src/Rules/Codes/Isbn.php | 8 +- src/Rules/Codes/UpcE.php | 10 +- src/Rules/Colour/CssColor.php | 32 +-- src/Rules/Crypto/BitcoinAddress.php | 2 +- src/Rules/Database/Authorized.php | 10 +- src/Rules/Database/CompareToColumn.php | 4 +- src/Rules/Database/Comparison.php | 10 +- src/Rules/Database/ModelsExist.php | 8 +- src/Rules/Email/EmailDomainIs.php | 2 +- src/Rules/Email/NotDisposableEmail.php | 2 +- src/Rules/Email/NotRoleEmail.php | 2 +- src/Rules/Encoding/Base64Image.php | 4 +- src/Rules/Encoding/DataUri.php | 2 +- src/Rules/Fiscal/NationalIdentifier.php | 4 +- src/Rules/Fiscal/VatNumber.php | 20 +- src/Rules/Geo/Subdivisions.php | 2 +- src/Rules/I18n/CurrencyCode.php | 10 +- src/Rules/Identifiers/HashDigest.php | 56 ++-- src/Rules/Identifiers/SemVer.php | 6 +- src/Rules/Markup/Xml.php | 2 +- src/Rules/Net/DomainName.php | 2 +- src/Rules/Net/InCidrRange.php | 4 +- src/Rules/Net/MacAddress.php | 14 +- src/Rules/Net/Support/HostPattern.php | 4 +- src/Rules/Net/Support/IpClassifier.php | 6 +- src/Rules/Net/Url.php | 16 +- src/Rules/Network/DeliverableEmail.php | 2 +- src/Rules/Network/HasGravatar.php | 8 +- src/Rules/Network/ImageUrl.php | 8 +- src/Rules/Numbers/MonetaryAmount.php | 4 +- src/Rules/Numbers/Parity.php | 6 +- src/Rules/Payment/CardCvc.php | 4 +- src/Rules/Payment/CardNumber.php | 12 +- src/Rules/Postal/Patterns.php | 22 +- src/Rules/Postal/PostalCode.php | 12 +- src/Rules/Profanity/NoProfanity.php | 13 +- src/Rules/Storage/FileExistsOnDisk.php | 4 +- src/Rules/Structure/Delimited.php | 16 +- src/Rules/Telecom/Phone.php | 18 +- src/Rules/Telecom/UniquePhone.php | 24 +- src/Rules/Text/CaseStyle.php | 8 +- src/Rules/Text/MaxWords.php | 2 +- src/Rules/Text/MinWords.php | 2 +- src/Rules/Text/PersonName.php | 6 +- src/Rules/Text/Salutation.php | 4 +- src/Rules/Text/Username.php | 22 +- src/Rules/Vendor/VendorIdentifier.php | 10 +- src/Schemas/PersonNameSchema.php | 20 +- .../Email/BundledDisposableDomainList.php | 4 +- src/Support/Email/BundledRoleAccountList.php | 2 +- src/Support/Encoding/Base64File.php | 2 +- src/Support/I18n/BundledCountryDataset.php | 4 +- src/Support/I18n/BundledCurrencyDataset.php | 4 +- src/Support/I18n/BundledLanguageDataset.php | 2 +- src/Support/InlineTermList.php | 6 +- src/Support/Payment/CardBrand.php | 8 +- src/Support/RuleAliases.php | 240 +++++++++--------- src/Support/RuleRegistrar.php | 16 +- .../Text/DefaultReservedUsernameList.php | 2 +- .../Text/ExtendedReservedUsernameList.php | 4 +- src/Testing/Arch/BansFieldRuleTypeMethods.php | 32 +-- src/Testing/FluentRulesTester.php | 91 +++---- src/Testing/PestExpectations.php | 12 +- src/Testing/ValidationFake.php | 6 +- src/Validated.php | 4 +- src/ValueConditionalReducer.php | 26 +- src/WildcardExpander.php | 10 +- tests/AddRuleMessageParamTest.php | 4 +- tests/ArchTest.php | 2 +- tests/ArrayRuleExtendHelpersTest.php | 4 +- tests/BatchCollationParityTest.php | 12 +- tests/BatchDatabaseValidationTest.php | 24 +- .../BatchExistsUniqueSameFieldParityTest.php | 12 +- tests/BatchSharedColumnParityTest.php | 15 +- tests/BatchValidationGuardsTest.php | 32 +-- tests/Builder/EmailNodeRulesTest.php | 10 +- tests/CheckAndFakeTest.php | 6 +- tests/ConditionalEvaluationPhaseTest.php | 6 +- tests/ConditionalOptimizationTest.php | 46 ++-- tests/ConsumerExtensionTest.php | 4 +- .../DependentBooleanRuleRemovalParityTest.php | 18 +- tests/DocCountsTest.php | 16 +- tests/EmailRuleTest.php | 4 +- .../UnknownFluentRuleMethodTest.php | 6 +- tests/ExcludeBooleanDependentParityTest.php | 26 +- tests/ExcludeCoercionParityTest.php | 26 +- tests/ExcludeStringFormPruningTest.php | 18 +- tests/ExpandsWildcardsTest.php | 6 +- tests/FastCheck/FamilyCompilerTest.php | 2 +- tests/FastCheckArrayValueParityTest.php | 4 +- tests/FastCheckCompilerCacheTest.php | 12 +- tests/FastCheckParityTest.php | 78 +++--- tests/FieldRuleExtendHelpersTest.php | 4 +- tests/FluentFormRequestTest.php | 58 ++--- tests/FluentRuleContractTest.php | 16 +- tests/FluentRuleTest.php | 52 ++-- tests/FluentSchemaTest.php | 64 ++--- tests/FluentValidatorBatchDatabaseTest.php | 6 +- tests/FluentValidatorBenchTest.php | 58 ++--- .../FluentValidatorConditionalParityTest.php | 46 ++-- tests/HasFluentValidationForFilamentTest.php | 23 +- tests/HasFluentValidationTest.php | 53 ++-- tests/Helpers.php | 12 +- tests/ImplicitAttributesCanaryTest.php | 2 +- tests/ImportBenchTest.php | 150 +++++------ tests/InlineMessageParamPhase3aTest.php | 30 +-- tests/InlineMessageParamPhase3bTest.php | 40 +-- tests/InlineMessageParamPhase3cTest.php | 38 +-- tests/InlineMessageParamPhase3dTest.php | 22 +- tests/InlineMessageParamPhase3eTest.php | 4 +- tests/IntegrationTest.php | 10 +- tests/Internal/ItemRuleCompilerTest.php | 4 +- tests/Internal/ItemValidatorTest.php | 6 +- tests/Internal/ValidatorStateCopierTest.php | 6 +- tests/ItemExclusionLeakParityTest.php | 5 +- tests/ItemValidatorCustomResolverTest.php | 14 +- tests/ItemValidatorFastCheckCacheTest.php | 5 +- tests/MemoizingValidatorParityTest.php | 15 +- tests/NamingConventionTest.php | 4 +- tests/Pest.php | 25 +- tests/PhoneRuleTest.php | 16 +- tests/PresenceConditionalParityTest.php | 77 +++--- tests/ProhibitedConditionalParityTest.php | 18 +- tests/ProviderLayoutTest.php | 4 +- tests/PublicApiBoundaryTest.php | 16 +- tests/RegexBuilderTest.php | 2 +- tests/RegexWithPipeTest.php | 22 +- tests/RequiredConditionalNullableTest.php | 117 ++++----- tests/RuleAliasesTest.php | 30 +-- tests/RuleConfigBuilderTest.php | 2 +- tests/RuleMessagesResolveTest.php | 14 +- tests/RuleRegistrarTest.php | 10 +- tests/RuleSetEventsTest.php | 8 +- tests/RuleSetModifyEachTest.php | 4 +- tests/RuleSetParityHarnessTest.php | 16 +- tests/RuleSetTest.php | 182 ++++++------- tests/RuleSetWithBagTest.php | 6 +- tests/Rules/AntiSpam/AntiSpamRulesTest.php | 6 +- tests/Rules/Banking/BankingRulesTest.php | 6 +- tests/Rules/Chrono/ChronoRulesTest.php | 12 +- tests/Rules/ClientCheckableTest.php | 25 +- tests/Rules/Crypto/CryptoRulesTest.php | 44 ++-- tests/Rules/Database/CompareToColumnTest.php | 6 +- tests/Rules/Database/DatabaseRulesTest.php | 4 +- tests/Rules/Email/EmailRulesTest.php | 12 +- tests/Rules/Encoding/EncodingRulesTest.php | 12 +- tests/Rules/Fiscal/NationalIdentifierTest.php | 56 ++-- tests/Rules/Fiscal/VatNumberTest.php | 2 +- tests/Rules/Geo/GeoRulesTest.php | 6 +- tests/Rules/I18n/I18nRulesTest.php | 12 +- .../Identifiers/IdentifiersRulesTest.php | 6 +- tests/Rules/Markup/XmlTest.php | 4 +- tests/Rules/Net/NetRulesTest.php | 12 +- tests/Rules/Net/NetworkFieldsTest.php | 4 +- tests/Rules/Net/UrlTest.php | 6 +- tests/Rules/Network/DeliverableEmailTest.php | 6 +- tests/Rules/Network/ProbeRulesTest.php | 14 +- tests/Rules/Numbers/NumbersRulesTest.php | 48 ++-- tests/Rules/Payment/PaymentRulesTest.php | 2 +- tests/Rules/Postal/PostalCodeTest.php | 44 ++-- tests/Rules/Profanity/InlineTermListTest.php | 2 +- tests/Rules/Profanity/NoProfanityTest.php | 8 +- tests/Rules/RuleTierTest.php | 16 +- tests/Rules/Structure/DelimitedTest.php | 12 +- tests/Rules/Text/ReservedUsernameListTest.php | 2 +- tests/Rules/Text/TextRulesTest.php | 14 +- tests/Rules/Text/WordsAndSalutationTest.php | 2 +- tests/Rules/TrailingNewlineTest.php | 66 ++--- tests/Rules/Vendor/VendorIdentifierTest.php | 30 +-- tests/SchemaRulesMergeTest.php | 42 +-- tests/Schemas/PersonNameSchemaTest.php | 72 +++--- tests/SlowPathBenchTest.php | 118 ++++----- tests/StringRuleTest.php | 2 +- tests/Support/ThrowsOnEveryCacheCall.php | 2 +- tests/TestCase.php | 11 +- .../Arch/BansFieldRuleTypeMethodsTest.php | 24 +- .../FluentRulesTesterClassTargetsTest.php | 14 +- .../Testing/FluentRulesTesterLivewireTest.php | 2 +- tests/Testing/FluentRulesTesterTest.php | 20 +- tests/Testing/PestExpectationsTest.php | 6 +- tests/ValidationServiceProviderTest.php | 2 +- tests/ValueConditionalParityTest.php | 43 ++-- tests/WildcardDepPathParityTest.php | 32 +-- tests/WildcardExpanderTest.php | 4 +- tools/build-datasets.php | 46 ++-- 267 files changed, 2952 insertions(+), 2822 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index e8d6c80..d969f32 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -24,11 +24,23 @@ jobs: with: ref: ${{ github.head_ref }} - - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@36de00d5f5a8a4e12d443e01671daa12a18f4c79 # 2.6 + # NOT aglipanci/laravel-pint-action with `configPath: pint.json`. This + # package ships no pint.json, and Pint handed a config path that does not + # exist falls back to its OWN defaults and exits 0 rather than failing. So + # this job was rewriting the code to Pint's defaults and committing that - + # while `composer pint` checks the shared laranail config. The two + # disagreed, and this one won because it pushes. + - name: Setup PHP + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: - configPath: pint.json - verboseMode: true + php-version: '8.5' + coverage: none + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Fix PHP code style issues + run: composer pint-fix - name: Pull latest changes env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd9abe..579bf46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,22 @@ Entries below `Unreleased` are written by CI from the GitHub release body — se ## Unreleased -_Nothing yet._ +### Fixed + +- **The code-style CI job was formatting against Pint's defaults, not the shared laranail config.** + `fix-php-code-style-issues.yml` ran `aglipanci/laravel-pint-action` with `configPath: pint.json` - + a file this package does not ship. Pint handed a config path that does not exist falls back to its + own defaults and exits 0, so the job rewrote every touched file to the wrong style and committed + the result, while `composer pint` graded against + `vendor/laranail/package-tools/pint.json`. The job now installs dependencies and runs + `composer pint-fix`, which is the same command the `lint` gate checks. 259 files are reformatted + by this change; no behaviour is affected (6276 tests, 10486 assertions, identical before and after). + +### Changed + +- `PostIncDecToPreIncDecRector` is skipped. Rector rewrote `$i++` to `++$i` in 21 files and Pint's + `increment_style` rewrote every one of them back, so the two gates could never both pass. + Formatting belongs to Pint. ## v0.1.0 - 2026-08-27 diff --git a/benchmark.php b/benchmark.php index 547ad9b..236fd1e 100644 --- a/benchmark.php +++ b/benchmark.php @@ -14,15 +14,15 @@ * php benchmark.php --json Output JSON */ -require __DIR__.'/vendor/autoload.php'; +require __DIR__ . '/vendor/autoload.php'; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Support\Facades\Validator; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Validation\ValidationException; use Orchestra\Testbench\Foundation\Application; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Contracts\Validation\ValidationRule; $isSnapshot = in_array('--snapshot', $argv); $isCi = in_array('--ci', $argv); @@ -37,13 +37,13 @@ function makeProducts(int $count): array { return array_map(fn (int $i): array => [ - 'sku' => "SKU-{$i}", - 'name' => "Product {$i}", - 'price' => round(9.99 + ($i % 100) * 0.5, 2), + 'sku' => "SKU-{$i}", + 'name' => "Product {$i}", + 'price' => round(9.99 + ($i % 100) * 0.5, 2), 'quantity' => $i % 1000, 'category' => ['electronics', 'clothing', 'food', 'books', 'toys'][$i % 5], - 'active' => $i % 2 === 0, - 'tags' => 'tag-'.($i % 20), + 'active' => $i % 2 === 0, + 'tags' => 'tag-' . ($i % 20), ], range(1, $count)); } @@ -51,11 +51,11 @@ function makeOrders(int $orders, int $lineItemsPerOrder): array { return array_map(fn (int $i): array => [ 'order_number' => "ORD-{$i}", - 'status' => ['pending', 'processing', 'shipped'][$i % 3], - 'line_items' => array_map(fn (int $j): array => [ + 'status' => ['pending', 'processing', 'shipped'][$i % 3], + 'line_items' => array_map(fn (int $j): array => [ 'product_id' => ($i * 10 + $j) % 9999 + 1, - 'quantity' => $j + 1, - 'price' => round(10.00 + $j * 2.5, 2), + 'quantity' => $j + 1, + 'price' => round(10.00 + $j * 2.5, 2), ], range(1, $lineItemsPerOrder)), ], range(1, $orders)); } @@ -63,19 +63,19 @@ function makeOrders(int $orders, int $lineItemsPerOrder): array function makeEvents(int $count): array { return array_map(fn (int $i): array => [ - 'name' => "Event {$i}", - 'start_date' => '2025-06-'.str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), - 'end_date' => '2025-07-'.str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), - 'registration_deadline' => '2025-05-'.str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), + 'name' => "Event {$i}", + 'start_date' => '2025-06-' . str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), + 'end_date' => '2025-07-' . str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), + 'registration_deadline' => '2025-05-' . str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), ], range(1, $count)); } function makeArticles(int $count): array { return array_map(fn (int $i): array => [ - 'title' => "Article {$i}", - 'slug' => "article-{$i}", - 'content' => str_repeat('Word ', 120), + 'title' => "Article {$i}", + 'slug' => "article-{$i}", + 'content' => str_repeat('Word ', 120), 'category' => ['tech', 'science', 'art'][$i % 3], 'priority' => $i % 10, ], range(1, $count)); @@ -170,7 +170,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 1. Product import — 500 items, all fast-checkable --- $scenarios['Product import — 500 items, simple rules'] = [ 'optimizations' => 'Wildcard, fast-check', - 'native' => [ + 'native' => [ ['products' => 'required|array', 'products.*.sku' => 'required|string|max:50|regex:/^SKU-/', 'products.*.name' => 'required|string|min:2|max:255', 'products.*.price' => 'required|numeric|min:0', 'products.*.quantity' => 'required|numeric|integer|min:0', 'products.*.category' => ['required', 'string', Rule::in(['electronics', 'clothing', 'food', 'books', 'toys'])], 'products.*.active' => 'required|boolean', 'products.*.tags' => 'nullable|string|max:50'], ['products' => $products500], ], @@ -183,7 +183,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 2. Nested order lines — 1000 orders × 5 line items --- $scenarios['Nested order lines — 1000 orders × 5 line items'] = [ 'optimizations' => 'Wildcard, fast-check (nested)', - 'native' => [ + 'native' => [ ['orders' => 'required|array', 'orders.*.order_number' => 'required|string|alpha_dash|min:5', 'orders.*.status' => ['required', 'string', Rule::in(['pending', 'processing', 'shipped'])], 'orders.*.line_items' => 'required|array|min:1', 'orders.*.line_items.*.product_id' => 'required|integer|min:1', 'orders.*.line_items.*.quantity' => 'required|integer|min:1', 'orders.*.line_items.*.price' => 'required|numeric|min:0.01'], ['orders' => $orders1000], ], @@ -196,7 +196,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 3. Event scheduling — field-reference date comparisons (partial fast-check) --- $scenarios['Event scheduling — 100 items, field-ref dates'] = [ 'optimizations' => 'Wildcard, partial fast-check', - 'native' => [ + 'native' => [ ['events' => 'required|array', 'events.*.name' => 'required|string|min:3|max:255', 'events.*.start_date' => 'required|date|after:2025-01-01', 'events.*.end_date' => 'required|date|after:events.*.start_date', 'events.*.registration_deadline' => 'required|date|before:events.*.start_date'], ['events' => $events100], ], @@ -209,7 +209,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 4. Article submission — custom Rule objects (wildcard expansion only) --- $scenarios['Article submission — 50 items, custom Rule objects'] = [ 'optimizations' => 'Wildcard only', - 'native' => [ + 'native' => [ ['articles' => 'required|array', 'articles.*.title' => 'required|string|min:3|max:255', 'articles.*.slug' => 'required|string|alpha_dash|max:255', 'articles.*.content' => ['required', 'string', $minWordCount], 'articles.*.category' => ['required', $validCategory], 'articles.*.priority' => ['required', $validPriority]], ['articles' => $articles50], ], @@ -222,95 +222,95 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 5. Conditional import — 100 items, ~47 wildcard patterns with exclude_unless --- $interactionTypes = ['button', 'hotspot', 'scroll_area', 'image', 'chapter', 'menu', 'frame', 'text', 'iframe']; $interactions100 = array_map(fn (int $i): array => [ - 'type' => $interactionTypes[$i % count($interactionTypes)], - 'title' => "Interaction {$i}", - 'start_time' => $i * 10, - 'end_time' => $i * 10 + 5, - 'position' => 'bottom', - 'should_start_collapsed' => false, + 'type' => $interactionTypes[$i % count($interactionTypes)], + 'title' => "Interaction {$i}", + 'start_time' => $i * 10, + 'end_time' => $i * 10 + 5, + 'position' => 'bottom', + 'should_start_collapsed' => false, 'should_collapse_after_menu_item_click' => true, - 'should_pause_when_shown' => false, - 'should_not_use_time' => false, - 'should_use_menu_layout' => false, - 'text' => '

Sample text

', - 'text_stroke' => null, - 'should_fade_volume' => false, - 'sound_url' => null, - 'should_enable_sound' => false, - 'should_fade_in' => true, - 'should_fade_out' => true, - 'image_url' => $interactionTypes[$i % count($interactionTypes)] === 'image' ? 'https://example.com/image.png' : null, - 'style' => [ - 'top' => '10%', 'left' => '20%', 'height' => '30%', 'width' => '40%', + 'should_pause_when_shown' => false, + 'should_not_use_time' => false, + 'should_use_menu_layout' => false, + 'text' => '

Sample text

', + 'text_stroke' => null, + 'should_fade_volume' => false, + 'sound_url' => null, + 'should_enable_sound' => false, + 'should_fade_in' => true, + 'should_fade_out' => true, + 'image_url' => $interactionTypes[$i % count($interactionTypes)] === 'image' ? 'https://example.com/image.png' : null, + 'style' => [ + 'top' => '10%', 'left' => '20%', 'height' => '30%', 'width' => '40%', 'background_color' => '#ff0000', 'border_radius' => 5, - 'padding_top' => 10, 'padding_bottom' => 10, - 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], + 'padding_top' => 10, 'padding_bottom' => 10, + 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], ], - 'action' => ['type' => 'link', 'link' => 'https://example.com', 'time' => 0], + 'action' => ['type' => 'link', 'link' => 'https://example.com', 'time' => 0], 'attributes' => [ 'show_indicator' => true, 'indicator_color' => '#00ff00', 'blinking_speed' => 'normal', - 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], + 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], ], 'chapters' => $i % count($interactionTypes) === 4 ? array_map(fn (int $j): array => [ - 'title' => "Chapter {$j}", 'title_short' => null, + 'title' => "Chapter {$j}", 'title_short' => null, 'start_time' => $j * 5, 'end_time' => $j * 5 + 4, 'sort_order' => $j, ], range(1, 4)) : [], ], range(1, 100)); $conditionalRules = [ - 'interactions' => 'required|array|min:1', - 'interactions.*.type' => ['required', 'string', Rule::in($interactionTypes)], - 'interactions.*.title' => ['nullable', 'string'], - 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], - 'interactions.*.end_time' => ['required', 'numeric', 'gte:interactions.*.start_time'], - 'interactions.*.position' => ['bail', ['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], - 'interactions.*.should_start_collapsed' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], - 'interactions.*.should_collapse_after_menu_item_click' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], - 'interactions.*.should_pause_when_shown' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'nullable', 'boolean'], - 'interactions.*.should_not_use_time' => [['exclude_unless', 'interactions.*.type', 'menu'], 'nullable', 'boolean'], - 'interactions.*.should_use_menu_layout' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'boolean'], - 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], - 'interactions.*.text_stroke' => [['exclude_unless', 'interactions.*.type', 'text'], 'nullable', 'string'], - 'interactions.*.should_fade_volume' => ['boolean'], - 'interactions.*.sound_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'nullable', 'string'], - 'interactions.*.should_enable_sound' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'boolean'], - 'interactions.*.should_fade_in' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], - 'interactions.*.should_fade_out' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], - 'interactions.*.image_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], - 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.height' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.width' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.background_color' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'string'], - 'interactions.*.style.border_radius' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot'], 'nullable', 'numeric', 'integer', 'between:0,360'], - 'interactions.*.style.padding_top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], - 'interactions.*.style.padding_bottom' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], - 'interactions.*.style.border' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'array'], - 'interactions.*.style.border.width' => ['nullable', 'numeric', 'integer', 'min:0'], - 'interactions.*.style.border.style' => ['nullable', 'string', Rule::in(['solid', 'dashed', 'dotted', 'none'])], - 'interactions.*.style.border.color' => ['nullable', 'string'], - 'interactions.*.action' => ['nullable', 'array'], - 'interactions.*.action.type' => ['nullable', 'string', Rule::in(['link', 'time', 'video', 'none'])], - 'interactions.*.action.link' => ['nullable', 'string'], - 'interactions.*.action.time' => ['nullable', 'numeric'], - 'interactions.*.attributes' => ['nullable', 'array'], - 'interactions.*.attributes.show_indicator' => ['nullable', 'boolean'], - 'interactions.*.attributes.indicator_color' => ['nullable', 'string'], - 'interactions.*.attributes.blinking_speed' => ['nullable', 'string', Rule::in(['slow', 'normal', 'fast', 'none'])], - 'interactions.*.attributes.options' => ['nullable', 'array'], + 'interactions' => 'required|array|min:1', + 'interactions.*.type' => ['required', 'string', Rule::in($interactionTypes)], + 'interactions.*.title' => ['nullable', 'string'], + 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], + 'interactions.*.end_time' => ['required', 'numeric', 'gte:interactions.*.start_time'], + 'interactions.*.position' => ['bail', ['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], + 'interactions.*.should_start_collapsed' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], + 'interactions.*.should_collapse_after_menu_item_click' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], + 'interactions.*.should_pause_when_shown' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'nullable', 'boolean'], + 'interactions.*.should_not_use_time' => [['exclude_unless', 'interactions.*.type', 'menu'], 'nullable', 'boolean'], + 'interactions.*.should_use_menu_layout' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'boolean'], + 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], + 'interactions.*.text_stroke' => [['exclude_unless', 'interactions.*.type', 'text'], 'nullable', 'string'], + 'interactions.*.should_fade_volume' => ['boolean'], + 'interactions.*.sound_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'nullable', 'string'], + 'interactions.*.should_enable_sound' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'boolean'], + 'interactions.*.should_fade_in' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], + 'interactions.*.should_fade_out' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], + 'interactions.*.image_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], + 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.height' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.width' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.background_color' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'string'], + 'interactions.*.style.border_radius' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot'], 'nullable', 'numeric', 'integer', 'between:0,360'], + 'interactions.*.style.padding_top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], + 'interactions.*.style.padding_bottom' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], + 'interactions.*.style.border' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'array'], + 'interactions.*.style.border.width' => ['nullable', 'numeric', 'integer', 'min:0'], + 'interactions.*.style.border.style' => ['nullable', 'string', Rule::in(['solid', 'dashed', 'dotted', 'none'])], + 'interactions.*.style.border.color' => ['nullable', 'string'], + 'interactions.*.action' => ['nullable', 'array'], + 'interactions.*.action.type' => ['nullable', 'string', Rule::in(['link', 'time', 'video', 'none'])], + 'interactions.*.action.link' => ['nullable', 'string'], + 'interactions.*.action.time' => ['nullable', 'numeric'], + 'interactions.*.attributes' => ['nullable', 'array'], + 'interactions.*.attributes.show_indicator' => ['nullable', 'boolean'], + 'interactions.*.attributes.indicator_color' => ['nullable', 'string'], + 'interactions.*.attributes.blinking_speed' => ['nullable', 'string', Rule::in(['slow', 'normal', 'fast', 'none'])], + 'interactions.*.attributes.options' => ['nullable', 'array'], 'interactions.*.attributes.options.menu_button_location' => ['nullable', 'string'], - 'interactions.*.attributes.options.menu_button_name' => ['nullable', 'string'], - 'interactions.*.chapters' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'array', 'min:1', 'max:16'], - 'interactions.*.chapters.*.title' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'string'], - 'interactions.*.chapters.*.title_short' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'string'], - 'interactions.*.chapters.*.start_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric', 'min:0'], - 'interactions.*.chapters.*.end_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric'], - 'interactions.*.chapters.*.sort_order' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'integer', 'min:0'], + 'interactions.*.attributes.options.menu_button_name' => ['nullable', 'string'], + 'interactions.*.chapters' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'array', 'min:1', 'max:16'], + 'interactions.*.chapters.*.title' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'string'], + 'interactions.*.chapters.*.title_short' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'string'], + 'interactions.*.chapters.*.start_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric', 'min:0'], + 'interactions.*.chapters.*.end_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric'], + 'interactions.*.chapters.*.sort_order' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'integer', 'min:0'], ]; $scenarios['Conditional import — 100 items, 47 conditional fields'] = [ 'optimizations' => 'Wildcard, pre-evaluation', - 'native' => [ + 'native' => [ $conditionalRules, ['interactions' => $interactions100], ], @@ -323,7 +323,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // --- 6. Login form — no wildcards, minimal rules --- $scenarios['Login form — 3 fields, no wildcards'] = [ 'optimizations' => 'Fast-check (flat)', - 'native' => [ + 'native' => [ ['email' => 'required|string|email|max:255', 'password' => 'required|string|min:8', 'remember' => 'nullable|boolean'], ['email' => 'user@example.com', 'password' => 'secure-password-123', 'remember' => true], ], @@ -350,9 +350,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $results[$label] = [ 'optimizations' => $config['optimizations'], - 'native_ms' => round($native, 1), - 'optimized_ms' => round($ruleSet, 1), - 'speedup' => round($speedup, 1), + 'native_ms' => round($native, 1), + 'optimized_ms' => round($ruleSet, 1), + 'speedup' => round($speedup, 1), ]; } @@ -360,16 +360,16 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // Output // ========================================================================= -$snapshotFile = __DIR__.'/benchmark-snapshot.json'; +$snapshotFile = __DIR__ . '/benchmark-snapshot.json'; if ($isSnapshot) { - file_put_contents($snapshotFile, json_encode($results, JSON_PRETTY_PRINT)."\n"); + file_put_contents($snapshotFile, json_encode($results, JSON_PRETTY_PRINT) . "\n"); echo "Snapshot saved to benchmark-snapshot.json\n"; exit(0); } if ($isJson) { - echo json_encode($results, JSON_PRETTY_PRINT)."\n"; + echo json_encode($results, JSON_PRETTY_PRINT) . "\n"; exit(0); } diff --git a/config/laranail-validation.php b/config/laranail-validation.php index 98fb1ac..5d92d6a 100644 --- a/config/laranail-validation.php +++ b/config/laranail-validation.php @@ -24,7 +24,7 @@ 'aliases' => [ 'enabled' => env('LARANAIL_VALIDATION_ALIASES', false), - 'prefix' => 'laranail_', + 'prefix' => 'laranail_', ], /* diff --git a/rector-migrate-1.0.php b/rector-migrate-1.0.php index 16edec2..6703c8e 100644 --- a/rector-migrate-1.0.php +++ b/rector-migrate-1.0.php @@ -3,9 +3,9 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\Renaming\Rector\MethodCall\RenameMethodRector; use Rector\Renaming\ValueObject\MethodCallRename; use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; +use Rector\Renaming\Rector\MethodCall\RenameMethodRector; /** * The 0.x → 1.0 migration set (UPGRADING.md, v1.0.0). Run it against an diff --git a/rector.php b/rector.php index afec81e..008a7b7 100644 --- a/rector.php +++ b/rector.php @@ -2,21 +2,22 @@ declare(strict_types=1); +use Rector\Config\RectorConfig; +use RectorPest\Set\PestSetList; +use RectorLaravel\Set\LaravelSetList; +use RectorPest\Rules\UseToMatchRector; use Rector\Caching\ValueObject\Storage\FileCacheStorage; -use Rector\Carbon\Rector\FuncCall\DateFuncCallToCarbonRector; -use Rector\CodeQuality\Rector\BooleanOr\RepeatedOrEqualToInArrayRector; -use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector; use Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector; -use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector; -use Rector\Config\RectorConfig; +use Rector\Carbon\Rector\FuncCall\DateFuncCallToCarbonRector; +use Rector\CodingStyle\Rector\PostInc\PostIncDecToPreIncDecRector; use Rector\DeadCode\Rector\ClassMethod\RemoveUselessParamTagRector; use Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; +use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector; +use Rector\CodeQuality\Rector\BooleanOr\RepeatedOrEqualToInArrayRector; +use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector; use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector; use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector; -use RectorLaravel\Set\LaravelSetList; -use RectorPest\Rules\UseToMatchRector; -use RectorPest\Set\PestSetList; return RectorConfig::configure() ->withCache( @@ -25,10 +26,10 @@ containerCacheDirectory: './.cache/rectorContainer', ) ->withPaths([ - __DIR__.'/src', - __DIR__.'/tests', + __DIR__ . '/src', + __DIR__ . '/tests', ]) - ->withSkipPath(__DIR__.'/tests/Fixtures/MacroableFootgun') + ->withSkipPath(__DIR__ . '/tests/Fixtures/MacroableFootgun') ->withPreparedSets( deadCode: true, codeQuality: true, @@ -60,6 +61,11 @@ PestSetList::PEST_LARAVEL, ]) ->withSkip([ + // Pint owns formatting, and it disagrees: Rector rewrites $i++ to ++$i, + // Pint's laravel preset rewrites it straight back. Verified by running + // them in sequence - Rector changed 21 files, Pint reverted every one, + // so the two gates could never both pass. Formatting goes to Pint. + PostIncDecToPreIncDecRector::class, DateFuncCallToCarbonRector::class, NullToStrictStringFuncCallArgRector::class, AddArrowFunctionReturnTypeRector::class, @@ -78,6 +84,6 @@ // Hot-path closure allocates a literal array on every invocation // when in_array() is used. Explicit === comparisons avoid that. RepeatedOrEqualToInArrayRector::class => [ - __DIR__.'/src/FastCheckCompiler.php', + __DIR__ . '/src/FastCheckCompiler.php', ], ]); diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 20b147e..0fb18f6 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -17,27 +17,27 @@ return [ // Banking - 'bic' => 'The :attribute must be a valid BIC/SWIFT code.', + 'bic' => 'The :attribute must be a valid BIC/SWIFT code.', 'bsb_number' => 'The :attribute must be a valid BSB number.', - 'iban' => 'The :attribute must be a valid IBAN.', - 'isin' => 'The :attribute must be a valid ISIN.', - 'luhn' => 'The :attribute must pass the Luhn checksum.', + 'iban' => 'The :attribute must be a valid IBAN.', + 'isin' => 'The :attribute must be a valid ISIN.', + 'luhn' => 'The :attribute must pass the Luhn checksum.', // Codes - 'asin' => 'The :attribute must be a valid ASIN.', - 'ean' => 'The :attribute must be a valid EAN barcode.', - 'gtin' => 'The :attribute must be a valid GTIN.', - 'isbn' => 'The :attribute must be a valid ISBN.', - 'ismn' => 'The :attribute must be a valid ISMN.', + 'asin' => 'The :attribute must be a valid ASIN.', + 'ean' => 'The :attribute must be a valid EAN barcode.', + 'gtin' => 'The :attribute must be a valid GTIN.', + 'isbn' => 'The :attribute must be a valid ISBN.', + 'ismn' => 'The :attribute must be a valid ISMN.', 'upc_e' => 'The :attribute must be a valid UPC-E barcode.', - 'issn' => 'The :attribute must be a valid ISSN.', + 'issn' => 'The :attribute must be a valid ISSN.', // Identifiers 'hash_digest' => 'The :attribute must be a :length-character hex digest.', - 'imei' => 'The :attribute must be a valid IMEI.', - 'jwt' => 'The :attribute must be a valid JSON Web Token.', - 'semver' => 'The :attribute must be a valid semantic version.', - 'vin' => 'The :attribute must be a valid vehicle identification number.', + 'imei' => 'The :attribute must be a valid IMEI.', + 'jwt' => 'The :attribute must be a valid JSON Web Token.', + 'semver' => 'The :attribute must be a valid semantic version.', + 'vin' => 'The :attribute must be a valid vehicle identification number.', // Telecom // @@ -45,21 +45,21 @@ // nothing they can act on; "must be a mobile number" and "must be a number from KE" each name // the thing to change. None of the three upstream Filament phone packages ships any message at // all — each tells you to add one to your own validation.php. - 'phone' => 'The :attribute must be a valid phone number.', - 'phone_possible' => 'The :attribute is not a possible phone number.', - 'phone_country' => 'The :attribute must be a phone number from :country.', - 'phone_type' => 'The :attribute must be a :type number.', - 'phone_extension' => 'The :attribute must not include an extension.', + 'phone' => 'The :attribute must be a valid phone number.', + 'phone_possible' => 'The :attribute is not a possible phone number.', + 'phone_country' => 'The :attribute must be a phone number from :country.', + 'phone_type' => 'The :attribute must be a :type number.', + 'phone_extension' => 'The :attribute must not include an extension.', 'phone_short_code' => 'The :attribute must be a full phone number, not a short code.', - 'phone_emergency' => 'The :attribute must not be an emergency number.', - 'phone_unique' => 'The :attribute has already been taken.', + 'phone_emergency' => 'The :attribute must not be an emergency number.', + 'phone_unique' => 'The :attribute has already been taken.', // Net - 'cidr' => 'The :attribute must be a valid CIDR network.', + 'cidr' => 'The :attribute must be a valid CIDR network.', 'domain_name' => 'The :attribute must be a valid domain name.', - 'private_ip' => 'The :attribute must be a private or reserved IP address.', - 'public_ip' => 'The :attribute must be a publicly routable IP address.', - 'subdomain' => 'The :attribute must be a valid subdomain.', + 'private_ip' => 'The :attribute must be a private or reserved IP address.', + 'public_ip' => 'The :attribute must be a publicly routable IP address.', + 'subdomain' => 'The :attribute must be a valid subdomain.', // // Several keys rather than one, because "that is not a URL" and "that @@ -67,48 +67,48 @@ // single message for both makes a working link look broken for no // stated reason. 'url' => [ - 'credentials' => 'The :attribute must not contain a username or password.', - 'fragment' => 'The :attribute must not contain a fragment.', - 'host' => 'The :attribute must have a valid host.', - 'host_is' => 'The :attribute must be at one of: :hosts.', - 'host_is_not' => 'The :attribute must not be at any of: :hosts.', - 'malformed' => 'The :attribute must be a valid URL.', - 'port' => 'The :attribute must use one of these ports: :ports.', + 'credentials' => 'The :attribute must not contain a username or password.', + 'fragment' => 'The :attribute must not contain a fragment.', + 'host' => 'The :attribute must have a valid host.', + 'host_is' => 'The :attribute must be at one of: :hosts.', + 'host_is_not' => 'The :attribute must not be at any of: :hosts.', + 'malformed' => 'The :attribute must be a valid URL.', + 'port' => 'The :attribute must use one of these ports: :ports.', 'private_host' => 'The :attribute must point at a public address.', - 'query' => 'The :attribute must not contain a query string.', - 'scheme' => 'The :attribute must use one of these schemes: :schemes.', + 'query' => 'The :attribute must not contain a query string.', + 'scheme' => 'The :attribute must use one of these schemes: :schemes.', ], 'mac_address' => [ 'broadcast' => 'The :attribute must not be the broadcast address.', - 'format' => 'The :attribute must be written in one of these notations: :formats.', - 'length' => 'The :attribute must be :bytes bytes.', - 'local' => 'The :attribute must be a manufacturer-assigned address, not a randomised one.', + 'format' => 'The :attribute must be written in one of these notations: :formats.', + 'length' => 'The :attribute must be :bytes bytes.', + 'local' => 'The :attribute must be a manufacturer-assigned address, not a randomised one.', 'malformed' => 'The :attribute must be a valid MAC address.', 'multicast' => 'The :attribute must be a unicast address.', - 'null' => 'The :attribute must not be the null address.', - 'oui' => 'The :attribute must begin with one of: :ouis.', + 'null' => 'The :attribute must not be the null address.', + 'oui' => 'The :attribute must begin with one of: :ouis.', ], 'in_cidr_range' => 'The :attribute must be within one of these networks: :networks.', 'slug' => 'The :attribute must be a URL slug: lowercase letters, digits and single hyphens, with no hyphen at the start or end.', - 'username' => 'The :attribute may contain letters, digits, and single dots, hyphens or underscores between them — not at the start, at the end, or doubled.', + 'username' => 'The :attribute may contain letters, digits, and single dots, hyphens or underscores between them — not at the start, at the end, or doubled.', 'username_reserved' => 'The :attribute is reserved. Please choose another.', // Three keys rather than one. A field whose characters were fine and whose count was not gets // sent looking for a bad character that is not there. - 'person_name' => 'The :attribute must be a name: letters, marks, spaces, apostrophes and hyphens.', - 'person_name_min' => 'The :attribute must contain at least :min names.', - 'person_name_max' => 'The :attribute must not contain more than :max names.', + 'person_name' => 'The :attribute must be a name: letters, marks, spaces, apostrophes and hyphens.', + 'person_name_min' => 'The :attribute must contain at least :min names.', + 'person_name_max' => 'The :attribute must not contain more than :max names.', 'person_name_required' => 'Please provide at least one of :values.', 'contains_html' => 'The :attribute must contain HTML markup.', - 'html_clean' => 'The :attribute must not contain HTML tags.', - 'max_words' => 'The :attribute must not contain more than :max words.', - 'min_words' => 'The :attribute must contain at least :min words.', - 'salutation' => 'The :attribute must be a recognised salutation.', + 'html_clean' => 'The :attribute must not contain HTML tags.', + 'max_words' => 'The :attribute must not contain more than :max words.', + 'min_words' => 'The :attribute must contain at least :min words.', + 'salutation' => 'The :attribute must be a recognised salutation.', 'without_spaces' => 'The :attribute must not contain any spaces, including non-breaking and zero-width ones.', @@ -117,63 +117,63 @@ // Networking probes 'has_gravatar' => 'The :attribute must have a Gravatar.', - 'image_url' => 'The :attribute must be a URL serving an image.', + 'image_url' => 'The :attribute must be a URL serving an image.', // Payment - 'card_cvc' => 'The :attribute must be a valid security code.', - 'card_expiry' => 'The :attribute must be a valid expiry date that has not passed.', - 'card_number' => 'The :attribute is not a recognised card number.', - 'card_number_brand' => 'The :attribute must be one of these card brands: :brands.', + 'card_cvc' => 'The :attribute must be a valid security code.', + 'card_expiry' => 'The :attribute must be a valid expiry date that has not passed.', + 'card_number' => 'The :attribute is not a recognised card number.', + 'card_number_brand' => 'The :attribute must be one of these card brands: :brands.', 'card_number_checksum' => 'The :attribute fails the card number check digit.', - 'card_number_length' => 'The :attribute must be :lengths digits for :brand.', + 'card_number_length' => 'The :attribute must be :lengths digits for :brand.', // Chrono - 'date_interval' => 'The :attribute must be an ISO 8601 duration.', + 'date_interval' => 'The :attribute must be an ISO 8601 duration.', 'date_interval_positive' => 'The :attribute must be a non-zero ISO 8601 duration.', - 'max_date_difference' => 'The :attribute must be within :hours hours of the reference date.', - 'minimum_age' => 'The :attribute must be a date of birth at least :years years ago.', - 'minute_in' => 'The :attribute must be a time whose minutes are one of: :minutes.', - 'rfc3339' => 'The :attribute must be an RFC 3339 timestamp.', - 'time_of_day' => 'The :attribute must be a valid time of day.', - 'timezone_abbreviation' => 'The :attribute must be a timezone abbreviation.', - 'unix_timestamp' => 'The :attribute must be a Unix timestamp.', + 'max_date_difference' => 'The :attribute must be within :hours hours of the reference date.', + 'minimum_age' => 'The :attribute must be a date of birth at least :years years ago.', + 'minute_in' => 'The :attribute must be a time whose minutes are one of: :minutes.', + 'rfc3339' => 'The :attribute must be an RFC 3339 timestamp.', + 'time_of_day' => 'The :attribute must be a valid time of day.', + 'timezone_abbreviation' => 'The :attribute must be a timezone abbreviation.', + 'unix_timestamp' => 'The :attribute must be a Unix timestamp.', // Encoding - 'base64' => 'The :attribute must be a base64-encoded value.', - 'base64_image' => 'The :attribute must be a base64-encoded image of an accepted type.', + 'base64' => 'The :attribute must be a base64-encoded value.', + 'base64_image' => 'The :attribute must be a base64-encoded image of an accepted type.', 'base64_image_size' => 'The :attribute must be an image no larger than :max.', - 'data_uri' => 'The :attribute must be a valid data URI.', + 'data_uri' => 'The :attribute must be a valid data URI.', // I18n - 'country_code' => 'The :attribute must be a valid ISO 3166-1 country code.', - 'currency_code' => 'The :attribute must be a valid ISO 4217 currency code.', + 'country_code' => 'The :attribute must be a valid ISO 3166-1 country code.', + 'currency_code' => 'The :attribute must be a valid ISO 4217 currency code.', 'currency_code_numeric' => 'The :attribute must be a valid ISO 4217 numeric currency code.', - 'currency_symbol' => 'The :attribute must be a recognised currency symbol.', - 'language_code' => 'The :attribute must be a valid ISO 639-1 language code.', + 'currency_symbol' => 'The :attribute must be a recognised currency symbol.', + 'language_code' => 'The :attribute must be a valid ISO 639-1 language code.', // Geo 'ca_province' => 'The :attribute must be a Canadian province or territory.', - 'lat_lng' => 'The :attribute must be a latitude,longitude pair.', - 'latitude' => 'The :attribute must be a latitude between -90 and 90.', - 'longitude' => 'The :attribute must be a longitude between -180 and 180.', - 'us_state' => 'The :attribute must be a US state.', + 'lat_lng' => 'The :attribute must be a latitude,longitude pair.', + 'latitude' => 'The :attribute must be a latitude between -90 and 90.', + 'longitude' => 'The :attribute must be a longitude between -180 and 180.', + 'us_state' => 'The :attribute must be a US state.', // Email 'email' => [ - 'disposable' => 'The :attribute must not be a disposable email address.', - 'domain_is' => 'The :attribute must be at one of: :domains.', + 'disposable' => 'The :attribute must not be a disposable email address.', + 'domain_is' => 'The :attribute must be at one of: :domains.', 'domain_is_not' => 'The :attribute must not be at any of: :domains.', - 'malformed' => 'The :attribute must be a valid email address.', - 'role' => 'The :attribute must belong to a person, not a shared mailbox.', - 'subaddress' => 'The :attribute must not contain a plus tag.', + 'malformed' => 'The :attribute must be a valid email address.', + 'role' => 'The :attribute must belong to a person, not a shared mailbox.', + 'subaddress' => 'The :attribute must not contain a plus tag.', 'undeliverable' => 'The :attribute is at a domain that cannot receive mail.', ], // Database - 'authorized' => 'The selected :attribute is invalid.', + 'authorized' => 'The selected :attribute is invalid.', 'compare_to_column' => 'The :attribute is outside the allowed bound.', - 'models_exist' => [ - 'array' => 'The :attribute must be a list of identifiers.', + 'models_exist' => [ + 'array' => 'The :attribute must be a list of identifiers.', 'missing' => 'The :attribute contains values that do not exist: :values.', ], @@ -183,15 +183,15 @@ // the field holds a dozen values. Each key names what to change. 'delimited' => [ 'distinct' => 'The :attribute must not repeat an entry.', - 'empty' => 'Entry :position of :attribute is empty — check for a stray separator.', - 'invalid' => 'The :attribute must be a delimited list.', - 'item' => 'Entry :position of :attribute is not valid.', - 'max' => 'The :attribute must not have more than :max entries.', - 'min' => 'The :attribute must have at least :min entries.', + 'empty' => 'Entry :position of :attribute is empty — check for a stray separator.', + 'invalid' => 'The :attribute must be a delimited list.', + 'item' => 'Entry :position of :attribute is not valid.', + 'max' => 'The :attribute must not have more than :max entries.', + 'min' => 'The :attribute must have at least :min entries.', ], // Crypto - 'bitcoin_address' => 'The :attribute must be a valid Bitcoin address.', + 'bitcoin_address' => 'The :attribute must be a valid Bitcoin address.', 'ethereum_address' => 'The :attribute must be a valid Ethereum address.', // Postal @@ -200,7 +200,7 @@ // Numbers 'parity' => [ 'even' => 'The :attribute must be an even number.', - 'odd' => 'The :attribute must be an odd number.', + 'odd' => 'The :attribute must be an odd number.', ], 'monetary_amount' => 'The :attribute must be an amount with at most :decimals decimal places.', @@ -208,17 +208,17 @@ 'css_color' => 'The :attribute must be a colour in one of these notations: :notations.', // Anti-spam - 'honeypot' => 'The :attribute could not be submitted.', + 'honeypot' => 'The :attribute could not be submitted.', 'submission_timing' => [ 'too_fast' => 'The form was submitted too quickly. Please try again.', - 'expired' => 'The form has expired. Please reload and try again.', + 'expired' => 'The form has expired. Please reload and try again.', ], // Vendor identifiers 'vendor_identifier' => 'The :attribute must be a valid :vendor identifier.', // Fiscal - 'vat_number' => 'The :attribute must be a valid VAT number.', + 'vat_number' => 'The :attribute must be a valid VAT number.', 'national_identifier' => 'The :attribute must be a valid :country national identification number.', // Profanity @@ -226,8 +226,8 @@ // Markup 'xml' => [ - 'malformed' => 'The :attribute must be well-formed XML.', - 'schema' => 'The :attribute does not match the required schema.', + 'malformed' => 'The :attribute must be well-formed XML.', + 'schema' => 'The :attribute does not match the required schema.', 'schema_missing' => 'The :attribute could not be checked: the schema is unavailable.', ], @@ -236,11 +236,11 @@ | parameter rather than five classes differing by a pattern and a message. */ 'case_style' => [ - 'camel' => 'The :attribute must be camelCase.', - 'kebab' => 'The :attribute must be kebab-case.', + 'camel' => 'The :attribute must be camelCase.', + 'kebab' => 'The :attribute must be kebab-case.', 'pascal' => 'The :attribute must be PascalCase.', - 'snake' => 'The :attribute must be snake_case.', - 'title' => 'The :attribute must be Title Case.', + 'snake' => 'The :attribute must be snake_case.', + 'title' => 'The :attribute must be Title Case.', ], ]; diff --git a/src/Actions/CachedDnsResolver.php b/src/Actions/CachedDnsResolver.php index 573071e..a150859 100644 --- a/src/Actions/CachedDnsResolver.php +++ b/src/Actions/CachedDnsResolver.php @@ -4,10 +4,10 @@ namespace Simtabi\Laranail\Validation\Actions; -use Illuminate\Contracts\Cache\Repository; +use Throwable; use Illuminate\Support\Facades\Cache; +use Illuminate\Contracts\Cache\Repository; use Simtabi\Laranail\Validation\Contracts\Email\DnsResolver; -use Throwable; /** * The default {@see DnsResolver}: a cached MX lookup over PHP's resolver. @@ -68,7 +68,7 @@ public function hasMailExchanger(string $domain): bool } /** - * @param callable(): bool $callback + * @param callable(): bool $callback */ private function remember(string $domain, callable $callback): bool { @@ -80,7 +80,7 @@ private function remember(string $domain, callable $callback): bool try { $value = $store->remember( - self::CACHE_PREFIX.$domain, + self::CACHE_PREFIX . $domain, $this->ttl ?? $this->configuredTtl(), static fn (): bool => $callback(), ); diff --git a/src/BatchDatabaseChecker.php b/src/BatchDatabaseChecker.php index aaa3ea3..ab5d0f2 100644 --- a/src/BatchDatabaseChecker.php +++ b/src/BatchDatabaseChecker.php @@ -4,19 +4,19 @@ namespace Simtabi\Laranail\Validation; -use Illuminate\Validation\DatabasePresenceVerifier; -use Illuminate\Validation\PresenceVerifierInterface; +use Throwable; +use Stringable; +use ReflectionProperty; +use ReflectionException; use Illuminate\Validation\Rules\Exists; use Illuminate\Validation\Rules\Unique; -use ReflectionException; -use ReflectionProperty; -use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; -use Simtabi\Laranail\Validation\Internal\BatchPresenceQuery; +use Illuminate\Validation\DatabasePresenceVerifier; +use Illuminate\Validation\PresenceVerifierInterface; use Simtabi\Laranail\Validation\Internal\BatchRuleShape; -use Simtabi\Laranail\Validation\Internal\PresenceMatchFidelity; +use Simtabi\Laranail\Validation\Internal\BatchPresenceQuery; use Simtabi\Laranail\Validation\Internal\ValueTypePredicates; -use Stringable; -use Throwable; +use Simtabi\Laranail\Validation\Internal\PresenceMatchFidelity; +use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; /** * Batches exists/unique database validation queries for wildcard arrays. @@ -80,7 +80,8 @@ public static function isBatchable(Exists|Unique $rule): bool /** * Batch-query which values exist in the database for an Exists rule. * - * @param array $values Deduplicated, non-null values to check + * @param array $values Deduplicated, non-null values to check + * * @return array Values that exist in the DB */ public static function fetchExisting(array $values, Exists $rule): array @@ -101,7 +102,8 @@ public static function fetchExisting(array $values, Exists $rule): array /** * Batch-query which values are already taken for a Unique rule. * - * @param array $values Deduplicated, non-null values to check + * @param array $values Deduplicated, non-null values to check + * * @return array Values that already exist (taken) */ public static function fetchTaken(array $values, Unique $rule): array @@ -166,7 +168,8 @@ public static function batchableSignature(Exists|Unique $rule, string $table, st /** * Find batchable Exists/Unique rules in a set of compiled rules. * - * @param array $rules + * @param array $rules + * * @return array Field name → rule object */ public static function findBatchableRules(array $rules): array @@ -192,9 +195,10 @@ public static function findBatchableRules(array $rules): array /** * Collect values from items for batchable rules and group by table:column. * - * @param array $batchableFields - * @param array $items - * @param array $fieldRules Per-field rule array used to derive type predicates. + * @param array $batchableFields + * @param array $items + * @param array $fieldRules Per-field rule array used to derive type predicates. + * * @return array}> */ public static function collectValues(array $batchableFields, array $items, bool $isScalar, array $fieldRules = []): array @@ -263,8 +267,9 @@ public static function collectValues(array $batchableFields, array $items, bool * Unlike collectValues() which iterates items, this iterates expanded * rules like 'items.0.email', 'items.1.email' and uses data_get(). * - * @param array $preparedRules Expanded rules with concrete paths - * @param array $data + * @param array $preparedRules Expanded rules with concrete paths + * @param array $data + * * @return array}> */ public static function collectExpandedValues(array $preparedRules, array $data): array @@ -331,8 +336,9 @@ public static function collectExpandedValues(array $preparedRules, array $data): * Object-form rules fall through unchanged; if no known type rule is * present, values are returned as-is (matches previous behaviour). * - * @param array $values - * @param array|string $itemRules Either a pipe-delimited string or an array of rules. + * @param array $values + * @param array|string $itemRules Either a pipe-delimited string or an array of rules. + * * @return array */ public static function filterValuesByType(array $values, array|string $itemRules): array @@ -346,9 +352,9 @@ public static function filterValuesByType(array $values, array|string $itemRules * Groups arrive already filtered (Phase 1) and deduplicated (collectors * own the dedup). Canonical order is filter → dedup → cap check → query. * - * @param array}> $groups - * @param array $poisonedTableColumns Extra table:columns that must not - * register a lookup ({@see findPoisonedTableColumns}) + * @param array}> $groups + * @param array $poisonedTableColumns Extra table:columns that must not + * register a lookup ({@see findPoisonedTableColumns}) * * @throws BatchLimitExceededException When any group's value count exceeds `$maxValuesPerGroup`. */ @@ -393,11 +399,12 @@ public static function buildVerifier(array $groups, array $poisonedTableColumns * handles each rule correctly via per-item queries. Rare case, small * perf hit — exists and unique on the same (table, column) is unusual. * - * @param array}> $groups Keyed by "table:column:ruleType" - * @param array $poisonedTableColumns Table:columns claimed by rules the - * collectors never saw (a field's second DB rule, - * non-batchable or string-form rules) — treated - * exactly like a detected group conflict. + * @param array}> $groups Keyed by "table:column:ruleType" + * @param array $poisonedTableColumns Table:columns claimed by rules the + * collectors never saw (a field's second DB rule, + * non-batchable or string-form rules) — treated + * exactly like a detected group conflict. + * * @return bool False if any group was skipped, so the caller knows a * fallback verifier is required to answer it. */ @@ -415,7 +422,7 @@ public static function registerLookups(PrecomputedPresenceVerifier $verifier, ar [$table, $column] = self::parseGroupKey($key); - if (isset($conflicting[$table.':'.$column])) { + if (isset($conflicting[$table . ':' . $column])) { $complete = false; continue; @@ -440,7 +447,8 @@ public static function registerLookups(PrecomputedPresenceVerifier $verifier, ar /** * Deduplicate and cast values to strings for batch queries. * - * @param array $values + * @param array $values + * * @return list */ public static function uniqueStringValues(array $values): array @@ -461,9 +469,9 @@ public static function uniqueStringValues(array $values): array */ private static function groupKey(string $table, string $column, Exists|Unique $rule): string { - return $table.':'.$column.':' - .($rule instanceof Unique ? 'unique' : 'exists').':' - .BatchRuleShape::of(self::extractMeta($rule)); + return $table . ':' . $column . ':' + . ($rule instanceof Unique ? 'unique' : 'exists') . ':' + . BatchRuleShape::of(self::extractMeta($rule)); } /** @@ -471,7 +479,7 @@ private static function groupKey(string $table, string $column, Exists|Unique $r * Called after filter/dedup so the cap sees the same normalised set the * query will. * - * @param array}> $groups + * @param array}> $groups */ private static function assertWithinCap(array $groups): void { @@ -500,7 +508,8 @@ private static function assertWithinCap(array $groups): void * one rule type in the grouped rules (i.e. both an exists and a unique * group exist for the same physical column). * - * @param array}> $groups + * @param array}> $groups + * * @return array */ private static function findConflictingTableColumns(array $groups): array @@ -514,7 +523,7 @@ private static function findConflictingTableColumns(array $groups): array // Keyed by rule type AND query shape: the verifier looks up by // table and column only, so anything that would have queried // differently cannot share that lookup. - $seen[$table.':'.$column][$ruleType.':'.$shape] = true; + $seen[$table . ':' . $column][$ruleType . ':' . $shape] = true; } $conflicting = []; @@ -584,11 +593,11 @@ private static function extractMeta(Exists|Unique $rule): ?array return [ 'connection' => $connection, - 'table' => $table, - 'column' => $column === 'NULL' ? '' : $column, - 'wheres' => $typedWheres, - 'ignore' => $ignore, - 'idColumn' => $idColumn, + 'table' => $table, + 'column' => $column === 'NULL' ? '' : $column, + 'wheres' => $typedWheres, + 'ignore' => $ignore, + 'idColumn' => $idColumn, ]; } catch (ReflectionException) { return null; diff --git a/src/Builder/Concerns/HasEmbeddedRules.php b/src/Builder/Concerns/HasEmbeddedRules.php index 5273037..2485cb7 100644 --- a/src/Builder/Concerns/HasEmbeddedRules.php +++ b/src/Builder/Concerns/HasEmbeddedRules.php @@ -4,10 +4,10 @@ namespace Simtabi\Laranail\Validation\Builder\Concerns; -use BackedEnum; use Closure; -use Illuminate\Contracts\Support\Arrayable; +use BackedEnum; use Illuminate\Validation\Rule; +use Illuminate\Contracts\Support\Arrayable; trait HasEmbeddedRules { diff --git a/src/Builder/Concerns/HasFieldModifiers.php b/src/Builder/Concerns/HasFieldModifiers.php index 8967644..14bd115 100644 --- a/src/Builder/Concerns/HasFieldModifiers.php +++ b/src/Builder/Concerns/HasFieldModifiers.php @@ -4,24 +4,24 @@ namespace Simtabi\Laranail\Validation\Builder\Concerns; -use BackedEnum; use Closure; +use BackedEnum; +use LogicException; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; use Illuminate\Validation\Rule; -use Illuminate\Validation\Rules\Contains; -use Illuminate\Validation\Rules\DoesntContain; -use Illuminate\Validation\Rules\ExcludeIf; -use Illuminate\Validation\Rules\ExcludeUnless; -use Illuminate\Validation\Rules\Exists; use Illuminate\Validation\Rules\In; use Illuminate\Validation\Rules\NotIn; -use Illuminate\Validation\Rules\ProhibitedIf; -use Illuminate\Validation\Rules\ProhibitedUnless; +use Illuminate\Validation\Rules\Exists; +use Illuminate\Validation\Rules\Unique; +use Illuminate\Validation\Rules\Contains; +use Illuminate\Validation\Rules\ExcludeIf; use Illuminate\Validation\Rules\RequiredIf; +use Illuminate\Validation\Rules\ProhibitedIf; +use Illuminate\Validation\Rules\DoesntContain; +use Illuminate\Validation\Rules\ExcludeUnless; use Illuminate\Validation\Rules\RequiredUnless; -use Illuminate\Validation\Rules\Unique; -use LogicException; +use Illuminate\Validation\Rules\ProhibitedUnless; trait HasFieldModifiers { @@ -132,22 +132,22 @@ public function present(?string $message = null): static public function presentIf(string $field, string|int|bool|BackedEnum ...$values): static { - return $this->addRule('present_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('present_if:' . $field . ',' . self::serializeValues($values)); } public function presentUnless(string $field, string|int|bool|BackedEnum ...$values): static { - return $this->addRule('present_unless:'.$field.','.self::serializeValues($values)); + return $this->addRule('present_unless:' . $field . ',' . self::serializeValues($values)); } public function presentWith(string ...$fields): static { - return $this->addRule('present_with:'.implode(',', $fields)); + return $this->addRule('present_with:' . implode(',', $fields)); } public function presentWithAll(string ...$fields): static { - return $this->addRule('present_with_all:'.implode(',', $fields)); + return $this->addRule('present_with_all:' . implode(',', $fields)); } public function prohibited(?string $message = null): static @@ -167,22 +167,22 @@ public function missing(?string $message = null): static public function missingIf(string $field, string|int|bool|BackedEnum ...$values): static { - return $this->addRule('missing_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('missing_if:' . $field . ',' . self::serializeValues($values)); } public function missingUnless(string $field, string|int|bool|BackedEnum ...$values): static { - return $this->addRule('missing_unless:'.$field.','.self::serializeValues($values)); + return $this->addRule('missing_unless:' . $field . ',' . self::serializeValues($values)); } public function missingWith(string ...$fields): static { - return $this->addRule('missing_with:'.implode(',', $fields)); + return $this->addRule('missing_with:' . implode(',', $fields)); } public function missingWithAll(string ...$fields): static { - return $this->addRule('missing_with_all:'.implode(',', $fields)); + return $this->addRule('missing_with_all:' . implode(',', $fields)); } public function requiredIf(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -191,7 +191,7 @@ public function requiredIf(Closure|bool|string $field, string|int|bool|BackedEnu return $this->addRule(new RequiredIf($field)); } - return $this->addRule('required_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('required_if:' . $field . ',' . self::serializeValues($values)); } public function requiredUnless(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -205,37 +205,37 @@ public function requiredUnless(Closure|bool|string $field, string|int|bool|Backe return $this->addRule(new RequiredUnless($field)); } - return $this->addRule('required_unless:'.$field.','.self::serializeValues($values)); + return $this->addRule('required_unless:' . $field . ',' . self::serializeValues($values)); } public function requiredWith(string ...$fields): static { - return $this->addRule('required_with:'.implode(',', $fields)); + return $this->addRule('required_with:' . implode(',', $fields)); } public function requiredWithAll(string ...$fields): static { - return $this->addRule('required_with_all:'.implode(',', $fields)); + return $this->addRule('required_with_all:' . implode(',', $fields)); } public function requiredWithout(string ...$fields): static { - return $this->addRule('required_without:'.implode(',', $fields)); + return $this->addRule('required_without:' . implode(',', $fields)); } public function requiredWithoutAll(string ...$fields): static { - return $this->addRule('required_without_all:'.implode(',', $fields)); + return $this->addRule('required_without_all:' . implode(',', $fields)); } public function requiredIfAccepted(string $field, ?string $message = null): static { - return $this->addRule('required_if_accepted:'.$field, $message); + return $this->addRule('required_if_accepted:' . $field, $message); } public function requiredIfDeclined(string $field, ?string $message = null): static { - return $this->addRule('required_if_declined:'.$field, $message); + return $this->addRule('required_if_declined:' . $field, $message); } public function excludeIf(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -251,7 +251,7 @@ public function excludeIf(Closure|bool|string $field, string|int|bool|BackedEnum return $shouldExclude ? $this->addRule('exclude') : $this; } - return $this->addRule('exclude_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('exclude_if:' . $field . ',' . self::serializeValues($values)); } public function excludeUnless(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -267,17 +267,17 @@ public function excludeUnless(Closure|bool|string $field, string|int|bool|Backed return $shouldExclude ? $this->addRule('exclude') : $this; } - return $this->addRule('exclude_unless:'.$field.','.self::serializeValues($values)); + return $this->addRule('exclude_unless:' . $field . ',' . self::serializeValues($values)); } public function excludeWith(string $field): static { - return $this->addRule('exclude_with:'.$field); + return $this->addRule('exclude_with:' . $field); } public function excludeWithout(string $field): static { - return $this->addRule('exclude_without:'.$field); + return $this->addRule('exclude_without:' . $field); } public function prohibitedIf(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -286,7 +286,7 @@ public function prohibitedIf(Closure|bool|string $field, string|int|bool|BackedE return $this->addRule(new ProhibitedIf($field)); } - return $this->addRule('prohibited_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('prohibited_if:' . $field . ',' . self::serializeValues($values)); } public function prohibitedUnless(Closure|bool|string $field, string|int|bool|BackedEnum ...$values): static @@ -298,22 +298,22 @@ public function prohibitedUnless(Closure|bool|string $field, string|int|bool|Bac return $this->addRule(new ProhibitedUnless($field)); } - return $this->addRule('prohibited_unless:'.$field.','.self::serializeValues($values)); + return $this->addRule('prohibited_unless:' . $field . ',' . self::serializeValues($values)); } public function prohibits(string ...$fields): static { - return $this->addRule('prohibits:'.implode(',', $fields)); + return $this->addRule('prohibits:' . implode(',', $fields)); } public function prohibitedIfAccepted(string $field, ?string $message = null): static { - return $this->addRule('prohibited_if_accepted:'.$field, $message); + return $this->addRule('prohibited_if_accepted:' . $field, $message); } public function prohibitedIfDeclined(string $field, ?string $message = null): static { - return $this->addRule('prohibited_if_declined:'.$field, $message); + return $this->addRule('prohibited_if_declined:' . $field, $message); } /** @@ -328,14 +328,14 @@ public function prohibitedIfDeclined(string $field, ?string $message = null): st * instance — there is no defensive copy. Clone first if you need * isolation: `(clone $ruleSet->get($field))->rule($extra)`. * - * @param object|string|array $rule + * @param object|string|array $rule */ public function rule(object|string|array $rule, ?string $message = null): static { if (is_array($rule)) { $params = array_slice($rule, 1); - return $this->addRule($params === [] ? $rule[0] : $rule[0].':'.implode(',', $params), $message); + return $this->addRule($params === [] ? $rule[0] : $rule[0] . ':' . implode(',', $params), $message); } return $this->addRule($rule, $message); @@ -353,9 +353,9 @@ public function rule(object|string|array $rule, ?string $message = null): static * fn ($r) => $r->sometimes()->max(100), * ) * - * @param Closure(Fluent): bool $condition - * @param Closure(static): static|string|list $rules - * @param Closure(static): static|string|list $defaultRules + * @param Closure(Fluent): bool $condition + * @param Closure(static): static|string|list $rules + * @param Closure(static): static|string|list $defaultRules */ public function whenInput(Closure $condition, Closure|string|array $rules, Closure|string|array $defaultRules = []): static { @@ -385,7 +385,7 @@ protected function seedLastConstraint(string $name): void * resolved $lastConstraint key. Throws LogicException if $message is * set but no rule was added (e.g. an early-return branch in a caller). * - * @param array|string|object $rules + * @param array|string|object $rules */ protected function addRule(array|string|object $rules, ?string $message = null): static { @@ -395,19 +395,19 @@ protected function addRule(array|string|object $rules, ?string $message = null): $this->rules[] = $rules; $this->lastConstraint = match (true) { - $rules instanceof RequiredIf => 'required', - $rules instanceof RequiredUnless => 'required', - $rules instanceof ProhibitedIf => 'prohibited', + $rules instanceof RequiredIf => 'required', + $rules instanceof RequiredUnless => 'required', + $rules instanceof ProhibitedIf => 'prohibited', $rules instanceof ProhibitedUnless => 'prohibited', - $rules instanceof ExcludeIf => 'exclude', - $rules instanceof ExcludeUnless => 'exclude', - $rules instanceof In => 'in', - $rules instanceof NotIn => 'not_in', - $rules instanceof Unique => 'unique', - $rules instanceof Exists => 'exists', - $rules instanceof Contains => 'contains', - $rules instanceof DoesntContain => 'doesnt_contain', - default => lcfirst(class_basename($rules)), + $rules instanceof ExcludeIf => 'exclude', + $rules instanceof ExcludeUnless => 'exclude', + $rules instanceof In => 'in', + $rules instanceof NotIn => 'not_in', + $rules instanceof Unique => 'unique', + $rules instanceof Exists => 'exists', + $rules instanceof Contains => 'contains', + $rules instanceof DoesntContain => 'doesnt_contain', + default => lcfirst(class_basename($rules)), }; } else { $this->constraints = array_merge($this->constraints, Arr::wrap($rules)); @@ -432,7 +432,8 @@ protected function addRule(array|string|object $rules, ?string $message = null): * appear before type and size constraints ("required" must come * before "must be a string"). * - * @param list $constraints + * @param list $constraints + * * @return list */ protected function reorderConstraints(array $constraints): array @@ -457,8 +458,8 @@ private static function serializeValues(array $values): string return implode(',', array_map( static fn (string|int|bool|BackedEnum $v): string|int => match (true) { $v instanceof BackedEnum => $v->value, - is_bool($v) => $v ? '1' : '0', - default => $v, + is_bool($v) => $v ? '1' : '0', + default => $v, }, $values, )); @@ -474,7 +475,8 @@ private function isPresenceConstraint(string $constraint): bool } /** - * @param Closure(static): static|string|list $rules + * @param Closure(static): static|string|list $rules + * * @return string|list */ private function compileConditionalBranch(Closure|string|array $rules): string|array diff --git a/src/Builder/Concerns/SelfValidates.php b/src/Builder/Concerns/SelfValidates.php index f8666f9..4183987 100644 --- a/src/Builder/Concerns/SelfValidates.php +++ b/src/Builder/Concerns/SelfValidates.php @@ -5,18 +5,18 @@ namespace Simtabi\Laranail\Validation\Builder\Concerns; use Closure; +use Stringable; +use ReflectionProperty; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\Rules\ExcludeIf; -use Illuminate\Validation\Rules\ExcludeUnless; use Illuminate\Validation\Rules\In; use Illuminate\Validation\Rules\NotIn; -use Illuminate\Validation\Rules\ProhibitedIf; -use Illuminate\Validation\Rules\ProhibitedUnless; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rules\ExcludeIf; use Illuminate\Validation\Rules\RequiredIf; +use Illuminate\Validation\Rules\ProhibitedIf; +use Illuminate\Validation\Rules\ExcludeUnless; use Illuminate\Validation\Rules\RequiredUnless; -use ReflectionProperty; -use Stringable; +use Illuminate\Validation\Rules\ProhibitedUnless; trait SelfValidates { @@ -189,7 +189,7 @@ private function buildMessages(string $attribute): array $messages = $this->validator->customMessages ?? []; foreach ($this->getCustomMessages() as $ruleName => $message) { - $messages[$ruleName === '' ? $attribute : $attribute.'.'.$ruleName] = $message; + $messages[$ruleName === '' ? $attribute : $attribute . '.' . $ruleName] = $message; } return $messages; @@ -427,7 +427,7 @@ private function buildCompiledRules(): string|array * `regex:/^(a|b)$/` pattern) cannot be safely pipe-joined — Laravel's parser * would split it. Rules with one fall back to array form instead. * - * @param list $rules + * @param list $rules */ private function anyContainsPipe(array $rules): bool { diff --git a/src/Builder/Nodes/AcceptedRule.php b/src/Builder/Nodes/AcceptedRule.php index 7ade6f1..3b98c5a 100644 --- a/src/Builder/Nodes/AcceptedRule.php +++ b/src/Builder/Nodes/AcceptedRule.php @@ -4,13 +4,13 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class AcceptedRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -36,7 +36,7 @@ public function acceptedIf(string $field, string|int|bool ...$values): static static fn (string $rule): bool => $rule !== 'accepted', )); - return $this->addRule('accepted_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('accepted_if:' . $field . ',' . self::serializeValues($values)); } /** @return list */ diff --git a/src/Builder/Nodes/ArrayRule.php b/src/Builder/Nodes/ArrayRule.php index 280d8be..27a459f 100644 --- a/src/Builder/Nodes/ArrayRule.php +++ b/src/Builder/Nodes/ArrayRule.php @@ -4,22 +4,22 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use UnitEnum; use BackedEnum; +use LogicException; +use InvalidArgumentException; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Validation\Rules\Contains; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Support\Traits\Conditionable; +use Illuminate\Validation\Rules\DoesntContain; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Illuminate\Validation\Rules\Contains; -use Illuminate\Validation\Rules\DoesntContain; -use InvalidArgumentException; -use LogicException; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; use Simtabi\Laranail\Validation\Exceptions\CannotExtendListShapedEach; -use UnitEnum; class ArrayRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -143,7 +143,7 @@ public function addEachRule(string $key, ValidationRule $rule): static if (array_key_exists($key, $existing)) { throw new LogicException(sprintf( "addEachRule('%s'): key '%s' already exists in each(). " - .'Use mergeEachRules() if replacement is intentional.', + . 'Use mergeEachRules() if replacement is intentional.', $key, $key, )); @@ -159,7 +159,7 @@ public function addEachRule(string $key, ValidationRule $rule): static * Merge multiple keyed sub-rules into the current each() shape, * later-wins on collision. * - * @param array $rules + * @param array $rules * * @throws CannotExtendListShapedEach when `eachRules` is list-shaped. */ @@ -187,7 +187,7 @@ public function mergeEachRules(array $rules): static * Unlike each() which produces wildcard paths (items.*.name), * children() produces fixed paths (search.value, search.regex). * - * @param array $rules + * @param array $rules */ public function children(array $rules): static { @@ -214,22 +214,22 @@ public function withoutEachRules(): static public function min(int $value, ?string $message = null): static { - return $this->addRule('min:'.$value, $message); + return $this->addRule('min:' . $value, $message); } public function max(int $value, ?string $message = null): static { - return $this->addRule('max:'.$value, $message); + return $this->addRule('max:' . $value, $message); } public function between(int $min, int $max, ?string $message = null): static { - return $this->addRule('between:'.$min.','.$max, $message); + return $this->addRule('between:' . $min . ',' . $max, $message); } public function exactly(int $value, ?string $message = null): static { - return $this->addRule('size:'.$value, $message); + return $this->addRule('size:' . $value, $message); } public function list(?string $message = null): static @@ -239,16 +239,16 @@ public function list(?string $message = null): static public function requiredArrayKeys(string ...$keys): static { - return $this->addRule('required_array_keys:'.implode(',', $keys)); + return $this->addRule('required_array_keys:' . implode(',', $keys)); } public function distinct(?string $mode = null, ?string $message = null): static { - return $this->addRule($mode ? 'distinct:'.$mode : 'distinct', $message); + return $this->addRule($mode ? 'distinct:' . $mode : 'distinct', $message); } /** - * @param Arrayable|UnitEnum|array|string|int ...$values + * @param Arrayable|UnitEnum|array|string|int ...$values * * Note: `Arrayable` is template-invariant, so concrete types like * `Collection` don't satisfy `Arrayable`. @@ -285,7 +285,7 @@ protected function buildArrayRule(): string $this->keys, ); - return 'array:'.implode(',', $keys); + return 'array:' . implode(',', $keys); } /** @return list */ @@ -306,7 +306,8 @@ protected function buildValidationRules(): array * Laravel's Rule::contains silently ignores extras, and leaving nested * arrays/Arrayables in the value list would crash Contains::__toString. * - * @param array $values + * @param array $values + * * @return array */ private function flattenContainsValues(array $values): array @@ -327,7 +328,7 @@ private function flattenContainsValues(array $values): array if (is_array($value) || $value instanceof Arrayable) { throw new InvalidArgumentException( 'contains()/doesntContain() does not accept multiple array or Arrayable arguments. ' - .'Pass either a single iterable (->contains($values)) or variadic scalars (->contains($a, $b, $c)).', + . 'Pass either a single iterable (->contains($values)) or variadic scalars (->contains($a, $b, $c)).', ); } } @@ -339,7 +340,7 @@ private function flattenContainsValues(array $values): array private function buildEachNestedRules(string $attribute): array { if ($this->eachListRule instanceof ValidationRule) { - $key = $attribute.'.*'; + $key = $attribute . '.*'; $rules = [$key => $this->eachListRule]; return $this->eachListRule instanceof self @@ -356,7 +357,7 @@ private function buildEachNestedRules(string $attribute): array $nested = []; foreach ($this->eachRules as $field => $rule) { - $key = $attribute.'.*.'.$field; + $key = $attribute . '.*.' . $field; $rules[$key] = $rule; if ($rule instanceof self) { @@ -375,7 +376,7 @@ private function buildChildNestedRules(string $attribute): array $nested = []; foreach ($this->childRules ?? [] as $field => $rule) { - $key = $attribute.'.'.$field; + $key = $attribute . '.' . $field; $rules[$key] = $rule; if ($rule instanceof self) { diff --git a/src/Builder/Nodes/BooleanRule.php b/src/Builder/Nodes/BooleanRule.php index 021c9c5..cae9fd0 100644 --- a/src/Builder/Nodes/BooleanRule.php +++ b/src/Builder/Nodes/BooleanRule.php @@ -4,13 +4,13 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class BooleanRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -34,7 +34,7 @@ public function accepted(?string $message = null): static public function acceptedIf(string $field, string|int|bool ...$values): static { - return $this->addRule('accepted_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('accepted_if:' . $field . ',' . self::serializeValues($values)); } public function declined(?string $message = null): static @@ -44,7 +44,7 @@ public function declined(?string $message = null): static public function declinedIf(string $field, string|int|bool ...$values): static { - return $this->addRule('declined_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('declined_if:' . $field . ',' . self::serializeValues($values)); } /** @return list */ diff --git a/src/Builder/Nodes/DateRule.php b/src/Builder/Nodes/DateRule.php index 7daa01e..6356462 100644 --- a/src/Builder/Nodes/DateRule.php +++ b/src/Builder/Nodes/DateRule.php @@ -5,14 +5,14 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; use DateTimeInterface; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; class DateRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -109,22 +109,22 @@ public function nowOrFuture(?string $message = null): static public function before(DateTimeInterface|string $date, ?string $message = null): static { - return $this->addRule('before:'.$this->formatDate($date), $message); + return $this->addRule('before:' . $this->formatDate($date), $message); } public function after(DateTimeInterface|string $date, ?string $message = null): static { - return $this->addRule('after:'.$this->formatDate($date), $message); + return $this->addRule('after:' . $this->formatDate($date), $message); } public function beforeOrEqual(DateTimeInterface|string $date, ?string $message = null): static { - return $this->addRule('before_or_equal:'.$this->formatDate($date), $message); + return $this->addRule('before_or_equal:' . $this->formatDate($date), $message); } public function afterOrEqual(DateTimeInterface|string $date, ?string $message = null): static { - return $this->addRule('after_or_equal:'.$this->formatDate($date), $message); + return $this->addRule('after_or_equal:' . $this->formatDate($date), $message); } /** @@ -148,17 +148,17 @@ public function betweenOrEqual(DateTimeInterface|string $from, DateTimeInterface public function dateEquals(DateTimeInterface|string $date, ?string $message = null): static { - return $this->addRule('date_equals:'.$this->formatDate($date), $message); + return $this->addRule('date_equals:' . $this->formatDate($date), $message); } public function same(string $field, ?string $message = null): static { - return $this->addRule('same:'.$field, $message); + return $this->addRule('same:' . $field, $message); } public function different(string $field, ?string $message = null): static { - return $this->addRule('different:'.$field, $message); + return $this->addRule('different:' . $field, $message); } protected function formatDate(DateTimeInterface|string $date): string @@ -172,7 +172,7 @@ protected function formatDate(DateTimeInterface|string $date): string protected function buildValidationRules(): array { return [ - ...$this->reorderConstraints([$this->format === null ? 'date' : 'date_format:'.$this->format, ...$this->constraints]), + ...$this->reorderConstraints([$this->format === null ? 'date' : 'date_format:' . $this->format, ...$this->constraints]), ...$this->rules, ]; } diff --git a/src/Builder/Nodes/DeclinedRule.php b/src/Builder/Nodes/DeclinedRule.php index d2eb086..9bcd014 100644 --- a/src/Builder/Nodes/DeclinedRule.php +++ b/src/Builder/Nodes/DeclinedRule.php @@ -4,13 +4,13 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class DeclinedRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -35,7 +35,7 @@ public function declinedIf(string $field, string|int|bool ...$values): static static fn (string $rule): bool => $rule !== 'declined', )); - return $this->addRule('declined_if:'.$field.','.self::serializeValues($values)); + return $this->addRule('declined_if:' . $field . ',' . self::serializeValues($values)); } /** @return list */ diff --git a/src/Builder/Nodes/EmailRule.php b/src/Builder/Nodes/EmailRule.php index 557ce92..82888ab 100644 --- a/src/Builder/Nodes/EmailRule.php +++ b/src/Builder/Nodes/EmailRule.php @@ -4,21 +4,21 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Validation\Rules\Email; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Illuminate\Validation\Rules\Email; -use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIs; -use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; use Simtabi\Laranail\Validation\Rules\Email\NoSubaddressing; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Rules\Email\NotDisposableEmail; -use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Network\DeliverableEmail; +use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class EmailRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -99,7 +99,7 @@ public function notRole(?string $message = null): static * `*.example.com` matches any subdomain and NOT the bare domain; list * both when both are wanted. See EmailDomainIs for why that is strict. * - * @param list|string $domains + * @param list|string $domains */ public function domainIs(array|string $domains, ?string $message = null): static { @@ -109,7 +109,7 @@ public function domainIs(array|string $domains, ?string $message = null): static /** * Bar the address from the given domains, same pattern syntax. * - * @param list|string $domains + * @param list|string $domains */ public function domainIsNot(array|string $domains, ?string $message = null): static { @@ -171,7 +171,7 @@ public function maxRfcLength(?string $message = null): static public function max(int $value, ?string $message = null): static { - return $this->addRule('max:'.$value, $message); + return $this->addRule('max:' . $value, $message); } public function confirmed(?string $message = null): static @@ -181,12 +181,12 @@ public function confirmed(?string $message = null): static public function same(string $field, ?string $message = null): static { - return $this->addRule('same:'.$field, $message); + return $this->addRule('same:' . $field, $message); } public function different(string $field, ?string $message = null): static { - return $this->addRule('different:'.$field, $message); + return $this->addRule('different:' . $field, $message); } /** @@ -203,7 +203,7 @@ protected function buildValidationRules(): array { // Explicit modes always take precedence. if ($this->modes !== []) { - return [...$this->reorderConstraints($this->constraints), 'email:'.implode(',', $this->modes), ...$this->rules]; + return [...$this->reorderConstraints($this->constraints), 'email:' . implode(',', $this->modes), ...$this->rules]; } // Use Email::default() when defaults are enabled and the app has configured them. diff --git a/src/Builder/Nodes/FieldRule.php b/src/Builder/Nodes/FieldRule.php index 4990b91..921e605 100644 --- a/src/Builder/Nodes/FieldRule.php +++ b/src/Builder/Nodes/FieldRule.php @@ -5,17 +5,17 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; use Closure; +use LogicException; +use InvalidArgumentException; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use InvalidArgumentException; -use LogicException; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; use Simtabi\Laranail\Validation\Exceptions\UnknownFluentRuleMethod; /** @@ -50,7 +50,7 @@ class FieldRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule * pointing at the correct typed builder instead of a bare * `BadMethodCallException`. See `UnknownFluentRuleMethod`. * - * @param array $parameters + * @param array $parameters */ public function __call(string $method, array $parameters): mixed { @@ -96,7 +96,7 @@ public static function __callStatic(string $method, array $parameters): mixed * * Produces fixed paths (answer.email_address) without wildcards. * - * @param array $rules + * @param array $rules */ public function children(array $rules): static { @@ -137,7 +137,7 @@ public function addChildRule(string $key, ValidationRule $rule): static if (array_key_exists($key, $existing)) { throw new LogicException(sprintf( "addChildRule('%s'): key '%s' already exists in children(). " - .'Use mergeChildRules() if replacement is intentional.', + . 'Use mergeChildRules() if replacement is intentional.', $key, $key, )); @@ -152,7 +152,7 @@ public function addChildRule(string $key, ValidationRule $rule): static /** * Merge multiple keyed child rules, later-wins on collision. * - * @param array $rules + * @param array $rules */ public function mergeChildRules(array $rules): static { @@ -170,12 +170,12 @@ public function mergeChildRules(array $rules): static public function same(string $field, ?string $message = null): static { - return $this->addRule('same:'.$field, $message); + return $this->addRule('same:' . $field, $message); } public function different(string $field, ?string $message = null): static { - return $this->addRule('different:'.$field, $message); + return $this->addRule('different:' . $field, $message); } public function confirmed(?string $message = null): static @@ -191,7 +191,7 @@ public function buildNestedRules(string $attribute): array $nested = []; foreach ($this->childRules ?? [] as $field => $rule) { - $key = $attribute.'.'.$field; + $key = $attribute . '.' . $field; $rules[$key] = $rule; if ($rule instanceof ArrayRule diff --git a/src/Builder/Nodes/FileRule.php b/src/Builder/Nodes/FileRule.php index 9bd75db..c29b764 100644 --- a/src/Builder/Nodes/FileRule.php +++ b/src/Builder/Nodes/FileRule.php @@ -4,13 +4,13 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class FileRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -29,37 +29,37 @@ public function __construct() public function min(int|string $size, ?string $message = null): static { - return $this->addRule('min:'.$this->toKilobytes($size), $message); + return $this->addRule('min:' . $this->toKilobytes($size), $message); } public function max(int|string $size, ?string $message = null): static { - return $this->addRule('max:'.$this->toKilobytes($size), $message); + return $this->addRule('max:' . $this->toKilobytes($size), $message); } public function between(int|string $min, int|string $max, ?string $message = null): static { - return $this->addRule('between:'.$this->toKilobytes($min).','.$this->toKilobytes($max), $message); + return $this->addRule('between:' . $this->toKilobytes($min) . ',' . $this->toKilobytes($max), $message); } public function exactly(int|string $size, ?string $message = null): static { - return $this->addRule('size:'.$this->toKilobytes($size), $message); + return $this->addRule('size:' . $this->toKilobytes($size), $message); } public function extensions(string ...$extensions): static { - return $this->addRule('extensions:'.implode(',', $extensions)); + return $this->addRule('extensions:' . implode(',', $extensions)); } public function mimes(string ...$mimes): static { - return $this->addRule('mimes:'.implode(',', $mimes)); + return $this->addRule('mimes:' . implode(',', $mimes)); } public function mimetypes(string ...$mimetypes): static { - return $this->addRule('mimetypes:'.implode(',', $mimetypes)); + return $this->addRule('mimetypes:' . implode(',', $mimetypes)); } /** @@ -82,10 +82,10 @@ protected function toKilobytes(int|string $size): int $unit = strtolower($matches[2]); return (int) round(match ($unit) { - 'kb' => $value, - 'mb' => $value * 1_000, - 'gb' => $value * 1_000_000, - 'tb' => $value * 1_000_000_000, + 'kb' => $value, + 'mb' => $value * 1_000, + 'gb' => $value * 1_000_000, + 'tb' => $value * 1_000_000_000, default => $value, }); } diff --git a/src/Builder/Nodes/IpAddressRule.php b/src/Builder/Nodes/IpAddressRule.php index 357a694..81b1eb0 100644 --- a/src/Builder/Nodes/IpAddressRule.php +++ b/src/Builder/Nodes/IpAddressRule.php @@ -4,17 +4,17 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; +use Simtabi\Laranail\Validation\Rules\Net\PublicIp; +use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; +use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; -use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; -use Simtabi\Laranail\Validation\Rules\Net\PublicIp; /** * An IP address field. @@ -102,7 +102,7 @@ public function private(?string $message = null): static * FluentRule::ip()->inRange(['203.0.113.0/24', '2001:db8::/32']) * ``` * - * @param list|string $networks + * @param list|string $networks */ public function inRange(array|string $networks, ?string $message = null): static { diff --git a/src/Builder/Nodes/MacAddressRule.php b/src/Builder/Nodes/MacAddressRule.php index 3aac8d0..59b9286 100644 --- a/src/Builder/Nodes/MacAddressRule.php +++ b/src/Builder/Nodes/MacAddressRule.php @@ -4,15 +4,15 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; +use Simtabi\Laranail\Validation\Rules\Net\MacAddress; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\Rules\Net\MacAddress; /** * A MAC address field. @@ -85,7 +85,7 @@ public function bare(): static /** * Accept only these notations. * - * @param list|string $formats {@see MacAddress::COLON} and friends. + * @param list|string $formats {@see MacAddress::COLON} and friends. */ public function format(array|string $formats): static { @@ -141,7 +141,7 @@ public function universal(): static * Written in any notation and any length — a 24-bit OUI, or a longer * MA-M/MA-S assignment. * - * @param list|string $ouis + * @param list|string $ouis */ public function oui(array|string $ouis): static { diff --git a/src/Builder/Nodes/NumericRule.php b/src/Builder/Nodes/NumericRule.php index 29806b5..1332407 100644 --- a/src/Builder/Nodes/NumericRule.php +++ b/src/Builder/Nodes/NumericRule.php @@ -4,14 +4,14 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; class NumericRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -31,14 +31,14 @@ public function __construct() public function between(int|float $min, int|float $max, ?string $message = null): static { - return $this->addRule('between:'.$min.','.$max, $message); + return $this->addRule('between:' . $min . ',' . $max, $message); } public function decimal(int $min, ?int $max = null, ?string $message = null): static { - $r = 'decimal:'.$min; + $r = 'decimal:' . $min; if ($max !== null) { - $r .= ','.$max; + $r .= ',' . $max; } return $this->addRule($r, $message); @@ -46,7 +46,7 @@ public function decimal(int $min, ?int $max = null, ?string $message = null): st public function different(string $field, ?string $message = null): static { - return $this->addRule('different:'.$field, $message); + return $this->addRule('different:' . $field, $message); } /** @@ -56,7 +56,7 @@ public function different(string $field, ?string $message = null): static */ public function digits(int $length, ?string $message = null): static { - return $this->integer()->addRule('digits:'.$length, $message); + return $this->integer()->addRule('digits:' . $length, $message); } /** @@ -65,17 +65,17 @@ public function digits(int $length, ?string $message = null): static */ public function digitsBetween(int $min, int $max, ?string $message = null): static { - return $this->integer()->addRule('digits_between:'.$min.','.$max, $message); + return $this->integer()->addRule('digits_between:' . $min . ',' . $max, $message); } public function greaterThan(string $field, ?string $message = null): static { - return $this->addRule('gt:'.$field, $message); + return $this->addRule('gt:' . $field, $message); } public function greaterThanOrEqualTo(string $field, ?string $message = null): static { - return $this->addRule('gte:'.$field, $message); + return $this->addRule('gte:' . $field, $message); } public function integer(bool $strict = false, ?string $message = null): static @@ -85,37 +85,37 @@ public function integer(bool $strict = false, ?string $message = null): static public function lessThan(string $field, ?string $message = null): static { - return $this->addRule('lt:'.$field, $message); + return $this->addRule('lt:' . $field, $message); } public function lessThanOrEqualTo(string $field, ?string $message = null): static { - return $this->addRule('lte:'.$field, $message); + return $this->addRule('lte:' . $field, $message); } public function max(int|float $value, ?string $message = null): static { - return $this->addRule('max:'.$value, $message); + return $this->addRule('max:' . $value, $message); } public function maxDigits(int $value, ?string $message = null): static { - return $this->addRule('max_digits:'.$value, $message); + return $this->addRule('max_digits:' . $value, $message); } public function min(int|float $value, ?string $message = null): static { - return $this->addRule('min:'.$value, $message); + return $this->addRule('min:' . $value, $message); } public function minDigits(int $value, ?string $message = null): static { - return $this->addRule('min_digits:'.$value, $message); + return $this->addRule('min_digits:' . $value, $message); } public function multipleOf(int|float $value, ?string $message = null): static { - return $this->addRule('multiple_of:'.$value, $message); + return $this->addRule('multiple_of:' . $value, $message); } public function positive(?string $message = null): static @@ -140,7 +140,7 @@ public function nonPositive(?string $message = null): static public function same(string $field, ?string $message = null): static { - return $this->addRule('same:'.$field, $message); + return $this->addRule('same:' . $field, $message); } /** @@ -149,7 +149,7 @@ public function same(string $field, ?string $message = null): static */ public function exactly(int $value, ?string $message = null): static { - return $this->integer()->addRule('size:'.$value, $message); + return $this->integer()->addRule('size:' . $value, $message); } public function confirmed(?string $message = null): static @@ -159,17 +159,17 @@ public function confirmed(?string $message = null): static public function inArray(string $field, ?string $message = null): static { - return $this->addRule('in_array:'.$field, $message); + return $this->addRule('in_array:' . $field, $message); } public function inArrayKeys(string $field, ?string $message = null): static { - return $this->addRule('in_array_keys:'.$field, $message); + return $this->addRule('in_array_keys:' . $field, $message); } public function distinct(?string $mode = null, ?string $message = null): static { - return $this->addRule($mode ? 'distinct:'.$mode : 'distinct', $message); + return $this->addRule($mode ? 'distinct:' . $mode : 'distinct', $message); } /** @return list */ diff --git a/src/Builder/Nodes/PasswordRule.php b/src/Builder/Nodes/PasswordRule.php index 55d9259..acc4f62 100644 --- a/src/Builder/Nodes/PasswordRule.php +++ b/src/Builder/Nodes/PasswordRule.php @@ -4,15 +4,15 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; -use Illuminate\Contracts\Validation\DataAwareRule; -use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; +use ReflectionProperty; use Illuminate\Support\Traits\Macroable; use Illuminate\Validation\Rules\Password; -use ReflectionProperty; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Illuminate\Support\Traits\Conditionable; +use Illuminate\Contracts\Validation\DataAwareRule; +use Illuminate\Contracts\Validation\ValidatorAwareRule; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; class PasswordRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { diff --git a/src/Builder/Nodes/PhoneRule.php b/src/Builder/Nodes/PhoneRule.php index b65e9f0..a43cad1 100644 --- a/src/Builder/Nodes/PhoneRule.php +++ b/src/Builder/Nodes/PhoneRule.php @@ -5,17 +5,17 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; use Closure; -use Illuminate\Contracts\Validation\DataAwareRule; -use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Simtabi\Laranail\Phone\Enums\PhoneNumberType; -use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Illuminate\Contracts\Validation\DataAwareRule; use Simtabi\Laranail\Validation\Rules\Telecom\Phone; +use Illuminate\Contracts\Validation\ValidatorAwareRule; use Simtabi\Laranail\Validation\Rules\Telecom\UniquePhone; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; /** * A phone-number field. @@ -72,7 +72,7 @@ public function __construct() * does not — picking one arbitrarily would make the outcome depend on array order — so pair a * multi-country list with {@see countryFrom()} or expect international input. * - * @param string|list $countries ISO 3166-1 alpha-2 + * @param string|list $countries ISO 3166-1 alpha-2 */ public function country(string|array $countries): static { @@ -110,7 +110,7 @@ public function countryFrom(string $field): static * * `FixedLineOrMobile` satisfies a request for either — see {@see Phone::typeMatches()}. * - * @param PhoneNumberType|list $types + * @param PhoneNumberType|list $types */ public function type(PhoneNumberType|array $types): static { @@ -202,7 +202,7 @@ public function rejectEmergency(): static * FluentRule::phone()->unique('contacts', 'phone', fn ($rule) => $rule->ignore($id)); * ``` * - * @param (Closure(UniquePhone): void)|null $callback Receives the rule, for `ignore()` + * @param (Closure(UniquePhone): void)|null $callback Receives the rule, for `ignore()` */ public function unique(string $table, ?string $column = null, ?Closure $callback = null, ?string $message = null): static { diff --git a/src/Builder/Nodes/StringRule.php b/src/Builder/Nodes/StringRule.php index 3ec54e7..669cd75 100644 --- a/src/Builder/Nodes/StringRule.php +++ b/src/Builder/Nodes/StringRule.php @@ -5,15 +5,15 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; use Closure; +use Simtabi\Laranail\Validation\Regex; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\Regex; class StringRule implements DataAwareRule, FluentRuleContract, ValidatorAwareRule { @@ -53,32 +53,32 @@ public function ascii(?string $message = null): static public function encoding(string $encoding, ?string $message = null): static { - return $this->addRule('encoding:'.$encoding, $message); + return $this->addRule('encoding:' . $encoding, $message); } public function between(int $min, int $max, ?string $message = null): static { - return $this->addRule('between:'.$min.','.$max, $message); + return $this->addRule('between:' . $min . ',' . $max, $message); } public function doesntEndWith(string ...$values): static { - return $this->addRule('doesnt_end_with:'.implode(',', $values)); + return $this->addRule('doesnt_end_with:' . implode(',', $values)); } public function doesntStartWith(string ...$values): static { - return $this->addRule('doesnt_start_with:'.implode(',', $values)); + return $this->addRule('doesnt_start_with:' . implode(',', $values)); } public function endsWith(string ...$values): static { - return $this->addRule('ends_with:'.implode(',', $values)); + return $this->addRule('ends_with:' . implode(',', $values)); } public function exactly(int $value, ?string $message = null): static { - return $this->addRule('size:'.$value, $message); + return $this->addRule('size:' . $value, $message); } public function lowercase(?string $message = null): static @@ -88,17 +88,17 @@ public function lowercase(?string $message = null): static public function max(int $value, ?string $message = null): static { - return $this->addRule('max:'.$value, $message); + return $this->addRule('max:' . $value, $message); } public function min(int $value, ?string $message = null): static { - return $this->addRule('min:'.$value, $message); + return $this->addRule('min:' . $value, $message); } public function startsWith(string ...$values): static { - return $this->addRule('starts_with:'.implode(',', $values)); + return $this->addRule('starts_with:' . implode(',', $values)); } public function uppercase(?string $message = null): static @@ -157,11 +157,11 @@ public function macAddress(?string $message = null): static * accepts a {@see Regex} or a builder closure; see {@see matches()} for * the raw-string spelling that adds delimiters and `D` for you. * - * @param string|Regex|Closure(Regex): Regex $pattern + * @param string|Regex|Closure(Regex): Regex $pattern */ public function regex(string|Regex|Closure $pattern, ?string $message = null): static { - return $this->addRule('regex:'.(is_string($pattern) ? $pattern : $this->compileRegex($pattern)), $message); + return $this->addRule('regex:' . (is_string($pattern) ? $pattern : $this->compileRegex($pattern)), $message); } /** @@ -176,7 +176,7 @@ public function regex(string|Regex|Closure $pattern, ?string $message = null): s * The builder is never required — a team that already has a pattern * just uses it. * - * @param string|Regex|Closure(Regex): Regex $pattern + * @param string|Regex|Closure(Regex): Regex $pattern */ public function matches(string|Regex|Closure $pattern, ?string $message = null): static { @@ -184,12 +184,12 @@ public function matches(string|Regex|Closure $pattern, ?string $message = null): ? Regex::of($pattern)->compile() : $this->compileRegex($pattern); - return $this->addRule('regex:'.$compiled, $message); + return $this->addRule('regex:' . $compiled, $message); } public function notRegex(string $pattern, ?string $message = null): static { - return $this->addRule('not_regex:'.$pattern, $message); + return $this->addRule('not_regex:' . $pattern, $message); } public function timezone(?string $message = null): static @@ -209,12 +209,12 @@ public function date(?string $message = null): static public function email(string ...$modes): static { - return $this->addRule($modes === [] ? 'email' : 'email:'.implode(',', $modes)); + return $this->addRule($modes === [] ? 'email' : 'email:' . implode(',', $modes)); } public function dateFormat(string $format, ?string $message = null): static { - return $this->addRule('date_format:'.$format, $message); + return $this->addRule('date_format:' . $format, $message); } public function confirmed(?string $message = null): static @@ -224,32 +224,32 @@ public function confirmed(?string $message = null): static public function currentPassword(?string $guard = null, ?string $message = null): static { - return $this->addRule($guard ? 'current_password:'.$guard : 'current_password', $message); + return $this->addRule($guard ? 'current_password:' . $guard : 'current_password', $message); } public function same(string $field, ?string $message = null): static { - return $this->addRule('same:'.$field, $message); + return $this->addRule('same:' . $field, $message); } public function different(string $field, ?string $message = null): static { - return $this->addRule('different:'.$field, $message); + return $this->addRule('different:' . $field, $message); } public function inArray(string $field, ?string $message = null): static { - return $this->addRule('in_array:'.$field, $message); + return $this->addRule('in_array:' . $field, $message); } public function inArrayKeys(string $field, ?string $message = null): static { - return $this->addRule('in_array_keys:'.$field, $message); + return $this->addRule('in_array_keys:' . $field, $message); } public function distinct(?string $mode = null, ?string $message = null): static { - return $this->addRule($mode ? 'distinct:'.$mode : 'distinct', $message); + return $this->addRule($mode ? 'distinct:' . $mode : 'distinct', $message); } /** @return list */ diff --git a/src/Builder/Nodes/UrlRule.php b/src/Builder/Nodes/UrlRule.php index b11fc4e..7bb6ac4 100644 --- a/src/Builder/Nodes/UrlRule.php +++ b/src/Builder/Nodes/UrlRule.php @@ -4,16 +4,16 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; +use Simtabi\Laranail\Validation\Rules\Net\Url; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; -use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; -use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; use Simtabi\Laranail\Validation\Rules\Net\Support\HostPattern; -use Simtabi\Laranail\Validation\Rules\Net\Url; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; +use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; +use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; /** * A URL field. @@ -77,7 +77,7 @@ public function __construct() /** * Accept only these schemes. Written without `://`. * - * @param list|string $schemes + * @param list|string $schemes */ public function scheme(array|string $schemes): static { @@ -97,7 +97,7 @@ public function secure(): static * bare domain. List both when both are wanted — see the note on * {@see HostPattern}. * - * @param list|string $hosts + * @param list|string $hosts */ public function hostIs(array|string $hosts): static { @@ -109,7 +109,7 @@ public function hostIs(array|string $hosts): static /** * Bar these hosts, same syntax. Applied after the allow-list. * - * @param list|string $hosts + * @param list|string $hosts */ public function hostIsNot(array|string $hosts): static { @@ -122,7 +122,7 @@ public function hostIsNot(array|string $hosts): static * Accept only these ports. A URL with no port is accepted only if the * default for its scheme is in the list — state 80 or 443 explicitly. * - * @param list|int $ports + * @param list|int $ports */ public function port(array|int $ports): static { diff --git a/src/Builder/Nodes/UsernameRule.php b/src/Builder/Nodes/UsernameRule.php index d1de9aa..4bcf78d 100644 --- a/src/Builder/Nodes/UsernameRule.php +++ b/src/Builder/Nodes/UsernameRule.php @@ -4,15 +4,15 @@ namespace Simtabi\Laranail\Validation\Builder\Nodes; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Contracts\Validation\DataAwareRule; +use Simtabi\Laranail\Validation\Rules\Text\Username; use Illuminate\Contracts\Validation\ValidatorAwareRule; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; +use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; use Simtabi\Laranail\Validation\Builder\Concerns\HasEmbeddedRules; use Simtabi\Laranail\Validation\Builder\Concerns\HasFieldModifiers; -use Simtabi\Laranail\Validation\Builder\Concerns\SelfValidates; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\Rules\Text\Username; /** * A username field. @@ -120,7 +120,7 @@ public function lowercase(): static * ->reserved([...Username::DEFAULT_RESERVED, 'acme', 'enterprise']) * ``` * - * @param list $names + * @param list $names */ public function reserved(array $names): static { @@ -132,7 +132,7 @@ public function reserved(array $names): static /** * Add to the shipped list rather than replacing it. * - * @param list|string $names + * @param list|string $names */ public function alsoReserved(array|string $names): static { diff --git a/src/Check.php b/src/Check.php index 565d0c7..564d30f 100644 --- a/src/Check.php +++ b/src/Check.php @@ -5,32 +5,32 @@ namespace Simtabi\Laranail\Validation; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Validator; +use Simtabi\Laranail\Validation\Rules\Net\Cidr; +use Simtabi\Laranail\Validation\Rules\Codes\Ean; +use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Simtabi\Laranail\Validation\Rules\Codes\Isbn; +use Simtabi\Laranail\Validation\Rules\Codes\Issn; use Illuminate\Validation\InvokableValidationRule; use Simtabi\Laranail\Validation\Rules\Banking\Bic; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Rules\Banking\Iban; use Simtabi\Laranail\Validation\Rules\Banking\Isin; use Simtabi\Laranail\Validation\Rules\Banking\Luhn; -use Simtabi\Laranail\Validation\Rules\Codes\Ean; -use Simtabi\Laranail\Validation\Rules\Codes\Isbn; -use Simtabi\Laranail\Validation\Rules\Codes\Issn; -use Simtabi\Laranail\Validation\Rules\Colour\CssColor; use Simtabi\Laranail\Validation\Rules\Geo\Latitude; use Simtabi\Laranail\Validation\Rules\Geo\Longitude; -use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; -use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; -use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; -use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; -use Simtabi\Laranail\Validation\Rules\Net\Cidr; +use Simtabi\Laranail\Validation\Rules\Net\Subdomain; +use Simtabi\Laranail\Validation\Rules\Text\Username; use Simtabi\Laranail\Validation\Rules\Net\DomainName; use Simtabi\Laranail\Validation\Rules\Net\MacAddress; -use Simtabi\Laranail\Validation\Rules\Net\Subdomain; -use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; -use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; +use Simtabi\Laranail\Validation\Rules\Colour\CssColor; +use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; +use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; use Simtabi\Laranail\Validation\Rules\Text\PersonName; -use Simtabi\Laranail\Validation\Rules\Text\Slug; -use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; +use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; +use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; +use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; /** * One-off boolean guards over the rule library — `Check::iban($v)` where diff --git a/src/Commands/BenchmarkCommand.php b/src/Commands/BenchmarkCommand.php index 4700dd1..c0142b1 100644 --- a/src/Commands/BenchmarkCommand.php +++ b/src/Commands/BenchmarkCommand.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation\Commands; +use Symfony\Component\Process\Process; use Simtabi\Laranail\Console\Tools\Commands\Command; use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; -use Symfony\Component\Process\Process; /** * The official face of the repo's benchmark harness. `benchmark.php` is a @@ -25,12 +25,12 @@ final class BenchmarkCommand extends Command public function handle(): int { - $script = dirname(__DIR__, 2).'/benchmark.php'; + $script = dirname(__DIR__, 2) . '/benchmark.php'; if (! is_file($script)) { $this->error( 'benchmark.php ships only with a repository checkout, not the Composer archive. ' - .'Clone laranail/validation to benchmark: https://github.com/laranail/validation', + . 'Clone laranail/validation to benchmark: https://github.com/laranail/validation', ); return self::FAILURE; diff --git a/src/Commands/DoctorCommand.php b/src/Commands/DoctorCommand.php index e403457..1f2d6f4 100644 --- a/src/Commands/DoctorCommand.php +++ b/src/Commands/DoctorCommand.php @@ -4,15 +4,15 @@ namespace Simtabi\Laranail\Validation\Commands; -use Simtabi\Laranail\Console\Tools\Commands\Command; -use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; +use Throwable; use Simtabi\Laranail\Phone\PhoneFormatter; +use Simtabi\Laranail\Console\Tools\Commands\Command; use Simtabi\Laranail\Validation\BatchDatabaseChecker; -use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; +use Simtabi\Laranail\Validation\Support\RuleRegistrar; use Simtabi\Laranail\Validation\Contracts\Email\DnsResolver; use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; -use Simtabi\Laranail\Validation\Support\RuleRegistrar; -use Throwable; +use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; +use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; /** * The package's boot-health surface (failure-handling standard: degraded @@ -46,7 +46,7 @@ public function handle(RuleRegistrar $registrar): int $count = count($registrar->classes()); return $count > 0 - ? ['OK', $count.' rules ('.count($registrar->clientCheckable()).' client-checkable)'] + ? ['OK', $count . ' rules (' . count($registrar->clientCheckable()) . ' client-checkable)'] : ['FAIL', 'no rules discovered']; }], ['disposable-domain list', function (): array { @@ -54,16 +54,16 @@ public function handle(RuleRegistrar $registrar): int return $list->contains('mailinator.com') ? ['OK', $list::class] - : ['WARN', $list::class.' does not flag a canonical disposable domain']; + : ['WARN', $list::class . ' does not flag a canonical disposable domain']; }], ['role-account list', fn (): array => ['OK', resolve(RoleAccountList::class)::class]], - ['dns resolver', fn (): array => ['OK', resolve(DnsResolver::class)::class]], + ['dns resolver', fn (): array => ['OK', resolve(DnsResolver::class)::class]], ['batch query cap', function (): array { $limit = BatchDatabaseChecker::$maxValuesPerGroup; return $limit > 0 ? ['OK', (string) $limit] - : ['FAIL', 'cap is '.$limit.' — every batch would be refused']; + : ['FAIL', 'cap is ' . $limit . ' — every batch would be refused']; }], ['string aliases', function (): array { if (config('laranail.validation.aliases.enabled') !== true) { @@ -73,7 +73,7 @@ public function handle(RuleRegistrar $registrar): int $prefix = config('laranail.validation.aliases.prefix'); return is_string($prefix) && $prefix !== '' - ? ['OK', 'enabled, prefix "'.$prefix.'"'] + ? ['OK', 'enabled, prefix "' . $prefix . '"'] : ['FAIL', 'enabled with an empty prefix — bare generic names collide silently']; }], ['ext-intl', fn (): array => extension_loaded('intl') @@ -88,7 +88,7 @@ public function handle(RuleRegistrar $registrar): int try { [$status, $detail] = $check(); } catch (Throwable $e) { - [$status, $detail] = ['FAIL', $e::class.': '.$e->getMessage()]; + [$status, $detail] = ['FAIL', $e::class . ': ' . $e->getMessage()]; } $failed = $failed || $status === 'FAIL'; diff --git a/src/Commands/RulesCommand.php b/src/Commands/RulesCommand.php index 774f963..abd442c 100644 --- a/src/Commands/RulesCommand.php +++ b/src/Commands/RulesCommand.php @@ -6,10 +6,10 @@ use Illuminate\Foundation\Auth\User; use Simtabi\Laranail\Console\Tools\Commands\Command; -use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; -use Simtabi\Laranail\Validation\Contracts\ClientCheckable; use Simtabi\Laranail\Validation\Support\RuleAliases; use Simtabi\Laranail\Validation\Support\RuleRegistrar; +use Simtabi\Laranail\Validation\Contracts\ClientCheckable; +use Simtabi\Laranail\Console\Tools\Commands\Concerns\SupportsNamespacedNames; /** * Lists every rule the registry knows — the package's discovered library @@ -64,19 +64,19 @@ class_basename($class), private function aliasIndex(): array { $samples = [ - 'models_exist' => [User::class], - 'authorized' => ['view', User::class], - 'parity' => ['even'], - 'vendor_identifier' => ['aws_region'], + 'models_exist' => [User::class], + 'authorized' => ['view', User::class], + 'parity' => ['even'], + 'vendor_identifier' => ['aws_region'], 'national_identifier' => ['nl'], - 'minute_in' => ['0', '15', '30', '45'], + 'minute_in' => ['0', '15', '30', '45'], 'max_date_difference' => ['48', '2026-08-24 00:00:00'], - 'minimum_age' => ['18'], - 'compare_to_column' => ['posts', 'owner_id', 'gte', 'id', '1'], + 'minimum_age' => ['18'], + 'compare_to_column' => ['posts', 'owner_id', 'gte', 'id', '1'], 'file_exists_on_disk' => ['local'], - 'hash_digest' => ['sha256'], - 'max_words' => ['200'], - 'min_words' => ['2'], + 'hash_digest' => ['sha256'], + 'max_words' => ['200'], + 'min_words' => ['2'], ]; $index = []; diff --git a/src/Concerns/SkipsPrecognition.php b/src/Concerns/SkipsPrecognition.php index aeae9cc..23be9fb 100644 --- a/src/Concerns/SkipsPrecognition.php +++ b/src/Concerns/SkipsPrecognition.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Concerns; -use Illuminate\Container\Container; use Illuminate\Http\Request; +use Illuminate\Container\Container; use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; /** diff --git a/src/Contracts/FluentRuleContract.php b/src/Contracts/FluentRuleContract.php index c8e44f3..450aa39 100644 --- a/src/Contracts/FluentRuleContract.php +++ b/src/Contracts/FluentRuleContract.php @@ -4,11 +4,11 @@ namespace Simtabi\Laranail\Validation\Contracts; -use BackedEnum; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; +use BackedEnum; use Illuminate\Support\Fluent; use Illuminate\Support\HigherOrderWhenProxy; +use Illuminate\Contracts\Validation\ValidationRule; /** * Contract implemented by every rule class shipped in @@ -133,7 +133,7 @@ public function prohibitedIfDeclined(string $field): static; // ---------- Escape hatch ---------- /** - * @param object|string|array $rule + * @param object|string|array $rule */ public function rule(object|string|array $rule): static; @@ -151,25 +151,27 @@ public function rule(object|string|array $rule): static; * return is `$this|\Illuminate\Conditionable\HigherOrderWhenProxy` * depending on whether a callback is supplied. * - * @param mixed $value - * @param (callable(static, mixed): mixed)|null $callback - * @param (callable(static, mixed): mixed)|null $default + * @param mixed $value + * @param (callable(static, mixed): mixed)|null $callback + * @param (callable(static, mixed): mixed)|null $default + * * @return static|HigherOrderWhenProxy */ public function when($value = null, ?callable $callback = null, ?callable $default = null); /** - * @param mixed $value - * @param (callable(static, mixed): mixed)|null $callback - * @param (callable(static, mixed): mixed)|null $default + * @param mixed $value + * @param (callable(static, mixed): mixed)|null $callback + * @param (callable(static, mixed): mixed)|null $default + * * @return static|HigherOrderWhenProxy */ public function unless($value = null, ?callable $callback = null, ?callable $default = null); /** - * @param Closure(Fluent): bool $condition - * @param Closure(static): static|string|list $rules - * @param Closure(static): static|string|list $defaultRules + * @param Closure(Fluent): bool $condition + * @param Closure(static): static|string|list $rules + * @param Closure(static): static|string|list $defaultRules */ public function whenInput(Closure $condition, Closure|string|array $rules, Closure|string|array $defaultRules = []): static; diff --git a/src/Events/RuleSetCompiling.php b/src/Events/RuleSetCompiling.php index 1f0bc1b..1b00b7a 100644 --- a/src/Events/RuleSetCompiling.php +++ b/src/Events/RuleSetCompiling.php @@ -20,10 +20,10 @@ final class RuleSetCompiling { /** - * @param array $rules Field → rule input, as the rule set holds it. Mutable. - * @param array $messages Custom messages. Mutable. - * @param array $attributes Human attribute names. Mutable. - * @param array $data The data about to be validated. Mutable. + * @param array $rules Field → rule input, as the rule set holds it. Mutable. + * @param array $messages Custom messages. Mutable. + * @param array $attributes Human attribute names. Mutable. + * @param array $data The data about to be validated. Mutable. */ public function __construct( public readonly RuleSet $ruleSet, diff --git a/src/Events/ValidationCompleted.php b/src/Events/ValidationCompleted.php index 9a5c709..2f54934 100644 --- a/src/Events/ValidationCompleted.php +++ b/src/Events/ValidationCompleted.php @@ -14,8 +14,8 @@ final readonly class ValidationCompleted { /** - * @param array $data - * @param array $validated + * @param array $data + * @param array $validated */ public function __construct( public RuleSet $ruleSet, diff --git a/src/Events/ValidationFailed.php b/src/Events/ValidationFailed.php index 92c8dcc..43b8557 100644 --- a/src/Events/ValidationFailed.php +++ b/src/Events/ValidationFailed.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Events; use Illuminate\Support\MessageBag; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Validation\ValidationException; /** * Fired when a {@see RuleSet} run FAILS — the notification seam. diff --git a/src/Exceptions/CannotExtendListShapedEach.php b/src/Exceptions/CannotExtendListShapedEach.php index 9530806..ce88abe 100644 --- a/src/Exceptions/CannotExtendListShapedEach.php +++ b/src/Exceptions/CannotExtendListShapedEach.php @@ -22,8 +22,8 @@ public static function on(string $method): self { return new self(sprintf( 'Cannot call %s() on an ArrayRule whose each() is list-shaped ' - .'(e.g. each(FluentRule::string())). Convert to keyed form ' - ."first: each(['key' => FluentRule::…]).", + . '(e.g. each(FluentRule::string())). Convert to keyed form ' + . "first: each(['key' => FluentRule::…]).", $method, )); } diff --git a/src/Exceptions/TypedBuilderHint.php b/src/Exceptions/TypedBuilderHint.php index ad9058a..4f72b49 100644 --- a/src/Exceptions/TypedBuilderHint.php +++ b/src/Exceptions/TypedBuilderHint.php @@ -6,17 +6,17 @@ use ReflectionClass; use ReflectionMethod; -use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; -use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; -use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; use Simtabi\Laranail\Validation\Builder\Nodes\DateRule; +use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; +use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; use Simtabi\Laranail\Validation\Builder\Nodes\ImageRule; +use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; +use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; +use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; -use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; /** * Maps method names to hints pointing at the typed builder(s) that @@ -47,16 +47,16 @@ final class TypedBuilderHint * factory name inserted into generated hints. */ private const array TYPED_BUILDERS = [ - StringRule::class => 'string', - NumericRule::class => 'numeric', - DateRule::class => 'date', - ArrayRule::class => 'array', - FileRule::class => 'file', - ImageRule::class => 'image', - BooleanRule::class => 'boolean', + StringRule::class => 'string', + NumericRule::class => 'numeric', + DateRule::class => 'date', + ArrayRule::class => 'array', + FileRule::class => 'file', + ImageRule::class => 'image', + BooleanRule::class => 'boolean', AcceptedRule::class => 'accepted', PasswordRule::class => 'password', - EmailRule::class => 'email', + EmailRule::class => 'email', ]; /** @@ -113,14 +113,14 @@ public static function for(string $method): ?string private static function specialCase(string $method): ?string { return match ($method) { - 'accepted' => 'Use `FluentRule::accepted()` for permissive accepted values (`\'yes\'`, `\'on\'`, `\'1\'`, `true`). Avoid `FluentRule::boolean()->accepted()` — the `boolean` base rule rejects `\'yes\'`/`\'on\'`.', - 'declined' => "Use `FluentRule::boolean()->declined(...)` for strict boolean-only input. If HTML form values (`'no'`, `'off'`) need to pass, use `->rule('declined')` on the untyped builder instead.", - 'size' => 'No `size()` method — use `->exactly(...)` on a typed builder. (This package renames Laravel\'s `size:` rule to `exactly()` for clarity.)', + 'accepted' => 'Use `FluentRule::accepted()` for permissive accepted values (`\'yes\'`, `\'on\'`, `\'1\'`, `true`). Avoid `FluentRule::boolean()->accepted()` — the `boolean` base rule rejects `\'yes\'`/`\'on\'`.', + 'declined' => "Use `FluentRule::boolean()->declined(...)` for strict boolean-only input. If HTML form values (`'no'`, `'off'`) need to pass, use `->rule('declined')` on the untyped builder instead.", + 'size' => 'No `size()` method — use `->exactly(...)` on a typed builder. (This package renames Laravel\'s `size:` rule to `exactly()` for clarity.)', 'gt', 'gte', 'lt', 'lte' => 'No `gt()`/`lt()` methods — use `FluentRule::numeric()->greaterThan(FIELD)`, `->greaterThanOrEqualTo(FIELD)`, `->lessThan(FIELD)`, `->lessThanOrEqualTo(FIELD)`.', - 'alphaNum' => 'No `alphaNum()` method — use `FluentRule::string()->alphaNumeric(...)`.', - 'contains' => 'Use `FluentRule::array()->contains(...)` (this package exposes `contains` on `array()`, not `string()`).', - 'format' => 'Use `FluentRule::date()->format(...)` for date format; for string-format regex use `FluentRule::string()->regex(...)`.', - default => null, + 'alphaNum' => 'No `alphaNum()` method — use `FluentRule::string()->alphaNumeric(...)`.', + 'contains' => 'Use `FluentRule::array()->contains(...)` (this package exposes `contains` on `array()`, not `string()`).', + 'format' => 'Use `FluentRule::date()->format(...)` for date format; for string-format regex use `FluentRule::string()->regex(...)`.', + default => null, }; } @@ -157,7 +157,8 @@ private static function methodMap(): array } /** - * @param class-string $class + * @param class-string $class + * * @return array */ private static function publicInstanceMethods(string $class): array @@ -186,7 +187,7 @@ private static function publicInstanceMethods(string $class): array } /** - * @param list $factories + * @param list $factories */ private static function formatFactoryList(array $factories): string { @@ -201,6 +202,6 @@ private static function formatFactoryList(array $factories): string $last = array_pop($formatted); - return implode(', ', $formatted).', or '.$last; + return implode(', ', $formatted) . ', or ' . $last; } } diff --git a/src/FastCheck/ItemContextCompiler.php b/src/FastCheck/ItemContextCompiler.php index d660355..f1a5a0b 100644 --- a/src/FastCheck/ItemContextCompiler.php +++ b/src/FastCheck/ItemContextCompiler.php @@ -74,9 +74,9 @@ private static function rewriteConfirmedRule(string $ruleString, string $attribu foreach ($parts as $i => $part) { if ($part === 'confirmed') { - $parts[$i] = 'same:'.$attributeName.'_confirmation'; + $parts[$i] = 'same:' . $attributeName . '_confirmation'; } elseif (str_starts_with($part, 'confirmed:')) { - $parts[$i] = 'same:'.substr($part, 10); + $parts[$i] = 'same:' . substr($part, 10); } } @@ -143,7 +143,8 @@ private static function parseWithItemContext(string $ruleString): ?array } /** - * @param array $config + * @param array $config + * * @return array|null */ private static function parsePartWithItemContext(string $part, array $config): ?array @@ -151,18 +152,18 @@ private static function parsePartWithItemContext(string $part, array $config): ? // For date rules, use the field-ref-aware parser. For everything else, // delegate to the value-only parser in RuleConfigBuilder. return match (true) { - str_starts_with($part, 'date_equals:') => self::parseDateParamWithFieldRef($config, 'dateEquals', substr($part, 12)), - str_starts_with($part, 'after_or_equal:') => self::parseDateParamWithFieldRef($config, 'afterOrEqual', substr($part, 15)), + str_starts_with($part, 'date_equals:') => self::parseDateParamWithFieldRef($config, 'dateEquals', substr($part, 12)), + str_starts_with($part, 'after_or_equal:') => self::parseDateParamWithFieldRef($config, 'afterOrEqual', substr($part, 15)), str_starts_with($part, 'before_or_equal:') => self::parseDateParamWithFieldRef($config, 'beforeOrEqual', substr($part, 16)), - str_starts_with($part, 'after:') => self::parseDateParamWithFieldRef($config, 'after', substr($part, 6)), - str_starts_with($part, 'before:') => self::parseDateParamWithFieldRef($config, 'before', substr($part, 7)), - str_starts_with($part, 'same:') => self::parseFieldOnlyRef($config, 'sameField', substr($part, 5)), - str_starts_with($part, 'different:') => self::parseFieldOnlyRef($config, 'differentField', substr($part, 10)), - str_starts_with($part, 'gte:') => self::parseFieldOnlyRef($config, 'gteField', substr($part, 4)), - str_starts_with($part, 'lte:') => self::parseFieldOnlyRef($config, 'lteField', substr($part, 4)), - str_starts_with($part, 'gt:') => self::parseFieldOnlyRef($config, 'gtField', substr($part, 3)), - str_starts_with($part, 'lt:') => self::parseFieldOnlyRef($config, 'ltField', substr($part, 3)), - default => RuleConfigBuilder::parseValuePart($part, $config), + str_starts_with($part, 'after:') => self::parseDateParamWithFieldRef($config, 'after', substr($part, 6)), + str_starts_with($part, 'before:') => self::parseDateParamWithFieldRef($config, 'before', substr($part, 7)), + str_starts_with($part, 'same:') => self::parseFieldOnlyRef($config, 'sameField', substr($part, 5)), + str_starts_with($part, 'different:') => self::parseFieldOnlyRef($config, 'differentField', substr($part, 10)), + str_starts_with($part, 'gte:') => self::parseFieldOnlyRef($config, 'gteField', substr($part, 4)), + str_starts_with($part, 'lte:') => self::parseFieldOnlyRef($config, 'lteField', substr($part, 4)), + str_starts_with($part, 'gt:') => self::parseFieldOnlyRef($config, 'gtField', substr($part, 3)), + str_starts_with($part, 'lt:') => self::parseFieldOnlyRef($config, 'ltField', substr($part, 3)), + default => RuleConfigBuilder::parseValuePart($part, $config), }; } @@ -171,7 +172,8 @@ private static function parsePartWithItemContext(string $part, array $config): ? * is a date literal, behaves like a literal parse. Otherwise, if it's a * plausible field name, stores it under `{$key}Field` for item-time resolution. * - * @param array $config + * @param array $config + * * @return array|null */ private static function parseDateParamWithFieldRef(array $config, string $key, string $param): ?array @@ -187,7 +189,7 @@ private static function parseDateParamWithFieldRef(array $config, string $key, s return null; } - return [...$config, $key.'Field' => $param]; + return [...$config, $key . 'Field' => $param]; } /** @@ -195,7 +197,8 @@ private static function parseDateParamWithFieldRef(array $config, string $key, s * parameter isn't a single plausible identifier — so multi-param forms * like `different:a,b` fall through to Laravel. * - * @param array $config + * @param array $config + * * @return array|null */ private static function parseFieldOnlyRef(array $config, string $key, string $param): ?array @@ -212,7 +215,8 @@ private static function parseFieldOnlyRef(array $config, string $key, string $pa * against the item array. Delegates most checks to CoreValueCompiler's * value-only closure. * - * @param array $c + * @param array $c + * * @return Closure(mixed, array): bool */ private static function buildItemAwareClosure(array $c): Closure @@ -434,7 +438,7 @@ private static function sizePair(mixed $value, mixed $ref, bool $isNumeric, bool * missing, empty, non-string, unparseable) coerce to 0 — matches Laravel's * loose-comparison behavior when a referenced field doesn't parse. * - * @param array $item + * @param array $item */ private static function resolveRefTimestamp(array $item, string $field): int { diff --git a/src/FastCheck/PresenceConditionalCompiler.php b/src/FastCheck/PresenceConditionalCompiler.php index b448018..48077f2 100644 --- a/src/FastCheck/PresenceConditionalCompiler.php +++ b/src/FastCheck/PresenceConditionalCompiler.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\FastCheck; use Closure; -use Simtabi\Laranail\Validation\FastCheck\Shared\ItemAwareBranchBuilder; use Simtabi\Laranail\Validation\FastCheck\Shared\LaravelEmptiness; +use Simtabi\Laranail\Validation\FastCheck\Shared\ItemAwareBranchBuilder; /** * Compiles rule strings that contain presence conditionals — @@ -95,8 +95,8 @@ public static function compile(string $ruleString): ?Closure * A field is "present" by Laravel's `validateRequired` criteria: not null, * not whitespace-only string, not empty array/Countable. * - * @param list $fields - * @param array $item + * @param list $fields + * @param array $item */ private static function presenceConditionActive(string $type, array $fields, array $item): bool { @@ -106,11 +106,11 @@ private static function presenceConditionActive(string $type, array $fields, arr } return match ($type) { - 'required_with' => in_array(true, $present, true), - 'required_without' => in_array(false, $present, true), - 'required_with_all' => ! in_array(false, $present, true), + 'required_with' => in_array(true, $present, true), + 'required_without' => in_array(false, $present, true), + 'required_with_all' => ! in_array(false, $present, true), 'required_without_all' => ! in_array(true, $present, true), - default => false, + default => false, }; } } diff --git a/src/FastCheck/RuleConfigBuilder.php b/src/FastCheck/RuleConfigBuilder.php index d0f8437..b32dc4a 100644 --- a/src/FastCheck/RuleConfigBuilder.php +++ b/src/FastCheck/RuleConfigBuilder.php @@ -34,28 +34,28 @@ final class RuleConfigBuilder public static function initialConfig(): array { return [ - 'required' => false, 'filled' => false, - 'nullable' => false, 'sometimes' => false, - 'string' => false, 'numeric' => false, 'integer' => false, 'integer.strict' => false, - 'boolean' => false, 'array' => false, 'email' => false, 'date' => false, - 'url' => false, 'ip' => false, 'uuid' => false, 'ulid' => false, - 'accepted' => false, 'declined' => false, - 'alpha' => false, 'alphaDash' => false, 'alphaNum' => false, - 'min' => null, 'max' => null, - 'digits' => null, 'digitsMin' => null, 'digitsMax' => null, - 'in' => null, 'notIn' => null, - 'regex' => null, 'notRegex' => null, - 'dateFormat' => null, - 'after' => null, 'before' => null, + 'required' => false, 'filled' => false, + 'nullable' => false, 'sometimes' => false, + 'string' => false, 'numeric' => false, 'integer' => false, 'integer.strict' => false, + 'boolean' => false, 'array' => false, 'email' => false, 'date' => false, + 'url' => false, 'ip' => false, 'uuid' => false, 'ulid' => false, + 'accepted' => false, 'declined' => false, + 'alpha' => false, 'alphaDash' => false, 'alphaNum' => false, + 'min' => null, 'max' => null, + 'digits' => null, 'digitsMin' => null, 'digitsMax' => null, + 'in' => null, 'notIn' => null, + 'regex' => null, 'notRegex' => null, + 'dateFormat' => null, + 'after' => null, 'before' => null, 'afterOrEqual' => null, 'beforeOrEqual' => null, - 'dateEquals' => null, + 'dateEquals' => null, // Item-context field-reference keys — populated by ItemContextCompiler. - 'afterField' => null, 'beforeField' => null, + 'afterField' => null, 'beforeField' => null, 'afterOrEqualField' => null, 'beforeOrEqualField' => null, - 'dateEqualsField' => null, - 'sameField' => null, 'differentField' => null, - 'gtField' => null, 'gteField' => null, - 'ltField' => null, 'lteField' => null, + 'dateEqualsField' => null, + 'sameField' => null, 'differentField' => null, + 'gtField' => null, 'gteField' => null, + 'ltField' => null, 'lteField' => null, ]; } @@ -65,7 +65,8 @@ public static function initialConfig(): array * rules (`same:FIELD`, `gt:FIELD`, etc.) are NOT handled here — see * `ItemContextCompiler::parsePartWithItemContext` for that path. * - * @param array $config + * @param array $config + * * @return array|null */ public static function parseValuePart(string $part, array $config): ?array @@ -85,34 +86,34 @@ public static function parseValuePart(string $part, array $config): ?array } return match (true) { - $part === 'integer' => [...$config, 'integer' => true], - $part === 'integer:strict' => [...$config, 'integer' => true, 'integer.strict' => true], - $part === 'alpha', $part === 'alpha:ascii' => [...$config, 'alpha' => true], + $part === 'integer' => [...$config, 'integer' => true], + $part === 'integer:strict' => [...$config, 'integer' => true, 'integer.strict' => true], + $part === 'alpha', $part === 'alpha:ascii' => [...$config, 'alpha' => true], $part === 'alpha_dash', $part === 'alpha_dash:ascii' => [...$config, 'alphaDash' => true], - $part === 'alpha_num', $part === 'alpha_num:ascii' => [...$config, 'alphaNum' => true], - $part === 'nullable' => [...$config, 'nullable' => true], + $part === 'alpha_num', $part === 'alpha_num:ascii' => [...$config, 'alphaNum' => true], + $part === 'nullable' => [...$config, 'nullable' => true], // 'sometimes' not fast-checkable: distinguishing absent from // present-null requires presence info the closure doesn't have. $part === 'sometimes' => null, - $part === 'bail' => $config, + $part === 'bail' => $config, // Not (int): NumericRule::min() accepts int|float, and Laravel // compares with BigNumber against the raw parameter. Casting // `min:2.5` to 2 lets 2.2 through. - str_starts_with($part, 'min:') => [...$config, 'min' => self::parseSize(substr($part, 4))], - str_starts_with($part, 'max:') => [...$config, 'max' => self::parseSize(substr($part, 4))], - str_starts_with($part, 'digits:') => [...$config, 'digits' => (int) substr($part, 7)], - str_starts_with($part, 'digits_between:') => self::parseDigitsBetween($config, substr($part, 15)), - str_starts_with($part, 'in:') => [...$config, 'in' => self::parseInValues(substr($part, 3))], - str_starts_with($part, 'not_in:') => [...$config, 'notIn' => self::parseInValues(substr($part, 7))], - str_starts_with($part, 'regex:') => [...$config, 'regex' => substr($part, 6)], - str_starts_with($part, 'not_regex:') => [...$config, 'notRegex' => substr($part, 10)], - str_starts_with($part, 'date_format:') => [...$config, 'dateFormat' => substr($part, 12)], - str_starts_with($part, 'date_equals:') => self::parseDateLiteral($config, 'dateEquals', substr($part, 12)), - str_starts_with($part, 'after_or_equal:') => self::parseDateLiteral($config, 'afterOrEqual', substr($part, 15)), + str_starts_with($part, 'min:') => [...$config, 'min' => self::parseSize(substr($part, 4))], + str_starts_with($part, 'max:') => [...$config, 'max' => self::parseSize(substr($part, 4))], + str_starts_with($part, 'digits:') => [...$config, 'digits' => (int) substr($part, 7)], + str_starts_with($part, 'digits_between:') => self::parseDigitsBetween($config, substr($part, 15)), + str_starts_with($part, 'in:') => [...$config, 'in' => self::parseInValues(substr($part, 3))], + str_starts_with($part, 'not_in:') => [...$config, 'notIn' => self::parseInValues(substr($part, 7))], + str_starts_with($part, 'regex:') => [...$config, 'regex' => substr($part, 6)], + str_starts_with($part, 'not_regex:') => [...$config, 'notRegex' => substr($part, 10)], + str_starts_with($part, 'date_format:') => [...$config, 'dateFormat' => substr($part, 12)], + str_starts_with($part, 'date_equals:') => self::parseDateLiteral($config, 'dateEquals', substr($part, 12)), + str_starts_with($part, 'after_or_equal:') => self::parseDateLiteral($config, 'afterOrEqual', substr($part, 15)), str_starts_with($part, 'before_or_equal:') => self::parseDateLiteral($config, 'beforeOrEqual', substr($part, 16)), - str_starts_with($part, 'after:') => self::parseDateLiteral($config, 'after', substr($part, 6)), - str_starts_with($part, 'before:') => self::parseDateLiteral($config, 'before', substr($part, 7)), - default => null, + str_starts_with($part, 'after:') => self::parseDateLiteral($config, 'after', substr($part, 6)), + str_starts_with($part, 'before:') => self::parseDateLiteral($config, 'before', substr($part, 7)), + default => null, }; } @@ -121,7 +122,7 @@ public static function parseValuePart(string $part, array $config): ?array * measure: string length, array count, or numeric value. Without one, * Laravel infers from runtime type — not fast-checkable. * - * @param array $config + * @param array $config */ public static function validateSizeRuleHasType(array $config): bool { @@ -146,7 +147,7 @@ public static function validateSizeRuleHasType(array $config): bool * loose subset semantics (array_diff), which the scalar closure * cannot reproduce. * - * @param array $config + * @param array $config */ public static function validateCompilableCombinations(array $config): bool { @@ -169,7 +170,8 @@ public static function validateCompilableCombinations(array $config): bool * Build the value-only closure from a parsed config. ItemContextCompiler * wraps the result in an item-aware closure for cross-field comparisons. * - * @param array $c + * @param array $c + * * @return Closure(mixed): bool */ public static function buildValueClosure(array $c): Closure @@ -313,7 +315,8 @@ public static function buildValueClosure(array $c): Closure } /** - * @param array $config + * @param array $config + * * @return array */ private static function parseDigitsBetween(array $config, string $value): array @@ -378,7 +381,7 @@ private static function parseSize(string $value): int|float * through a strict-comparison-friendly operator. Used to detect the * loose/strict DISAGREEMENT zone the fast path must not decide. * - * @param list $haystack + * @param list $haystack */ private static function matchesLoosely(string $needle, array $haystack): bool { @@ -407,7 +410,8 @@ private static function parseInValues(string $values): array * Parse a date comparison rule. Only compiles when the parameter is a * date literal (resolvable by strtotime), not a field reference. * - * @param array $config + * @param array $config + * * @return array|null */ private static function parseDateLiteral(array $config, string $key, string $param): ?array @@ -428,8 +432,8 @@ private static function parseDateLiteral(array $config, string $key, string $par * Mutates `$checks` in place; closures capture the type literals they * compare against. * - * @param array $c - * @param list $checks + * @param array $c + * @param list $checks */ private static function addTypeChecks(array $c, array &$checks): void { @@ -467,8 +471,8 @@ private static function addTypeChecks(array $c, array &$checks): void /** * Append format-check closures (email, url, ip, uuid, ulid, alpha-family). * - * @param array $c - * @param list $checks + * @param array $c + * @param list $checks */ private static function addFormatChecks(array $c, array &$checks): void { @@ -513,8 +517,8 @@ private static function addFormatChecks(array $c, array &$checks): void /** * Append date-check closure when any date-literal rule is set. * - * @param array $c - * @param list $checks + * @param array $c + * @param list $checks */ private static function addDateChecks(array $c, array &$checks): void { @@ -543,7 +547,7 @@ private static function addDateChecks(array $c, array &$checks): void } if ($dateFormat !== null) { - $d = DateTime::createFromFormat('!'.$dateFormat, $v); + $d = DateTime::createFromFormat('!' . $dateFormat, $v); return $d !== false && $d->format($dateFormat) === $v; } @@ -590,8 +594,8 @@ private static function timestampWithinBounds( /** * Append digit-check closure(s) when `digits:` or `digits_between:` is set. * - * @param array $c - * @param list $checks + * @param array $c + * @param list $checks */ private static function addDigitChecks(array $c, array &$checks): void { diff --git a/src/FastCheckCompiler.php b/src/FastCheckCompiler.php index e0a611d..1d3f8a3 100644 --- a/src/FastCheckCompiler.php +++ b/src/FastCheckCompiler.php @@ -6,9 +6,9 @@ use Closure; use Simtabi\Laranail\Validation\FastCheck\CoreValueCompiler; +use Simtabi\Laranail\Validation\FastCheck\ProhibitedCompiler; use Simtabi\Laranail\Validation\FastCheck\ItemContextCompiler; use Simtabi\Laranail\Validation\FastCheck\PresenceConditionalCompiler; -use Simtabi\Laranail\Validation\FastCheck\ProhibitedCompiler; /** * Thin dispatcher over per-family compilers under {@see FastCheck}. diff --git a/src/FluentRule.php b/src/FluentRule.php index be18ada..6993c9c 100644 --- a/src/FluentRule.php +++ b/src/FluentRule.php @@ -4,31 +4,31 @@ namespace Simtabi\Laranail\Validation; -use BackedEnum; use Closure; -use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Support\Traits\Macroable; +use BackedEnum; use Illuminate\Validation\Rules\AnyOf; -use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; -use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; -use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Contracts\Support\Arrayable; +use Simtabi\Laranail\Validation\Rules\Net\Subdomain; +use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Rules\Net\DomainName; +use Simtabi\Laranail\Validation\Builder\Nodes\UrlRule; use Simtabi\Laranail\Validation\Builder\Nodes\DateRule; -use Simtabi\Laranail\Validation\Builder\Nodes\DeclinedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; +use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; use Simtabi\Laranail\Validation\Builder\Nodes\ImageRule; -use Simtabi\Laranail\Validation\Builder\Nodes\IpAddressRule; -use Simtabi\Laranail\Validation\Builder\Nodes\MacAddressRule; -use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; -use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; use Simtabi\Laranail\Validation\Builder\Nodes\PhoneRule; use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; -use Simtabi\Laranail\Validation\Builder\Nodes\UrlRule; +use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; +use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; +use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\DeclinedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; use Simtabi\Laranail\Validation\Builder\Nodes\UsernameRule; -use Simtabi\Laranail\Validation\Rules\Net\DomainName; -use Simtabi\Laranail\Validation\Rules\Net\Subdomain; -use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Builder\Nodes\IpAddressRule; +use Simtabi\Laranail\Validation\Builder\Nodes\MacAddressRule; class FluentRule { @@ -395,7 +395,7 @@ public static function field(?string $label = null): FieldRule } /** - * @param array $rules + * @param array $rules */ public static function anyOf(array $rules): AnyOf { diff --git a/src/FluentSchema.php b/src/FluentSchema.php index 09e4e14..7667fd3 100644 --- a/src/FluentSchema.php +++ b/src/FluentSchema.php @@ -4,27 +4,27 @@ namespace Simtabi\Laranail\Validation; -use BackedEnum; use Closure; -use Illuminate\Contracts\Support\Arrayable; +use BackedEnum; use Illuminate\Validation\Rules\AnyOf; -use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; -use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; -use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; +use Illuminate\Contracts\Support\Arrayable; +use Simtabi\Laranail\Validation\Builder\Nodes\UrlRule; use Simtabi\Laranail\Validation\Builder\Nodes\DateRule; -use Simtabi\Laranail\Validation\Builder\Nodes\DeclinedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; +use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; use Simtabi\Laranail\Validation\Builder\Nodes\ImageRule; -use Simtabi\Laranail\Validation\Builder\Nodes\IpAddressRule; -use Simtabi\Laranail\Validation\Builder\Nodes\MacAddressRule; -use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; -use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; use Simtabi\Laranail\Validation\Builder\Nodes\PhoneRule; use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; -use Simtabi\Laranail\Validation\Builder\Nodes\UrlRule; +use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; +use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; +use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\DeclinedRule; +use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; use Simtabi\Laranail\Validation\Builder\Nodes\UsernameRule; +use Simtabi\Laranail\Validation\Builder\Nodes\IpAddressRule; +use Simtabi\Laranail\Validation\Builder\Nodes\MacAddressRule; /** * Instance-based mirror of the {@see FluentRule} static factory. @@ -51,7 +51,7 @@ final class FluentSchema * Forward anything not declared above to {@see FluentRule}, so macros * registered on the static factory are also reachable on the instance. * - * @param array $arguments + * @param array $arguments */ public function __call(string $name, array $arguments): mixed { @@ -223,7 +223,7 @@ public function field(?string $label = null): FieldRule } /** - * @param array $rules + * @param array $rules */ public function anyOf(array $rules): AnyOf { diff --git a/src/FluentValidator.php b/src/FluentValidator.php index f0bbabe..b114e2a 100644 --- a/src/FluentValidator.php +++ b/src/FluentValidator.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation; +use ReflectionProperty; use Illuminate\Contracts\Translation\Translator; use Illuminate\Validation\DatabasePresenceVerifier; -use ReflectionProperty; use Simtabi\Laranail\Validation\Internal\PreparesOptimizedRules; /** @@ -35,10 +35,10 @@ abstract class FluentValidator extends OptimizedValidator use PreparesOptimizedRules; /** - * @param array $data - * @param array $rules - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $rules + * @param array $messages + * @param array $attributes */ public function __construct( array $data, diff --git a/src/HasFluentRules.php b/src/HasFluentRules.php index f7d6676..110bc14 100644 --- a/src/HasFluentRules.php +++ b/src/HasFluentRules.php @@ -4,14 +4,14 @@ namespace Simtabi\Laranail\Validation; -use Illuminate\Container\Container; -use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Illuminate\Validation\Validator; use ReflectionMethod; -use ReflectionNamedType; use ReflectionProperty; -use Simtabi\Laranail\Validation\Internal\PreparesOptimizedRules; +use ReflectionNamedType; +use Illuminate\Container\Container; +use Illuminate\Validation\Validator; use Simtabi\Laranail\Validation\Internal\ValidatorStateCopier; +use Simtabi\Laranail\Validation\Internal\PreparesOptimizedRules; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; /** * Add this trait to a FormRequest to enable FluentRule features: @@ -99,8 +99,8 @@ protected function createDefaultValidator(ValidationFactory $factory): Validator $this->container->call([$this, 'rules']), ), $hasSchema => $this->container->call([$this, 'schema']), - $hasRules => $this->container->call([$this, 'rules']), - default => [], + $hasRules => $this->container->call([$this, 'rules']), + default => [], }; /** @var array $data */ @@ -252,10 +252,10 @@ private function definesOwnRules(): bool * (extensions, container, presence verifier, excludeUnvalidatedArrayKeys) * without mutating the shared factory's resolver. Octane-safe. * - * @param array $data - * @param array $rules - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $rules + * @param array $messages + * @param array $attributes */ private function makeOptimizedValidator( ValidationFactory $factory, diff --git a/src/HasFluentValidation.php b/src/HasFluentValidation.php index 18b2237..9bf9cf5 100644 --- a/src/HasFluentValidation.php +++ b/src/HasFluentValidation.php @@ -167,9 +167,10 @@ public function validateOnly(mixed $field, mixed $rules = null, mixed $messages * Compile FluentRule objects to native format, expand wildcards * against actual data, and extract labels/messages. * - * @param array|null $rules - * @param array $messages - * @param array $attributes + * @param array|null $rules + * @param array $messages + * @param array $attributes + * * @return array{0: array|null, 1: array, 2: array} */ protected function compileFluentRules(?array $rules, array $messages, array $attributes): array @@ -218,7 +219,8 @@ protected function compileFluentRules(?array $rules, array $messages, array $att /** * Merge with Livewire's rulesFromOutside (same as parent::getRules()). * - * @param array $rules + * @param array $rules + * * @return array */ private function mergeRulesFromOutside(array $rules): array diff --git a/src/Internal/AbstractConditionalReducer.php b/src/Internal/AbstractConditionalReducer.php index 76f98f3..f517bfb 100644 --- a/src/Internal/AbstractConditionalReducer.php +++ b/src/Internal/AbstractConditionalReducer.php @@ -28,7 +28,7 @@ abstract class AbstractConditionalReducer * reducer recognises? Used by callers to decide whether the per-item * reducer path must engage. * - * @param array $itemRules + * @param array $itemRules */ public static function hasAny(array $itemRules): bool { @@ -57,7 +57,7 @@ public static function hasAny(array $itemRules): bool * Subclasses' `apply()` methods bind their per-call context (field, * itemData, etc.) into a closure and hand it here. * - * @param Closure(string): ?string $rewriteOne + * @param Closure(string): ?string $rewriteOne */ protected static function applyTemplate(mixed $rule, Closure $rewriteOne): mixed { @@ -105,7 +105,7 @@ protected static function applyTemplate(mixed $rule, Closure $rewriteOne): mixed * rewrite path doesn't bypass a `{field}.{rule}`-style override at * message-formatting time. Identical across all conditional reducers. * - * @param array $itemMessages + * @param array $itemMessages */ protected static function hasCustomMessage(string $field, string $ruleName, array $itemMessages): bool { @@ -115,8 +115,8 @@ protected static function hasCustomMessage(string $field, string $ruleName, arra // Match any message whose key equals `{field}.{rule}` or ends with // `.{field}.{rule}` — covers bare-field, wildcard-prefixed, and // any parent-prefixed variant. - $suffix = '.'.$field.'.'.$ruleName; - $exactKey = $field.'.'.$ruleName; + $suffix = '.' . $field . '.' . $ruleName; + $exactKey = $field . '.' . $ruleName; foreach (array_keys($itemMessages) as $key) { $key = (string) $key; if ($key === $exactKey || str_ends_with($key, $suffix)) { @@ -125,7 +125,7 @@ protected static function hasCustomMessage(string $field, string $ruleName, arra } if (function_exists('trans')) { - $translatorKey = 'validation.custom.'.$field.'.'.$ruleName; + $translatorKey = 'validation.custom.' . $field . '.' . $ruleName; $translated = trans($translatorKey); if (is_string($translated) && $translated !== $translatorKey) { return true; @@ -137,10 +137,10 @@ protected static function hasCustomMessage(string $field, string $ruleName, arra // `validation.custom` namespace — mirror Validator::getCustomMessageFromTranslator. $custom = trans('validation.custom'); if (is_array($custom)) { - $shortKey = $field.'.'.$ruleName; + $shortKey = $field . '.' . $ruleName; foreach (array_keys(Arr::dot($custom)) as $customKey) { $customKey = (string) $customKey; - if ($customKey === $shortKey || str_ends_with($customKey, '.'.$shortKey)) { + if ($customKey === $shortKey || str_ends_with($customKey, '.' . $shortKey)) { return true; } } diff --git a/src/Internal/BatchLimitRemap.php b/src/Internal/BatchLimitRemap.php index a34f7c2..653633a 100644 --- a/src/Internal/BatchLimitRemap.php +++ b/src/Internal/BatchLimitRemap.php @@ -4,10 +4,10 @@ namespace Simtabi\Laranail\Validation\Internal; +use ReflectionProperty; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Validator as IlluminateValidator; -use ReflectionProperty; use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; /** diff --git a/src/Internal/BatchPresenceQuery.php b/src/Internal/BatchPresenceQuery.php index 20b72de..4211dc9 100644 --- a/src/Internal/BatchPresenceQuery.php +++ b/src/Internal/BatchPresenceQuery.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Internal; -use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\DB; +use Illuminate\Database\Query\Builder; /** * Runs the batched whereIn query for exists/unique presence verification, @@ -19,8 +19,9 @@ final class BatchPresenceQuery private const int CHUNK_SIZE = 1000; /** - * @param array $values - * @param array $wheres + * @param array $values + * @param array $wheres + * * @return array */ public static function run( @@ -62,7 +63,7 @@ public static function run( /** * Apply a single Laravel-style where condition to the query. * - * @param array{column: string, value: string} $where + * @param array{column: string, value: string} $where */ private static function applyWhere(Builder $query, array $where): void { diff --git a/src/Internal/BatchRuleShape.php b/src/Internal/BatchRuleShape.php index c6af149..feb86aa 100644 --- a/src/Internal/BatchRuleShape.php +++ b/src/Internal/BatchRuleShape.php @@ -24,7 +24,7 @@ final class BatchRuleShape * the whole shape, so the annotation stays honest about its one caller, * even though only three of the keys are read. * - * @param array{connection: string|null, table: string, column: string, wheres: array, ignore: mixed, idColumn: string}|null $meta + * @param array{connection: string|null, table: string, column: string, wheres: array, ignore: mixed, idColumn: string}|null $meta */ public static function of(?array $meta): string { diff --git a/src/Internal/ConditionalEvaluationPhase.php b/src/Internal/ConditionalEvaluationPhase.php index a4a2f09..088eccc 100644 --- a/src/Internal/ConditionalEvaluationPhase.php +++ b/src/Internal/ConditionalEvaluationPhase.php @@ -59,7 +59,8 @@ public static function resolveWildcard(string $attribute, string $conditionField * rule shape. Accepts the Validator's untyped `$rules` array directly; * non-string keys are filtered inside. * - * @param array $rules + * @param array $rules + * * @return array}>> */ public function indexConditionalAttrs(array $rules): array @@ -97,13 +98,13 @@ public function indexConditionalAttrs(array $rules): array * because `Validator::getValue()` is protected — the closure must be * constructed inside the validator subclass to capture scope. * - * @param list}> $tuples - * @param Closure(string): mixed $getValue - * @param array $rules The validator's parsed rules, read - * only to learn whether a dependent - * is declared `boolean`. Defaults to - * none, which simply skips the - * conversion. + * @param list}> $tuples + * @param Closure(string): mixed $getValue + * @param array $rules The validator's parsed rules, read + * only to learn whether a dependent + * is declared `boolean`. Defaults to + * none, which simply skips the + * conversion. */ public function evaluate(string $attribute, array $tuples, Closure $getValue, array $rules = []): ConditionalVerdict { diff --git a/src/Internal/ConditionalValueMatcher.php b/src/Internal/ConditionalValueMatcher.php index 85845cc..45e179b 100644 --- a/src/Internal/ConditionalValueMatcher.php +++ b/src/Internal/ConditionalValueMatcher.php @@ -25,9 +25,9 @@ final class ConditionalValueMatcher * Does the dependent at `$depPath` match `$rawValues` under Laravel's * `validateRequiredIf`/`…Unless` comparison semantics? * - * @param list $rawValues The rule's value parameters. - * @param array $itemData - * @param array $itemRules Item-scoped rules, for the `boolean` declaration check. + * @param list $rawValues The rule's value parameters. + * @param array $itemData + * @param array $itemRules Item-scoped rules, for the `boolean` declaration check. */ public static function matches(string $depPath, array $rawValues, array $itemData, array $itemRules): bool { @@ -44,7 +44,7 @@ public static function matches(string $depPath, array $rawValues, array $itemDat * through a validator's protected `getValue()`), so they don't re-resolve * via `data_get`. * - * @param list $rawValues + * @param list $rawValues */ public static function matchesValue(mixed $other, array $rawValues, bool $dependentHasBooleanRule = false): bool { @@ -74,7 +74,7 @@ public static function matchesValue(mixed $other, array $rawValues, bool $depend * `array-key`, not `string`: the top-level caller hands over the * validator's own `$rules`, which Laravel types as a bare array. * - * @param array $itemRules + * @param array $itemRules */ public static function dependentHasBooleanRule(string $depPath, array $itemRules): bool { @@ -103,7 +103,7 @@ public static function dependentHasBooleanRule(string $depPath, array $itemRules * - any other scalar compares by string coercion, covering numeric-string ↔ * numeric (`'1'` ↔ `1`). * - * @param list $values + * @param list $values */ private static function scalarLooseIn(int|float|string $other, array $values): bool { @@ -128,14 +128,15 @@ private static function scalarLooseIn(int|float|string $other, array $values): b * value is already a bool; null conversion when the resolved value is * null. Order matters — bool first, then null. * - * @param list $values + * @param list $values + * * @return list */ private static function convertValues(array $values, mixed $other, bool $dependentHasBooleanRule): array { if (is_bool($other) || $dependentHasBooleanRule) { $values = array_map(static fn (mixed $v): mixed => match ($v) { - 'true' => true, + 'true' => true, 'false' => false, default => $v, }, $values); diff --git a/src/Internal/DatabaseClaimScanner.php b/src/Internal/DatabaseClaimScanner.php index 1fbd9ae..8d21d5b 100644 --- a/src/Internal/DatabaseClaimScanner.php +++ b/src/Internal/DatabaseClaimScanner.php @@ -33,7 +33,8 @@ final class DatabaseClaimScanner { /** - * @param array $rules Field (or expanded attribute) → rules + * @param array $rules Field (or expanded attribute) → rules + * * @return array */ public static function findPoisonedTableColumns(array $rules): array @@ -114,9 +115,9 @@ private static function describeObjectClaim(string $field, Exists|Unique $rule, $signature = BatchDatabaseChecker::isBatchable($rule) ? BatchDatabaseChecker::batchableSignature($rule, $table, $column) - : 'unbatchable#'.$marker++; + : 'unbatchable#' . $marker++; - return [$table.':'.$column, $signature]; + return [$table . ':' . $column, $signature]; } /** @return array{0: string, 1: string}|null */ @@ -141,7 +142,7 @@ private static function describeStringClaim(string $field, string $rule, int &$m $column = self::leafAttribute($field); } - return [$table.':'.$column, 'string#'.$marker++]; + return [$table . ':' . $column, 'string#' . $marker++]; } /** diff --git a/src/Internal/ExcludeConditionExtractor.php b/src/Internal/ExcludeConditionExtractor.php index 3414f14..6f1b3d9 100644 --- a/src/Internal/ExcludeConditionExtractor.php +++ b/src/Internal/ExcludeConditionExtractor.php @@ -97,7 +97,8 @@ private static function parseSegment(mixed $segment): ?array } /** - * @param array $segment + * @param array $segment + * * @return array{action: string, field: string, values: list}|null */ private static function parseTuple(array $segment): ?array @@ -115,7 +116,7 @@ private static function parseTuple(array $segment): ?array return [ 'action' => $action, - 'field' => $field, + 'field' => $field, 'values' => array_map( static fn (mixed $v): string => is_scalar($v) ? (string) $v : '', array_values(array_slice($segment, 2)), @@ -129,7 +130,7 @@ private static function parseTuple(array $segment): ?array private static function parseString(string $segment): ?array { foreach (self::ACTIONS as $action) { - $prefix = $action.':'; + $prefix = $action . ':'; if (! str_starts_with($segment, $prefix)) { continue; @@ -146,7 +147,7 @@ private static function parseString(string $segment): ?array return [ 'action' => $action, - 'field' => $params[0], + 'field' => $params[0], 'values' => array_map(static fn (?string $v): string => (string) $v, array_slice($params, 1)), ]; } diff --git a/src/Internal/ItemErrorCollector.php b/src/Internal/ItemErrorCollector.php index e8ffad6..c7546e3 100644 --- a/src/Internal/ItemErrorCollector.php +++ b/src/Internal/ItemErrorCollector.php @@ -17,8 +17,8 @@ final class ItemErrorCollector { /** - * @param iterable): bool> $fastChecks - * @param array $itemData + * @param iterable): bool> $fastChecks + * @param array $itemData */ public function passesAllFastChecks(iterable $fastChecks, array $itemData): bool { @@ -35,7 +35,7 @@ public function passesAllFastChecks(iterable $fastChecks, array $itemData): bool * Append errors from a failed per-item validator into the accumulator, * keyed by full dotted path (`{parent}.{index}.{field}` or `{parent}.{index}` for scalar each). * - * @param array> $errors + * @param array> $errors */ public function collectErrors(Validator $validator, string $parent, int|string $index, bool $isScalar, array &$errors): void { diff --git a/src/Internal/ItemRuleCompiler.php b/src/Internal/ItemRuleCompiler.php index 138d4da..0beeb2a 100644 --- a/src/Internal/ItemRuleCompiler.php +++ b/src/Internal/ItemRuleCompiler.php @@ -5,13 +5,13 @@ namespace Simtabi\Laranail\Validation\Internal; use Closure; +use Stringable; use Illuminate\Support\Arr; -use Simtabi\Laranail\Validation\BatchDatabaseChecker; use Simtabi\Laranail\Validation\FastCheckCompiler; -use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; -use Simtabi\Laranail\Validation\PresenceConditionalReducer; +use Simtabi\Laranail\Validation\BatchDatabaseChecker; use Simtabi\Laranail\Validation\ValueConditionalReducer; -use Stringable; +use Simtabi\Laranail\Validation\PresenceConditionalReducer; +use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; /** * Collaborator for {@see ItemValidator}. Extracts the rule-shape concerns @@ -31,7 +31,8 @@ final class ItemRuleCompiler * evaluation. A field may carry more than one exclude_* rule; all of them * must be evaluated (the field is excluded if any fires), matching native. * - * @param array $itemRules + * @param array $itemRules + * * @return array}>> */ public function analyzeConditionals(array $itemRules): array @@ -54,10 +55,11 @@ public function analyzeConditionals(array $itemRules): array /** * Reduce item rules by evaluating conditional exclusions against the item data. * - * @param array $itemRules - * @param array $itemData - * @param array}>> $conditionalFields - * @param array $itemMessages + * @param array $itemRules + * @param array $itemData + * @param array}>> $conditionalFields + * @param array $itemMessages + * * @return array */ public function reduceRulesForItem(array $itemRules, array $itemData, array $conditionalFields, array $itemMessages = []): array @@ -85,7 +87,7 @@ public function reduceRulesForItem(array $itemRules, array $itemData, array $con * same field. Returns the field name (e.g., "type") or null if conditions * reference different fields or there are no conditionals. * - * @param array}>> $conditionalFields + * @param array}>> $conditionalFields */ public function findCommonDispatchField(array $conditionalFields): ?string { @@ -112,7 +114,7 @@ public function findCommonDispatchField(array $conditionalFields): ?string * Delegate to `RuleCacheKey::for` — see that class for the rationale on * why field names alone are insufficient after per-item reducers engage. * - * @param array $rules + * @param array $rules */ public function ruleCacheKey(array $rules): string { @@ -123,7 +125,8 @@ public function ruleCacheKey(array $rules): string * Build fast-check closures for eligible fields. * Returns fast checks for compilable fields and the remaining slow rules. * - * @param array $compiledRules + * @param array $compiledRules + * * @return array{0: list): bool>, 1: array} */ public function buildFastChecks(array $compiledRules): array @@ -233,8 +236,8 @@ public function buildFastChecks(array $compiledRules): array * Build a PrecomputedPresenceVerifier by batching all exists/unique values * from slow rules across all items in a single whereIn query. * - * @param array $slowRules - * @param array $items + * @param array $slowRules + * @param array $items */ public function buildBatchVerifier(array $slowRules, array $items, bool $isScalar): ?PrecomputedPresenceVerifier { @@ -260,9 +263,9 @@ public function buildBatchVerifier(array $slowRules, array $items, bool $isScala * Is the field excluded by ANY of its exclude_* conditions? Mirrors native * Laravel, which evaluates every exclude rule on a field. * - * @param list}> $conditions - * @param array $itemData - * @param array $itemRules + * @param list}> $conditions + * @param array $itemData + * @param array $itemRules */ private function fieldIsExcluded(array $conditions, array $itemData, array $itemRules): bool { diff --git a/src/Internal/ItemValidator.php b/src/Internal/ItemValidator.php index 7bd2b5d..ab3c1d1 100644 --- a/src/Internal/ItemValidator.php +++ b/src/Internal/ItemValidator.php @@ -5,14 +5,14 @@ namespace Simtabi\Laranail\Validation\Internal; use Closure; +use ReflectionProperty; use Illuminate\Support\Facades\Validator; +use Simtabi\Laranail\Validation\MemoizingValidator; use Illuminate\Validation\Validator as BaseValidator; -use ReflectionProperty; use Simtabi\Laranail\Validation\BatchDatabaseChecker; -use Simtabi\Laranail\Validation\MemoizingValidator; -use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; -use Simtabi\Laranail\Validation\PresenceConditionalReducer; use Simtabi\Laranail\Validation\ValueConditionalReducer; +use Simtabi\Laranail\Validation\PresenceConditionalReducer; +use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; /** * Executes the per-item validation loop for a wildcard group. Applies @@ -36,10 +36,11 @@ public function __construct( ) {} /** - * @param array $items - * @param array $itemRules - * @param array $itemMessages - * @param array $itemAttributes + * @param array $items + * @param array $itemRules + * @param array $itemMessages + * @param array $itemAttributes + * * @return array> */ public function validate(array $items, array $itemRules, array $itemMessages, array $itemAttributes, string $parent, bool $isScalar): array @@ -187,10 +188,10 @@ public function validate(array $items, array $itemRules, array $itemMessages, ar * its own memoization, a consumer's overrides keep firing. Only the plain * default is safe to optimize. * - * @param array $itemData - * @param array $rules - * @param array $messages - * @param array $attributes + * @param array $itemData + * @param array $rules + * @param array $messages + * @param array $attributes */ /** * Clear exclusions carried over from the previous item. @@ -225,10 +226,10 @@ private function resetExclusions(BaseValidator $validator): void } /** - * @param array $itemData - * @param array $rules - * @param array $messages - * @param array $attributes + * @param array $itemData + * @param array $rules + * @param array $messages + * @param array $attributes */ private function makeItemValidator(array $itemData, array $rules, array $messages, array $attributes): BaseValidator { diff --git a/src/Internal/PreparesOptimizedRules.php b/src/Internal/PreparesOptimizedRules.php index 150d1e6..bb65527 100644 --- a/src/Internal/PreparesOptimizedRules.php +++ b/src/Internal/PreparesOptimizedRules.php @@ -6,14 +6,14 @@ use Closure; use Illuminate\Support\Arr; +use Illuminate\Validation\Validator; use Illuminate\Validation\Rules\Unique; use Illuminate\Validation\ValidationException; -use Illuminate\Validation\Validator; -use Simtabi\Laranail\Validation\BatchDatabaseChecker; -use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; +use Simtabi\Laranail\Validation\PreparedRules; use Simtabi\Laranail\Validation\OptimizedValidator; +use Simtabi\Laranail\Validation\BatchDatabaseChecker; use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; -use Simtabi\Laranail\Validation\PreparedRules; +use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; /** * Shared rule-preparation steps for the optimized validation entry points. @@ -36,8 +36,9 @@ trait PreparesOptimizedRules * don't match the actual data. Operates on expanded rules (concrete * paths like interactions.5.style.top) before they reach the validator. * - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data + * * @return array */ private function preExcludeRules(array $rules, array $data): array @@ -82,7 +83,8 @@ private function preExcludeRules(array $rules, array $data): array /** * Build fast-check closures and the attribute-to-pattern lookup map. * - * @param array $preparedRules Rules after pre-exclusion + * @param array $preparedRules Rules after pre-exclusion + * * @return array{0: array, 1: array} */ private function buildFastCheckMaps(PreparedRules $prepared, array $preparedRules): array @@ -100,9 +102,10 @@ private function buildFastCheckMaps(PreparedRules $prepared, array $preparedRule } /** - * @param array $fastChecks - * @param array> $implicitAttributes - * @param array $preparedRules + * @param array $fastChecks + * @param array> $implicitAttributes + * @param array $preparedRules + * * @return array */ private function buildAttributePatternMap(array $fastChecks, array $implicitAttributes, array $preparedRules): array @@ -129,8 +132,8 @@ private function buildAttributePatternMap(array $fastChecks, array $implicitAttr * exists/unique checks on single fields. No-op when there are no wildcard * attributes or the batch checker isn't available (no DB bound). * - * @param array $preparedRules - * @param array $data + * @param array $preparedRules + * @param array $data * * @throws ValidationException when a parent array exceeds its max:N budget */ @@ -173,9 +176,9 @@ private function applyBatchPresenceVerifier(Validator $validator, PreparedRules * happens before the validator runs, `failedValidation()` is NOT invoked. * - Hard cap (Phase 2): enforced inside `BatchDatabaseChecker::buildVerifier`. * - * @param array $preparedRules Full prepared rules (NOT pre-intersected). - * @param array $data - * @param list $wildcardAttributes Concrete expanded wildcard paths (e.g. `items.0.id`). + * @param array $preparedRules Full prepared rules (NOT pre-intersected). + * @param array $data + * @param list $wildcardAttributes Concrete expanded wildcard paths (e.g. `items.0.id`). * * @throws BatchLimitExceededException */ @@ -208,9 +211,9 @@ private function buildWildcardBatchVerifier(array $preparedRules, array $data, a * Exists / Unique rule — no point refusing to query when we weren't * going to query anyway. * - * @param array $wildcardRules Rules keyed by concrete wildcard path. - * @param array $preparedRules Full rules, needed to find parent rule strings. - * @param array $data + * @param array $wildcardRules Rules keyed by concrete wildcard path. + * @param array $preparedRules Full rules, needed to find parent rule strings. + * @param array $data * * @throws BatchLimitExceededException */ diff --git a/src/Internal/PresenceMatchFidelity.php b/src/Internal/PresenceMatchFidelity.php index e08267c..fd585d0 100644 --- a/src/Internal/PresenceMatchFidelity.php +++ b/src/Internal/PresenceMatchFidelity.php @@ -42,8 +42,8 @@ final class PresenceMatchFidelity * one direction only: it can decline to batch a group that would have * been fine, never the reverse. * - * @param list $values - * @param array $fetched + * @param list $values + * @param array $fetched */ public static function isFaithful(array $values, array $fetched): bool { diff --git a/src/Internal/RuleCacheKey.php b/src/Internal/RuleCacheKey.php index 76cf17b..e3d35d6 100644 --- a/src/Internal/RuleCacheKey.php +++ b/src/Internal/RuleCacheKey.php @@ -24,8 +24,8 @@ public static function for(array $rules): string $parts = []; foreach ($rules as $field => $rule) { $parts[] = is_string($rule) - ? $field.'='.$rule - : $field.'='.self::nonStringFingerprint($rule); + ? $field . '=' . $rule + : $field . '=' . self::nonStringFingerprint($rule); } return implode("\x1f", $parts); @@ -44,14 +44,14 @@ private static function nonStringFingerprint(mixed $rule): string foreach ($rule as $item) { $parts[] = is_string($item) ? $item - : (is_object($item) ? '#'.spl_object_id($item) : gettype($item)); + : (is_object($item) ? '#' . spl_object_id($item) : gettype($item)); } - return '['.implode(',', $parts).']'; + return '[' . implode(',', $parts) . ']'; } if (is_object($rule)) { - return '#'.spl_object_id($rule); + return '#' . spl_object_id($rule); } return gettype($rule); diff --git a/src/Internal/ValidatorStateCopier.php b/src/Internal/ValidatorStateCopier.php index fd794fc..b7c40ab 100644 --- a/src/Internal/ValidatorStateCopier.php +++ b/src/Internal/ValidatorStateCopier.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Internal; -use Illuminate\Validation\Validator; use ReflectionObject; +use Illuminate\Validation\Validator; /** * Copies factory-applied state from a base {@see Validator} (built through the diff --git a/src/Internal/ValueTypePredicates.php b/src/Internal/ValueTypePredicates.php index c9b69ba..5d791ea 100644 --- a/src/Internal/ValueTypePredicates.php +++ b/src/Internal/ValueTypePredicates.php @@ -20,8 +20,9 @@ final class ValueTypePredicates { /** - * @param array $values - * @param array|string $itemRules + * @param array $values + * @param array|string $itemRules + * * @return array */ public static function filter(array $values, array|string $itemRules): array @@ -36,7 +37,8 @@ public static function filter(array $values, array|string $itemRules): array } /** - * @param array|string $itemRules + * @param array|string $itemRules + * * @return list */ private static function derive(array|string $itemRules): array @@ -55,7 +57,8 @@ private static function derive(array|string $itemRules): array } /** - * @param array|string $itemRules + * @param array|string $itemRules + * * @return list */ private static function stringRules(array|string $itemRules): array @@ -90,11 +93,11 @@ private static function predicateFor(string $rule): ?Closure return match ($token) { 'integer', 'int' => static fn (mixed $v): bool => filter_var($v, FILTER_VALIDATE_INT) !== false, - 'numeric' => is_numeric(...), - 'uuid' => static fn (mixed $v): bool => is_string($v) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $v) === 1, - 'ulid' => static fn (mixed $v): bool => is_string($v) && preg_match('/^[0-9A-HJKMNP-TV-Z]{26}$/i', $v) === 1, - 'string' => static fn (mixed $v): bool => is_scalar($v) || $v instanceof Stringable, - default => null, + 'numeric' => is_numeric(...), + 'uuid' => static fn (mixed $v): bool => is_string($v) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $v) === 1, + 'ulid' => static fn (mixed $v): bool => is_string($v) && preg_match('/^[0-9A-HJKMNP-TV-Z]{26}$/i', $v) === 1, + 'string' => static fn (mixed $v): bool => is_scalar($v) || $v instanceof Stringable, + default => null, }; } } diff --git a/src/Internal/VanillaAfterRoute.php b/src/Internal/VanillaAfterRoute.php index 08f4ce2..047ce15 100644 --- a/src/Internal/VanillaAfterRoute.php +++ b/src/Internal/VanillaAfterRoute.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Internal; use Closure; +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\RuleSet; /** * The one-validator route `RuleSet::after()` forces. @@ -23,11 +23,12 @@ final class VanillaAfterRoute { /** - * @param array $compiledRules Output of {@see RuleSet::compile()} over the flattened fields. - * @param array $data - * @param array $messages - * @param array $attributes - * @param list $afterCallbacks + * @param array $compiledRules Output of {@see RuleSet::compile()} over the flattened fields. + * @param array $data + * @param array $messages + * @param array $attributes + * @param list $afterCallbacks + * * @return array * * @throws ValidationException diff --git a/src/MemoizingValidator.php b/src/MemoizingValidator.php index 7b0dc09..f7bcbb0 100644 --- a/src/MemoizingValidator.php +++ b/src/MemoizingValidator.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation; -use Illuminate\Validation\ValidationRuleParser; use Illuminate\Validation\Validator; +use Illuminate\Validation\ValidationRuleParser; /** * Validator subclass that memoizes string-rule parsing. Behaviour-identical @@ -68,8 +68,9 @@ public static function resetParseCache(): void * Get a rule and its parameters for a given attribute, memoizing the parse * of string rules. Byte-identical to the parent otherwise. * - * @param string $attribute - * @param string|array $rules + * @param string $attribute + * @param string|array $rules + * * @return array|null */ protected function getRule($attribute, $rules): ?array diff --git a/src/OptimizedValidator.php b/src/OptimizedValidator.php index f222922..54037c7 100644 --- a/src/OptimizedValidator.php +++ b/src/OptimizedValidator.php @@ -5,11 +5,11 @@ namespace Simtabi\Laranail\Validation; use Closure; +use Stringable; use Illuminate\Support\Arr; use Illuminate\Support\MessageBag; -use Simtabi\Laranail\Validation\Internal\ConditionalEvaluationPhase; use Simtabi\Laranail\Validation\Internal\ConditionalVerdict; -use Stringable; +use Simtabi\Laranail\Validation\Internal\ConditionalEvaluationPhase; /** * Validator subclass that fast-checks expanded wildcard attributes @@ -48,7 +48,8 @@ class OptimizedValidator extends MemoizingValidator * Only string-only rules are eligible. Object rules, date comparisons, * cross-field references, distinct, size/between are skipped. * - * @param array $compiledRules Compiled rules keyed by wildcard pattern + * @param array $compiledRules Compiled rules keyed by wildcard pattern + * * @return array */ public static function buildFastChecks(array $compiledRules): array @@ -71,8 +72,8 @@ public static function buildFastChecks(array $compiledRules): array } /** - * @param array $fastChecks - * @param array $attributePatternMap + * @param array $fastChecks + * @param array $attributePatternMap */ public function withFastChecks(array $fastChecks, array $attributePatternMap): static { @@ -132,7 +133,7 @@ public function passes(): bool * longer matched a notify of 1 — leaving a required field unenforced * rather than reporting an error. * - * @param string $parameter + * @param string $parameter */ protected function shouldConvertToBoolean($parameter): bool { @@ -197,7 +198,7 @@ private function runFastCheckPhase(): array * {@see ConditionalEvaluationPhase} so this validator stays focused on * orchestration and Laravel-validator state mutation. * - * @param array $removedRules + * @param array $removedRules */ private function runConditionalPhase(array &$removedRules): void { @@ -241,7 +242,7 @@ private function runConditionalPhase(array &$removedRules): void * portion of an attribute's rules. Returns true when the closure * compiled and the value passed — caller may then drop the attribute. * - * @param list $rules + * @param list $rules */ private function tryFastCheckRemaining(string $attribute, array $rules): bool { @@ -283,7 +284,7 @@ private function finalizeWithoutParent(): bool * Extract the non-conditional string rules from an attribute's rule array * and join them into a pipe-delimited string for fast-check compilation. * - * @param list $rules + * @param list $rules */ private function extractNonConditionalRule(array $rules): ?string { diff --git a/src/PrecomputedPresenceVerifier.php b/src/PrecomputedPresenceVerifier.php index 501ae6a..46f0135 100644 --- a/src/PrecomputedPresenceVerifier.php +++ b/src/PrecomputedPresenceVerifier.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation; -use Illuminate\Validation\DatabasePresenceVerifierInterface; -use Illuminate\Validation\PresenceVerifierInterface; use Stringable; +use Illuminate\Validation\PresenceVerifierInterface; +use Illuminate\Validation\DatabasePresenceVerifierInterface; /** * A presence verifier that returns pre-computed results from batch queries. @@ -36,7 +36,7 @@ public function __construct(private readonly ?PresenceVerifierInterface $fallbac * Values are cast to strings for loose comparison matching database behavior * (databases do implicit type coercion on WHERE column = value). * - * @param array $values Values that exist in the database + * @param array $values Values that exist in the database */ public function addLookup(string $table, string $column, array $values): void { @@ -54,13 +54,13 @@ public function addLookup(string $table, string $column, array $values): void } } - $this->lookups[$table.':'.$column] = $map; + $this->lookups[$table . ':' . $column] = $map; } /** @param array $extra */ public function getCount(mixed $collection, mixed $column, mixed $value, mixed $excludeId = null, mixed $idColumn = null, array $extra = []): int { - $key = $collection.':'.$column; + $key = $collection . ':' . $column; if (! isset($this->lookups[$key])) { if ($this->fallback instanceof PresenceVerifierInterface) { @@ -74,12 +74,12 @@ public function getCount(mixed $collection, mixed $column, mixed $value, mixed $ } /** - * @param array $values - * @param array $extra + * @param array $values + * @param array $extra */ public function getMultiCount(mixed $collection, mixed $column, array $values, array $extra = []): int { - $key = $collection.':'.$column; + $key = $collection . ':' . $column; if (! isset($this->lookups[$key])) { if ($this->fallback instanceof PresenceVerifierInterface) { diff --git a/src/PreparedRules.php b/src/PreparedRules.php index 411a915..2273935 100644 --- a/src/PreparedRules.php +++ b/src/PreparedRules.php @@ -14,10 +14,10 @@ final readonly class PreparedRules { /** - * @param array $rules Compiled rules in native Laravel format - * @param array $messages Extracted per-rule messages (from ->message() / ->fieldMessage()) - * @param array $attributes Extracted labels (from ->label() / factory argument) - * @param array> $implicitAttributes Wildcard-to-concrete path mapping + * @param array $rules Compiled rules in native Laravel format + * @param array $messages Extracted per-rule messages (from ->message() / ->fieldMessage()) + * @param array $attributes Extracted labels (from ->label() / factory argument) + * @param array> $implicitAttributes Wildcard-to-concrete path mapping */ public function __construct( public array $rules, diff --git a/src/PresenceConditionalReducer.php b/src/PresenceConditionalReducer.php index 619065a..4aaed90 100644 --- a/src/PresenceConditionalReducer.php +++ b/src/PresenceConditionalReducer.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation; +use stdClass; use Illuminate\Http\UploadedFile; use Simtabi\Laranail\Validation\Internal\AbstractConditionalReducer; -use stdClass; /** * Per-item pre-evaluation of Laravel's presence-conditional rules @@ -38,8 +38,8 @@ final class PresenceConditionalReducer extends AbstractConditionalReducer * Rewrite presence-conditional rules for a single field against * item data. Handles pipe-joined strings and list-of-rules shape. * - * @param array $itemData - * @param array $itemMessages + * @param array $itemData + * @param array $itemMessages */ public static function apply(mixed $rule, string $field, array $itemData, array $itemMessages): mixed { @@ -59,7 +59,7 @@ public static function apply(mixed $rule, string $field, array $itemData, array protected static function parse(string $rule): ?array { foreach (self::RULE_NAMES as $name) { - $prefix = $name.':'; + $prefix = $name . ':'; if (str_starts_with($rule, $prefix)) { return [$name, substr($rule, strlen($prefix))]; } @@ -75,8 +75,8 @@ protected static function parse(string $rule): ?array * * Covers both single- and multi-param forms of all four rules. * - * @param array $itemData - * @param array $itemMessages + * @param array $itemData + * @param array $itemMessages */ private static function rewriteOne(string $rule, string $field, array $itemData, array $itemMessages): ?string { @@ -120,10 +120,10 @@ private static function rewriteOne(string $rule, string $field, array $itemData, * | `required_with_all` | ALL of `$params` present | * | `required_without_all` | ALL of `$params` absent | * - * @param list $params May contain `null` entries when `str_getcsv` - * resolved an empty slot (matches Laravel's - * `Arr::get($data, null)` → full-item semantics). - * @param array $itemData + * @param list $params May contain `null` entries when `str_getcsv` + * resolved an empty slot (matches Laravel's + * `Arr::get($data, null)` → full-item semantics). + * @param array $itemData */ private static function ruleActivates(string $ruleName, array $params, array $itemData): bool { @@ -139,11 +139,11 @@ private static function ruleActivates(string $ruleName, array $params, array $it } return match ($ruleName) { - 'required_with' => $anyPresent, - 'required_without' => ! $allPresent, - 'required_with_all' => $allPresent, + 'required_with' => $anyPresent, + 'required_without' => ! $allPresent, + 'required_with_all' => $allPresent, 'required_without_all' => ! $anyPresent, - default => false, + default => false, }; } @@ -154,7 +154,7 @@ private static function ruleActivates(string $ruleName, array $params, array $it * (`required_without:profile.birthdate`), so resolve via `data_get` * instead of direct array-key lookup. * - * @param array $itemData + * @param array $itemData */ private static function fieldPresent(array $itemData, ?string $field): bool { diff --git a/src/Providers/ValidationServiceProvider.php b/src/Providers/ValidationServiceProvider.php index fd896ef..ea8107b 100644 --- a/src/Providers/ValidationServiceProvider.php +++ b/src/Providers/ValidationServiceProvider.php @@ -4,35 +4,35 @@ namespace Simtabi\Laranail\Validation\Providers; -use Illuminate\Support\Facades\Validator; +use Stringable; use Illuminate\Support\Str; -use Illuminate\Validation\InvokableValidationRule; -use Illuminate\Validation\Validator as ValidationValidator; +use Illuminate\Support\Facades\Validator; use Simtabi\Laranail\Package\Tools\Package; -use Simtabi\Laranail\Package\Tools\Providers\PackageServiceProvider; -use Simtabi\Laranail\Validation\Actions\CachedDnsResolver; +use Illuminate\Validation\InvokableValidationRule; +use Simtabi\Laranail\Validation\Support\RuleAliases; use Simtabi\Laranail\Validation\BatchDatabaseChecker; -use Simtabi\Laranail\Validation\Commands\BenchmarkCommand; -use Simtabi\Laranail\Validation\Commands\DoctorCommand; use Simtabi\Laranail\Validation\Commands\RulesCommand; -use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; +use Simtabi\Laranail\Validation\Support\RuleRegistrar; +use Simtabi\Laranail\Validation\Commands\DoctorCommand; +use Simtabi\Laranail\Validation\Actions\CachedDnsResolver; +use Simtabi\Laranail\Validation\Commands\BenchmarkCommand; +use Illuminate\Validation\Validator as ValidationValidator; use Simtabi\Laranail\Validation\Contracts\Email\DnsResolver; -use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; use Simtabi\Laranail\Validation\Contracts\I18n\CountryDataset; use Simtabi\Laranail\Validation\Contracts\I18n\CurrencyDataset; use Simtabi\Laranail\Validation\Contracts\I18n\LanguageDataset; -use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; -use Simtabi\Laranail\Validation\Support\Email\BundledDisposableDomainList; -use Simtabi\Laranail\Validation\Support\Email\BundledRoleAccountList; +use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; use Simtabi\Laranail\Validation\Support\I18n\BundledCountryDataset; +use Simtabi\Laranail\Package\Tools\Providers\PackageServiceProvider; use Simtabi\Laranail\Validation\Support\I18n\BundledCurrencyDataset; use Simtabi\Laranail\Validation\Support\I18n\BundledLanguageDataset; -use Simtabi\Laranail\Validation\Support\Payment\BundledCardBrandCatalogue; -use Simtabi\Laranail\Validation\Support\RuleAliases; -use Simtabi\Laranail\Validation\Support\RuleRegistrar; +use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; +use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; +use Simtabi\Laranail\Validation\Support\Email\BundledRoleAccountList; use Simtabi\Laranail\Validation\Support\Text\DefaultReservedUsernameList; -use Stringable; +use Simtabi\Laranail\Validation\Support\Email\BundledDisposableDomainList; +use Simtabi\Laranail\Validation\Support\Payment\BundledCardBrandCatalogue; /** * The package is usable with no provider at all — every builder entry point is @@ -109,7 +109,8 @@ public function bootingPackage(): void * rather than coerced: `(string) []` is a fatal, and a rule string cannot * carry an array parameter in the first place. * - * @param array $parameters + * @param array $parameters + * * @return list */ private static function stringParameters(array $parameters): array @@ -210,7 +211,7 @@ private function registerRuleAliases(): void } foreach ($factories as $suffix => $factory) { - $alias = array_key_exists($suffix, RuleAliases::map()) ? $prefix.$suffix : $suffix; + $alias = array_key_exists($suffix, RuleAliases::map()) ? $prefix . $suffix : $suffix; // The key Laravel will look the message up under. It studly-cases // the rule string to dispatch and snake-cases it again to format, @@ -241,7 +242,7 @@ static function (string $attribute, mixed $value, array $parameters, ValidationV if ($messages !== []) { $validator->setCustomMessages([ - $attribute.'.'.$messageKey => implode(' ', self::stringParameters($messages)), + $attribute . '.' . $messageKey => implode(' ', self::stringParameters($messages)), ]); } diff --git a/src/Regex.php b/src/Regex.php index 9354835..584f1e1 100644 --- a/src/Regex.php +++ b/src/Regex.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation; use Closure; -use InvalidArgumentException; -use LogicException; use Stringable; +use LogicException; +use InvalidArgumentException; /** * A regex that is safe by construction — and never required. @@ -99,7 +99,7 @@ public function literal(string $text): self public function oneOf(string ...$alternatives): self { $escaped = array_map(static fn (string $alt): string => preg_quote($alt, '/'), $alternatives); - $this->fragments[] = '(?:'.implode('|', $escaped).')'; + $this->fragments[] = '(?:' . implode('|', $escaped) . ')'; return $this; } @@ -107,7 +107,7 @@ public function oneOf(string ...$alternatives): self /** A group that may be absent. */ public function optional(Closure|string $part): self { - $this->fragments[] = $this->subPattern($part).'?'; + $this->fragments[] = $this->subPattern($part) . '?'; return $this; } @@ -121,7 +121,7 @@ public function optional(Closure|string $part): self public function oneOrMore(Closure|string $part): self { $sub = $this->subPattern($part, forbidUnbounded: ! $this->allowUnbounded); - $this->fragments[] = $sub.'+'; + $this->fragments[] = $sub . '+'; $this->hasUnbounded = true; return $this; @@ -139,7 +139,7 @@ public function group(Closure|string $part): self public function or(Closure|string $part): self { $left = implode('', $this->fragments); - $this->fragments = ['(?:'.$left.'|'.$this->innerPattern($part).')']; + $this->fragments = ['(?:' . $left . '|' . $this->innerPattern($part) . ')']; return $this; } @@ -198,11 +198,11 @@ public function compile(): string $body = implode('', $this->fragments); if ($this->anchored) { - $body = '^(?:'.$body.')$'; + $body = '^(?:' . $body . ')$'; } - $flags = 'D'.($this->caseInsensitive ? 'i' : ''); - $pattern = self::wrap($body).$flags; + $flags = 'D' . ($this->caseInsensitive ? 'i' : ''); + $pattern = self::wrap($body) . $flags; self::assertCompiles($pattern); @@ -214,7 +214,7 @@ private static function wrap(string $body): string { foreach (self::DELIMITERS as $delimiter) { if (! str_contains($body, $delimiter)) { - return $delimiter.$body.$delimiter; + return $delimiter . $body . $delimiter; } } @@ -228,7 +228,7 @@ private static function normalizeRaw(string $pattern): string { $delimited = preg_match('/^([\/#~%])(.*)\1([a-zA-Z]*)$/s', $pattern) === 1; - $normalized = $delimited ? $pattern : self::wrap($pattern).'D'; + $normalized = $delimited ? $pattern : self::wrap($pattern) . 'D'; self::assertCompiles($normalized); @@ -244,7 +244,7 @@ private static function assertCompiles(string $pattern): void { if (@preg_match($pattern, '') === false) { throw new InvalidArgumentException( - 'The pattern does not compile: '.$pattern.' ('.preg_last_error_msg().')', + 'The pattern does not compile: ' . $pattern . ' (' . preg_last_error_msg() . ')', ); } } @@ -255,7 +255,7 @@ private function quantified(string $atom, ?int $count): self throw new InvalidArgumentException('A count must be at least 1.'); } - $this->fragments[] = $atom.($count === null ? '+' : ($count === 1 ? '' : '{'.$count.'}')); + $this->fragments[] = $atom . ($count === null ? '+' : ($count === 1 ? '' : '{' . $count . '}')); $this->hasUnbounded = $this->hasUnbounded || $count === null; return $this; @@ -269,11 +269,11 @@ private function subPattern(Closure|string $part, bool $forbidUnbounded = false) if ($forbidUnbounded && $this->looksUnbounded($inner)) { throw new LogicException( 'An unbounded quantifier inside an unbounded group is the catastrophic-backtracking shape. ' - .'Bound the inner part, or opt in explicitly with dangerouslyUnbounded().', + . 'Bound the inner part, or opt in explicitly with dangerouslyUnbounded().', ); } - return '(?:'.$inner.')'; + return '(?:' . $inner . ')'; } private function innerPattern(Closure|string $part): string diff --git a/src/RuleSet.php b/src/RuleSet.php index d6dc0f0..94e5a73 100644 --- a/src/RuleSet.php +++ b/src/RuleSet.php @@ -5,33 +5,33 @@ namespace Simtabi\Laranail\Validation; use Closure; -use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Http\Request; +use Traversable; +use LogicException; +use IteratorAggregate; +use ReflectionProperty; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Validator; +use Illuminate\Http\Request; +use InvalidArgumentException; use Illuminate\Support\MessageBag; -use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Facades\Validator; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Support\Traits\Conditionable; use Illuminate\Validation\ValidationException; -use InvalidArgumentException; -use IteratorAggregate; -use LogicException; -use ReflectionProperty; +use Simtabi\Laranail\ValidationJs\RuleExporter; +use Illuminate\Contracts\Validation\ValidationRule; +use Simtabi\Laranail\Validation\Internal\ItemValidator; use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; use Simtabi\Laranail\Validation\Events\RuleSetCompiling; -use Simtabi\Laranail\Validation\Events\ValidationCompleted; use Simtabi\Laranail\Validation\Events\ValidationFailed; -use Simtabi\Laranail\Validation\Events\ValidationStarting; -use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; use Simtabi\Laranail\Validation\Internal\BatchLimitRemap; -use Simtabi\Laranail\Validation\Internal\ItemErrorCollector; +use Simtabi\Laranail\Validation\Events\ValidationStarting; use Simtabi\Laranail\Validation\Internal\ItemRuleCompiler; -use Simtabi\Laranail\Validation\Internal\ItemValidator; +use Simtabi\Laranail\Validation\Events\ValidationCompleted; use Simtabi\Laranail\Validation\Internal\VanillaAfterRoute; -use Simtabi\Laranail\ValidationJs\RuleExporter; -use Traversable; +use Simtabi\Laranail\Validation\Internal\ItemErrorCollector; +use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; /** * @implements Arrayable @@ -93,7 +93,7 @@ public static function from(array $rules): self * 'email' => $rules->email()->required(), * ])->validate($data); * - * @param Closure(FluentSchema): array $callback + * @param Closure(FluentSchema): array $callback */ public static function define(Closure $callback): self { @@ -101,7 +101,8 @@ public static function define(Closure $callback): self } /** - * @param array $rules + * @param array $rules + * * @return array */ public static function compile(array $rules): array @@ -120,7 +121,8 @@ public static function compile(array $rules): array * Useful when passing rules to APIs that expect array> * (e.g., Livewire's $this->validate()). * - * @param array $rules + * @param array $rules + * * @return array> */ /** @@ -131,7 +133,8 @@ public static function compile(array $rules): array * [$rules, $messages, $attributes] = RuleSet::compileWithMetadata($this->rules()); * $this->validate($rules, $messages, $attributes); * - * @param array $rules + * @param array $rules + * * @return array{0: array, 1: array, 2: array} */ public static function compileWithMetadata(array $rules): array @@ -144,7 +147,8 @@ public static function compileWithMetadata(array $rules): array } /** - * @param array $rules + * @param array $rules + * * @return array> */ public static function compileToArrays(array $rules): array @@ -170,7 +174,8 @@ public static function compileToArrays(array $rules): array /** * Extract labels and per-rule messages from rule objects before compilation. * - * @param array $rules + * @param array $rules + * * @return array{0: array, 1: array} */ public static function extractMetadata(array $rules): array @@ -210,10 +215,10 @@ public function merge(self|array $rules): self } /** - * @param string|list ...$fields Pass either as variadic strings - * (`->only('a', 'b')`) or as a single - * array (`->only(['a', 'b'])`) — matches - * Collection::only / Arr::only semantics. + * @param string|list ...$fields Pass either as variadic strings + * (`->only('a', 'b')`) or as a single + * array (`->only(['a', 'b'])`) — matches + * Collection::only / Arr::only semantics. */ public function only(string|array ...$fields): self { @@ -273,7 +278,7 @@ public function hasObjectRules(): bool * add new fields. The throw differentiates `modify` from `put` semantically: * silently creating missing keys would conflate the two. * - * @param Closure(mixed): mixed $callback Receives the clone, returns the replacement rule. + * @param Closure(mixed): mixed $callback Receives the clone, returns the replacement rule. * * @throws LogicException When `$field` is not in the rule set. */ @@ -310,7 +315,7 @@ public function modify(string $field, Closure $callback): self * If strict add-only behaviour is required (collision throws), use the * primitive `modify($field, fn ($r) => $r->addEachRule($key, $rule))`. * - * @param array $rules + * @param array $rules * * @throws LogicException When `$field` is not in the rule set, when the * stored rule is not an `ArrayRule`, or when the @@ -340,7 +345,7 @@ public function modifyEach(string $field, array $rules): self * consumer demand surfaced). Falls through to `modify()` with the * primitive if you need to extend an `ArrayRule`'s `children()`. * - * @param array $rules + * @param array $rules * * @throws LogicException When `$field` is not in the rule set or the * stored rule is not a `FieldRule`. @@ -406,7 +411,7 @@ public function stopOnFirstFailure(): self * Callbacks run in registration order, each seeing the previous one's * output — trimming, casting, or normalising input belongs here. * - * @param Closure(array): (array|null) $callback + * @param Closure(array): (array|null) $callback */ public function before(Closure $callback): self { @@ -428,7 +433,7 @@ public function before(Closure $callback): self * shortcuts. Hooks trade speed for the full Laravel feature; a rule set * without them keeps the fast engine. * - * @param Closure(\Illuminate\Validation\Validator): void $callback + * @param Closure(\Illuminate\Validation\Validator): void $callback */ public function after(Closure $callback): self { @@ -504,8 +509,8 @@ public function dump(): array [$messages, $attributes] = self::extractMetadata($flat); return [ - 'rules' => self::compileToArrays($flat), - 'messages' => $messages, + 'rules' => self::compileToArrays($flat), + 'messages' => $messages, 'attributes' => $attributes, ]; } @@ -519,8 +524,8 @@ public function dump(): array * $p = RuleSet::from($rules)->prepare($data); * parent::__construct($translator, $data, $p->rules, $p->messages, $p->attributes); * - * @param array $data - * @param array|null $flatRules Pre-computed flatten() result + * @param array $data + * @param array|null $flatRules Pre-computed flatten() result */ public function prepare(array $data, ?array $flatRules = null): PreparedRules { @@ -553,9 +558,9 @@ public function prepare(array $data, ?array $flatRules = null): PreparedRules * calls `$request->all()` internally, keeping the unsafe read scoped * to the library boundary for static-analysis purposes. * - * @param array|Request $data - * @param array $messages - * @param array $attributes + * @param array|Request $data + * @param array $messages + * @param array $attributes */ public function check(array|Request $data, array $messages = [], array $attributes = []): Validated { @@ -585,7 +590,8 @@ public function check(array|Request $data, array $messages = [], array $attribut } /** - * @param array $data + * @param array $data + * * @return array */ public function expandWildcards(array $data): array @@ -605,8 +611,9 @@ public function expandWildcards(array $data): array * fast at the call site with the install command rather than returning * an empty schema a browser would silently treat as "nothing to check". * - * @param array $messages - * @param array $attributes + * @param array $messages + * @param array $attributes + * * @return array{version: int, fields: array}>, server: list}>, messages: array, messageVariants: array>} * * @throws LogicException When laranail/validation-js is not installed. @@ -616,7 +623,7 @@ public function toSchema(array $messages = [], array $attributes = []): array if (! class_exists(RuleExporter::class)) { throw new LogicException( 'toSchema() exports the wire schema through laranail/validation-js, which is not installed. ' - .'Install it with `composer require laranail/validation-js`.', + . 'Install it with `composer require laranail/validation-js`.', ); } @@ -637,9 +644,10 @@ public function toSchema(array $messages = [], array $attributes = []): array * calls `$request->all()` internally, keeping the unsafe read scoped * to the library boundary for static-analysis purposes. * - * @param array|Request $data - * @param array $messages - * @param array $attributes + * @param array|Request $data + * @param array $messages + * @param array $attributes + * * @return array * * @throws ValidationException @@ -674,8 +682,9 @@ public function validate(array|Request $data, array $messages = [], array $attri * @internal Used by {@see prepare()}, {@see validate()}, and the * compile pipeline. Not covered by the package's BC promise. * - * @param array $data - * @param array|null $flatRules Pre-computed flatten() result + * @param array $data + * @param array|null $flatRules Pre-computed flatten() result + * * @return array{0: array, 1: array>} */ public function expand(array $data, ?array $flatRules = null): array @@ -717,8 +726,8 @@ public function flattenRules(): array } /** - * @param array $messages - * @param array $attributes + * @param array $messages + * @param array $attributes */ private static function extractObjectMetadata(object $object, string $field, array &$messages, array &$attributes): void { @@ -734,7 +743,7 @@ private static function extractObjectMetadata(object $object, string $field, arr /** @var array $customMessages */ $customMessages = $object->getCustomMessages(); foreach ($customMessages as $ruleName => $msg) { - $messages[$ruleName === '' ? $field : $field.'.'.$ruleName] = $msg; + $messages[$ruleName === '' ? $field : $field . '.' . $ruleName] = $msg; } } } @@ -760,16 +769,16 @@ private static function flattenRule(string $prefix, mixed $rule, array &$rules): // each() → wildcard paths: items.*.name if ($eachListRule instanceof ValidationRule) { - self::flattenRule($prefix.'.*', $eachListRule, $rules); + self::flattenRule($prefix . '.*', $eachListRule, $rules); } elseif ($eachKeyedRules !== null) { foreach ($eachKeyedRules as $field => $fieldRule) { - self::flattenRule($prefix.'.*.'.$field, $fieldRule, $rules); + self::flattenRule($prefix . '.*.' . $field, $fieldRule, $rules); } } // children() → fixed paths: search.value, answer.email_address foreach ($childRules ?? [] as $field => $fieldRule) { - self::flattenRule($prefix.'.'.$field, $fieldRule, $rules); + self::flattenRule($prefix . '.' . $field, $fieldRule, $rules); } } @@ -779,7 +788,8 @@ private static function flattenRule(string $prefix, mixed $rule, array &$rules): * package boundary, so callers can use `RuleSet::validate($request)` * without tripping static-analysis rules against `$request->all()`. * - * @param array|Request $data + * @param array|Request $data + * * @return array */ private function normalizeInput(array|Request $data): array @@ -797,9 +807,10 @@ private function normalizeInput(array|Request $data): array } /** - * @param array $data - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $messages + * @param array $attributes + * * @return array * * @throws ValidationException @@ -861,9 +872,10 @@ private function runValidateInternal(array $data, array $messages, array $attrib } /** - * @param array $data - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $messages + * @param array $attributes + * * @return array * * @throws ValidationException @@ -885,9 +897,10 @@ private function validateWithAfterCallbacks(array $data, array $messages, array } /** - * @param array $data - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $messages + * @param array $attributes + * * @return array * * @throws ValidationException @@ -952,7 +965,8 @@ private function validateInternal(array $data, array $messages, array $attribute /** * Split flattened rules into top-level rules and wildcard groups. * - * @param array|null $flatRules Pre-computed flatten() result to avoid re-processing + * @param array|null $flatRules Pre-computed flatten() result to avoid re-processing + * * @return array{0: array, 1: array>} */ private function separateRules(?array $flatRules = null): array @@ -978,7 +992,7 @@ private function separateRules(?array $flatRules = null): array if (! str_contains($field, '.*')) { throw new InvalidArgumentException( "Malformed wildcard rule key [{$field}]: a wildcard segment must be written as '.*' " - ."(e.g. 'items.*.name'). Did you mean '".str_replace('*', '.*', $field)."'?", + . "(e.g. 'items.*.name'). Did you mean '" . str_replace('*', '.*', $field) . "'?", ); } @@ -995,12 +1009,13 @@ private function separateRules(?array $flatRules = null): array /** * Validate all wildcard groups per-item with fast-check optimization. * - * @param array> $wildcardGroups - * @param array> $wildcardGroups - * @param array $data - * @param array $messages - * @param array $attributes - * @param array|null $fallbackResult Set when full expansion fallback is used + * @param array> $wildcardGroups + * @param array> $wildcardGroups + * @param array $data + * @param array $messages + * @param array $attributes + * @param array|null $fallbackResult Set when full expansion fallback is used + * * @return array> * * @throws ValidationException @@ -1066,10 +1081,11 @@ private function validateWildcardGroups( /** * Validate individual items in a wildcard group. * - * @param array $items - * @param array $itemRules - * @param array $itemMessages - * @param array $itemAttributes + * @param array $items + * @param array $itemRules + * @param array $itemMessages + * @param array $itemAttributes + * * @return array> */ private function validateItems(array $items, array $itemRules, array $itemMessages, array $itemAttributes, string $parent, bool $isScalar, bool $stopOnFirstFailure = false): array @@ -1081,11 +1097,11 @@ private function validateItems(array $items, array $itemRules, array $itemMessag /** * Reject input keys not covered by any rule in the set. * - * @param array $data - * @param array $topRules - * @param array> $wildcardGroups - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $topRules + * @param array> $wildcardGroups + * @param array $messages + * @param array $attributes * * @throws ValidationException */ @@ -1098,8 +1114,8 @@ private function rejectUnknownFields(array $data, array $topRules, array $wildca foreach (array_keys($children) as $child) { $allowedKeys[] = $child === '*' - ? $parent.'.*' - : $parent.'.*.'.$child; + ? $parent . '.*' + : $parent . '.*.' . $child; } } @@ -1119,7 +1135,7 @@ private function rejectUnknownFields(array $data, array $topRules, array $wildca /** * Check if an input key matches any allowed rule key, including wildcard patterns. * - * @param list $allowedKeys + * @param list $allowedKeys */ private function isKnownField(string $inputKey, array $allowedKeys): bool { @@ -1129,7 +1145,7 @@ private function isKnownField(string $inputKey, array $allowedKeys): bool } if (str_contains($ruleKey, '*')) { - $pattern = '/^'.str_replace('\*', '[^.]+', preg_quote($ruleKey, '/')).'$/'; + $pattern = '/^' . str_replace('\*', '[^.]+', preg_quote($ruleKey, '/')) . '$/'; if (preg_match($pattern, $inputKey) === 1) { return true; @@ -1159,12 +1175,13 @@ private function throwValidationErrors(array $errors): never * ['exclude_unless', 'items.*.type', 'chapter'] → ['exclude_unless', 'type', 'chapter'] * 'gte:items.*.start_time' → 'gte:start_time' * - * @param array $groupRules + * @param array $groupRules + * * @return array */ private function rewriteRulesForPerItem(array $groupRules, string $parent): array { - $prefix = $parent.'.*.'; + $prefix = $parent . '.*.'; $rewritten = []; foreach ($groupRules as $field => $rule) { @@ -1201,7 +1218,8 @@ private function rewriteRulesForPerItem(array $groupRules, string $parent): arra * rule's constraint list so per-item validation resolves the dep against * the relative item. * - * @param array $rule + * @param array $rule + * * @return list */ private function stripPrefixFromConstraints(array $rule, string $prefix): array @@ -1246,9 +1264,10 @@ private function requiresFullExpansion(array $compiledRules): bool } /** - * @param array $data - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $messages + * @param array $attributes + * * @return array * * @throws ValidationException diff --git a/src/Rules/AntiSpam/SubmissionTiming.php b/src/Rules/AntiSpam/SubmissionTiming.php index 0b13b64..d1731ac 100644 --- a/src/Rules/AntiSpam/SubmissionTiming.php +++ b/src/Rules/AntiSpam/SubmissionTiming.php @@ -5,10 +5,10 @@ namespace Simtabi\Laranail\Validation\Rules\AntiSpam; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Facades\Crypt; -use Illuminate\Support\Facades\Date; use Throwable; +use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Contracts\Validation\ValidationRule; /** * The form was not submitted impossibly fast. diff --git a/src/Rules/Banking/Iban.php b/src/Rules/Banking/Iban.php index a3adbf7..b86b2c5 100644 --- a/src/Rules/Banking/Iban.php +++ b/src/Rules/Banking/Iban.php @@ -88,7 +88,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void */ private static function checksumIsValid(string $iban): bool { - $rearranged = substr($iban, 4).substr($iban, 0, 4); + $rearranged = substr($iban, 4) . substr($iban, 0, 4); $numeric = ''; foreach (str_split($rearranged) as $character) { @@ -104,7 +104,7 @@ private static function checksumIsValid(string $iban): bool foreach (str_split($numeric, 7) as $chunk) { // At most two carried digits plus a seven-digit chunk, so the // concatenation always fits an int and the cast is exact. - $remainder = ((int) ($remainder.$chunk)) % 97; + $remainder = ((int) ($remainder . $chunk)) % 97; } return $remainder === 1; diff --git a/src/Rules/Chrono/DateInterval.php b/src/Rules/Chrono/DateInterval.php index 7f19ce1..98c1116 100644 --- a/src/Rules/Chrono/DateInterval.php +++ b/src/Rules/Chrono/DateInterval.php @@ -28,7 +28,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void { if (! is_string($value) || ! $this->passes($value)) { $key = $this->positive ? 'date_interval_positive' : 'date_interval'; - $fail('laranail/validation::validation.'.$key)->translate(); + $fail('laranail/validation::validation.' . $key)->translate(); } } diff --git a/src/Rules/Chrono/MaxDateDifference.php b/src/Rules/Chrono/MaxDateDifference.php index 5faa6ac..f517254 100644 --- a/src/Rules/Chrono/MaxDateDifference.php +++ b/src/Rules/Chrono/MaxDateDifference.php @@ -5,12 +5,12 @@ namespace Simtabi\Laranail\Validation\Rules\Chrono; use Closure; +use Exception; use DateTimeImmutable; use DateTimeInterface; -use Exception; +use Illuminate\Support\Arr; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Arr; /** * The value lies within `$hours` of a reference instant — either way: a diff --git a/src/Rules/Chrono/MinimumAge.php b/src/Rules/Chrono/MinimumAge.php index 6b22d1f..1c69d35 100644 --- a/src/Rules/Chrono/MinimumAge.php +++ b/src/Rules/Chrono/MinimumAge.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\Chrono; use Closure; -use DateTimeImmutable; -use DateTimeZone; use Exception; +use DateTimeZone; +use DateTimeImmutable; use Illuminate\Contracts\Validation\ValidationRule; /** diff --git a/src/Rules/Chrono/MinuteIn.php b/src/Rules/Chrono/MinuteIn.php index 7999418..e18c99e 100644 --- a/src/Rules/Chrono/MinuteIn.php +++ b/src/Rules/Chrono/MinuteIn.php @@ -5,10 +5,10 @@ namespace Simtabi\Laranail\Validation\Rules\Chrono; use Closure; -use DateTimeImmutable; use Exception; -use Illuminate\Contracts\Validation\ValidationRule; use LogicException; +use DateTimeImmutable; +use Illuminate\Contracts\Validation\ValidationRule; /** * A time whose minute component is one of the allowed values — @@ -26,7 +26,7 @@ final readonly class MinuteIn implements ValidationRule { /** - * @param list $minutes Allowed minute values, each 0–59. + * @param list $minutes Allowed minute values, each 0–59. */ public function __construct(private array $minutes) { diff --git a/src/Rules/Chrono/Rfc3339.php b/src/Rules/Chrono/Rfc3339.php index 8605f82..e841123 100644 --- a/src/Rules/Chrono/Rfc3339.php +++ b/src/Rules/Chrono/Rfc3339.php @@ -24,8 +24,8 @@ final class Rfc3339 implements ValidationRule { private const string GRAMMAR = '/^(?\d{4})-(?\d{2})-(?\d{2})[Tt]' - .'(?[01]\d|2[0-3]):(?[0-5]\d):(?[0-5]\d|60)(?:\.\d+)?' - .'(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/D'; + . '(?[01]\d|2[0-3]):(?[0-5]\d):(?[0-5]\d|60)(?:\.\d+)?' + . '(?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/D'; public static function passes(string $value): bool { diff --git a/src/Rules/Chrono/TimeOfDay.php b/src/Rules/Chrono/TimeOfDay.php index 7742f85..71ba8d9 100644 --- a/src/Rules/Chrono/TimeOfDay.php +++ b/src/Rules/Chrono/TimeOfDay.php @@ -41,9 +41,9 @@ private function pattern(): string $sep = preg_quote($this->separator, '/'); if ($this->twelveHour) { - return '/^(0?[1-9]|1[0-2])'.$sep.'[0-5]\d(?:'.$sep.'[0-5]\d)? ?[AaPp][Mm]$/D'; + return '/^(0?[1-9]|1[0-2])' . $sep . '[0-5]\d(?:' . $sep . '[0-5]\d)? ?[AaPp][Mm]$/D'; } - return '/^([01]?\d|2[0-3])'.$sep.'[0-5]\d(?:'.$sep.'[0-5]\d)?$/D'; + return '/^([01]?\d|2[0-3])' . $sep . '[0-5]\d(?:' . $sep . '[0-5]\d)?$/D'; } } diff --git a/src/Rules/Codes/Gtin.php b/src/Rules/Codes/Gtin.php index 5d02716..8aba04a 100644 --- a/src/Rules/Codes/Gtin.php +++ b/src/Rules/Codes/Gtin.php @@ -32,7 +32,7 @@ private array $lengths; /** - * @param list $lengths Widths to accept; defaults to all four. + * @param list $lengths Widths to accept; defaults to all four. */ public function __construct(array $lengths = self::VALID_LENGTHS) { @@ -40,7 +40,7 @@ public function __construct(array $lengths = self::VALID_LENGTHS) } /** - * @param list $lengths + * @param list $lengths */ public static function passes(mixed $value, array $lengths = self::VALID_LENGTHS): bool { diff --git a/src/Rules/Codes/Isbn.php b/src/Rules/Codes/Isbn.php index 4704a68..5697196 100644 --- a/src/Rules/Codes/Isbn.php +++ b/src/Rules/Codes/Isbn.php @@ -32,7 +32,7 @@ private array $editions; /** - * @param list $editions 10, 13, or both (the default). + * @param list $editions 10, 13, or both (the default). */ public function __construct(array $editions = [self::EDITION_10, self::EDITION_13]) { @@ -40,7 +40,7 @@ public function __construct(array $editions = [self::EDITION_10, self::EDITION_1 } /** - * @param list $editions + * @param list $editions */ public static function passes(mixed $value, array $editions = [self::EDITION_10, self::EDITION_13]): bool { @@ -51,8 +51,8 @@ public static function passes(mixed $value, array $editions = [self::EDITION_10, $isbn = strtoupper(str_replace([' ', '-'], '', (string) $value)); return match (strlen($isbn)) { - 10 => in_array(self::EDITION_10, $editions, true) && self::isbn10IsValid($isbn), - 13 => in_array(self::EDITION_13, $editions, true) && self::isbn13IsValid($isbn), + 10 => in_array(self::EDITION_10, $editions, true) && self::isbn10IsValid($isbn), + 13 => in_array(self::EDITION_13, $editions, true) && self::isbn13IsValid($isbn), default => false, }; } diff --git a/src/Rules/Codes/UpcE.php b/src/Rules/Codes/UpcE.php index 3a1609b..b7b8ceb 100644 --- a/src/Rules/Codes/UpcE.php +++ b/src/Rules/Codes/UpcE.php @@ -36,13 +36,13 @@ public static function passes(string $value): bool // GS1's zero-suppression patterns, keyed by the sixth data digit. $body = match (true) { - in_array($selector, ['0', '1', '2'], true) => $numberSystem.substr($data, 0, 2).$selector.'0000'.substr($data, 2, 3), - $selector === '3' => $numberSystem.substr($data, 0, 3).'00000'.substr($data, 3, 2), - $selector === '4' => $numberSystem.substr($data, 0, 4).'00000'.$data[4], - default => $numberSystem.substr($data, 0, 5).'0000'.$selector, + in_array($selector, ['0', '1', '2'], true) => $numberSystem . substr($data, 0, 2) . $selector . '0000' . substr($data, 2, 3), + $selector === '3' => $numberSystem . substr($data, 0, 3) . '00000' . substr($data, 3, 2), + $selector === '4' => $numberSystem . substr($data, 0, 4) . '00000' . $data[4], + default => $numberSystem . substr($data, 0, 5) . '0000' . $selector, }; - return Gtin::passes($body.$value[7], [12]); + return Gtin::passes($body . $value[7], [12]); } public function validate(string $attribute, mixed $value, Closure $fail): void diff --git a/src/Rules/Colour/CssColor.php b/src/Rules/Colour/CssColor.php index 1f2258a..cfb66ec 100644 --- a/src/Rules/Colour/CssColor.php +++ b/src/Rules/Colour/CssColor.php @@ -118,7 +118,7 @@ public function clientRules(): array $branches[] = $branch; } - return [['rule' => 'regex', 'params' => ['pattern' => '/^(?:'.implode('|', $branches).')$/iD']]]; + return [['rule' => 'regex', 'params' => ['pattern' => '/^(?:' . implode('|', $branches) . ')$/iD']]]; } private static function matches(string $value, string $notation): bool @@ -127,12 +127,12 @@ private static function matches(string $value, string $notation): bool // 3, 4, 6 and 8 digits: the 4- and 8-digit forms carry alpha. // Anything else — 5 digits, 7 digits — is not a colour, and a // pattern of {3,8} would accept them. - self::HEX => preg_match('/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/iD', $value) === 1, - self::RGB => self::functional($value, 'rgba?', 3), - self::HSL => self::functional($value, 'hsla?', 3), - self::HSV => self::functional($value, 'hsva?', 3), + self::HEX => preg_match('/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/iD', $value) === 1, + self::RGB => self::functional($value, 'rgba?', 3), + self::HSL => self::functional($value, 'hsla?', 3), + self::HSV => self::functional($value, 'hsva?', 3), self::NAME => Names::has($value), - default => false, + default => false, }; } @@ -151,10 +151,10 @@ private static function functional(string $value, string $function, int $compone $component = '[+-]?(?:\d+\.?\d*|\.\d+)(?:%|deg|grad|rad|turn)?'; $separator = '(?:\s*,\s*|\s+)'; - $body = $component.str_repeat($separator.$component, $components - 1); - $alpha = '(?:\s*(?:,|\/)\s*'.$component.')?'; + $body = $component . str_repeat($separator . $component, $components - 1); + $alpha = '(?:\s*(?:,|\/)\s*' . $component . ')?'; - return preg_match('/^'.$function.'\(\s*'.$body.$alpha.'\s*\)$/iD', $value) === 1; + return preg_match('/^' . $function . '\(\s*' . $body . $alpha . '\s*\)$/iD', $value) === 1; } /** The unanchored body for one notation, or null if there is no such notation. */ @@ -162,15 +162,15 @@ private function branch(string $notation): ?string { $component = '[+-]?(?:\d+\.?\d*|\.\d+)(?:%|deg|grad|rad|turn)?'; $separator = '(?:\s*,\s*|\s+)'; - $body = static fn (string $function): string => $function.'\(\s*'.$component - .str_repeat($separator.$component, 2) - .'(?:\s*(?:,|\/)\s*'.$component.')?\s*\)'; + $body = static fn (string $function): string => $function . '\(\s*' . $component + . str_repeat($separator . $component, 2) + . '(?:\s*(?:,|\/)\s*' . $component . ')?\s*\)'; return match ($notation) { - self::HEX => '#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})', - self::RGB => $body('rgba?'), - self::HSL => $body('hsla?'), - self::HSV => $body('hsva?'), + self::HEX => '#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})', + self::RGB => $body('rgba?'), + self::HSL => $body('hsla?'), + self::HSV => $body('hsva?'), self::NAME => implode('|', array_map( static fn (string $name): string => preg_quote($name, '/'), Names::all(), diff --git a/src/Rules/Crypto/BitcoinAddress.php b/src/Rules/Crypto/BitcoinAddress.php index 39a4144..641c0ce 100644 --- a/src/Rules/Crypto/BitcoinAddress.php +++ b/src/Rules/Crypto/BitcoinAddress.php @@ -6,8 +6,8 @@ use Closure; use Illuminate\Contracts\Validation\ValidationRule; -use Simtabi\Laranail\Validation\Rules\Crypto\Support\Base58Check; use Simtabi\Laranail\Validation\Rules\Crypto\Support\Bech32; +use Simtabi\Laranail\Validation\Rules\Crypto\Support\Base58Check; /** * A Bitcoin address, checksum-verified rather than pattern-matched. diff --git a/src/Rules/Database/Authorized.php b/src/Rules/Database/Authorized.php index f1b7cc8..389d0cd 100644 --- a/src/Rules/Database/Authorized.php +++ b/src/Rules/Database/Authorized.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\Database; use Closure; +use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Auth\Access\Gate; use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Database\Eloquent\Model; /** * The submitted identifier names a record the current user may act on. @@ -32,10 +32,10 @@ final readonly class Authorized implements ValidationRule { /** - * @param string $ability Ability name, as the policy declares it. - * @param class-string $model Model to resolve the value against. - * @param string|null $guard Auth guard, or null for the default. - * @param list $arguments Extra arguments passed after the model. + * @param string $ability Ability name, as the policy declares it. + * @param class-string $model Model to resolve the value against. + * @param string|null $guard Auth guard, or null for the default. + * @param list $arguments Extra arguments passed after the model. */ public function __construct( private string $ability, diff --git a/src/Rules/Database/CompareToColumn.php b/src/Rules/Database/CompareToColumn.php index e5e7780..57a82ed 100644 --- a/src/Rules/Database/CompareToColumn.php +++ b/src/Rules/Database/CompareToColumn.php @@ -5,10 +5,10 @@ namespace Simtabi\Laranail\Validation\Rules\Database; use Closure; -use Illuminate\Contracts\Validation\DataAwareRule; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; +use Illuminate\Contracts\Validation\DataAwareRule; +use Illuminate\Contracts\Validation\ValidationRule; /** * The value compared against a column looked up in another row — "quantity diff --git a/src/Rules/Database/Comparison.php b/src/Rules/Database/Comparison.php index 874d7eb..a4c6a2e 100644 --- a/src/Rules/Database/Comparison.php +++ b/src/Rules/Database/Comparison.php @@ -31,12 +31,12 @@ public function compare(int|float|string $left, int|float|string $right): bool } return match ($this) { - self::LessThan => $left < $right, - self::LessThanOrEqual => $left <= $right, - self::GreaterThan => $left > $right, + self::LessThan => $left < $right, + self::LessThanOrEqual => $left <= $right, + self::GreaterThan => $left > $right, self::GreaterThanOrEqual => $left >= $right, - self::Equal => ($left <=> $right) === 0, - self::NotEqual => ($left <=> $right) !== 0, + self::Equal => ($left <=> $right) === 0, + self::NotEqual => ($left <=> $right) !== 0, }; } } diff --git a/src/Rules/Database/ModelsExist.php b/src/Rules/Database/ModelsExist.php index a934cbf..b72d47f 100644 --- a/src/Rules/Database/ModelsExist.php +++ b/src/Rules/Database/ModelsExist.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\Database; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Contracts\Validation\ValidationRule; /** * Every value in the submitted array names an existing record. @@ -32,8 +32,8 @@ final readonly class ModelsExist implements ValidationRule { /** - * @param class-string $model Model to look the values up against. - * @param string|null $column Defaults to the model's route key. + * @param class-string $model Model to look the values up against. + * @param string|null $column Defaults to the model's route key. */ public function __construct( private string $model, diff --git a/src/Rules/Email/EmailDomainIs.php b/src/Rules/Email/EmailDomainIs.php index b3e7150..3d25c5a 100644 --- a/src/Rules/Email/EmailDomainIs.php +++ b/src/Rules/Email/EmailDomainIs.php @@ -41,7 +41,7 @@ public function __construct(private array $domains) {} * allow-list matcher is two chances to leave a gap, and a gap in one would * be a bypass in the other. * - * @param list $patterns + * @param list $patterns */ public static function matches(string $domain, array $patterns): bool { diff --git a/src/Rules/Email/NotDisposableEmail.php b/src/Rules/Email/NotDisposableEmail.php index a55e070..1fd869a 100644 --- a/src/Rules/Email/NotDisposableEmail.php +++ b/src/Rules/Email/NotDisposableEmail.php @@ -6,8 +6,8 @@ use Closure; use Illuminate\Contracts\Validation\ValidationRule; -use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; use Simtabi\Laranail\Validation\Rules\Email\Support\Address; +use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; /** * The address is not at a known throwaway-mail provider. diff --git a/src/Rules/Email/NotRoleEmail.php b/src/Rules/Email/NotRoleEmail.php index 34902e8..5de175c 100644 --- a/src/Rules/Email/NotRoleEmail.php +++ b/src/Rules/Email/NotRoleEmail.php @@ -6,8 +6,8 @@ use Closure; use Illuminate\Contracts\Validation\ValidationRule; -use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; use Simtabi\Laranail\Validation\Rules\Email\Support\Address; +use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; /** * The address belongs to a person rather than a function. diff --git a/src/Rules/Encoding/Base64Image.php b/src/Rules/Encoding/Base64Image.php index 7a79459..62cc327 100644 --- a/src/Rules/Encoding/Base64Image.php +++ b/src/Rules/Encoding/Base64Image.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Encoding; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Number; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Support\Encoding\Base64File; /** @@ -31,7 +31,7 @@ final readonly class Base64Image implements ValidationRule { /** - * @param list $mimes Accepted `image/*` subtypes. + * @param list $mimes Accepted `image/*` subtypes. */ public function __construct( private array $mimes = ['jpeg', 'png', 'gif', 'webp', 'bmp'], diff --git a/src/Rules/Encoding/DataUri.php b/src/Rules/Encoding/DataUri.php index b83d59c..6292cb9 100644 --- a/src/Rules/Encoding/DataUri.php +++ b/src/Rules/Encoding/DataUri.php @@ -34,7 +34,7 @@ private const string URL_ENCODED = "/^(?:[A-Za-z0-9\\-_.~!$&'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*$/D"; /** - * @param list $mediaTypes Accepted declared types, exact or `family/*`. + * @param list $mediaTypes Accepted declared types, exact or `family/*`. */ public function __construct(private array $mediaTypes = []) {} diff --git a/src/Rules/Fiscal/NationalIdentifier.php b/src/Rules/Fiscal/NationalIdentifier.php index 8307914..eeac3a8 100644 --- a/src/Rules/Fiscal/NationalIdentifier.php +++ b/src/Rules/Fiscal/NationalIdentifier.php @@ -64,7 +64,7 @@ public static function passes(mixed $value, string $country): bool self::GB => self::britishNino($value), self::FR => self::frenchNir($value), self::VN => self::vietnameseCccd($value), - default => false, + default => false, }; } @@ -86,7 +86,7 @@ private static function dutchBsn(string $value): bool $digits = self::digits($value); if (strlen($digits) === 8) { - $digits = '0'.$digits; + $digits = '0' . $digits; } if (strlen($digits) !== 9 || $digits === '000000000') { diff --git a/src/Rules/Fiscal/VatNumber.php b/src/Rules/Fiscal/VatNumber.php index ab29d21..b13cb01 100644 --- a/src/Rules/Fiscal/VatNumber.php +++ b/src/Rules/Fiscal/VatNumber.php @@ -61,7 +61,7 @@ ]; /** - * @param list|null $countries Accepted prefixes; null accepts every known one. + * @param list|null $countries Accepted prefixes; null accepts every known one. */ public function __construct(private ?array $countries = null) {} @@ -87,19 +87,19 @@ private function passes(string $value): bool return false; } - if (preg_match('/^(?:'.self::PATTERNS[$country].')$/D', $tail) !== 1) { + if (preg_match('/^(?:' . self::PATTERNS[$country] . ')$/D', $tail) !== 1) { return false; } return match ($country) { - 'NL' => $this->dutch($normalised, $tail), - 'BE' => (int) substr($tail, 8, 2) === 97 - ((int) substr($tail, 0, 8) % 97), - 'DE' => $this->germanMod11x10($tail), - 'IT' => $this->italianLuhn($tail), - 'SE' => str_ends_with($tail, '01') && $this->luhnPasses(substr($tail, 0, 10)), - 'EL' => $this->greekPowerSum($tail), - 'LU' => (int) substr($tail, 6, 2) === (int) substr($tail, 0, 6) % 89, - 'FR' => $this->frenchKey($tail), + 'NL' => $this->dutch($normalised, $tail), + 'BE' => (int) substr($tail, 8, 2) === 97 - ((int) substr($tail, 0, 8) % 97), + 'DE' => $this->germanMod11x10($tail), + 'IT' => $this->italianLuhn($tail), + 'SE' => str_ends_with($tail, '01') && $this->luhnPasses(substr($tail, 0, 10)), + 'EL' => $this->greekPowerSum($tail), + 'LU' => (int) substr($tail, 6, 2) === (int) substr($tail, 0, 6) % 89, + 'FR' => $this->frenchKey($tail), default => true, }; } diff --git a/src/Rules/Geo/Subdivisions.php b/src/Rules/Geo/Subdivisions.php index 2f7ccc3..5f45b95 100644 --- a/src/Rules/Geo/Subdivisions.php +++ b/src/Rules/Geo/Subdivisions.php @@ -19,7 +19,7 @@ final class Subdivisions * answer from a user's point of view and differ only in how carefully the * form was filled in. * - * @param array $subdivisions code => name + * @param array $subdivisions code => name */ public static function contains(string $value, array $subdivisions): bool { diff --git a/src/Rules/I18n/CurrencyCode.php b/src/Rules/I18n/CurrencyCode.php index 1153864..fc5b3f0 100644 --- a/src/Rules/I18n/CurrencyCode.php +++ b/src/Rules/I18n/CurrencyCode.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\I18n; use Closure; +use LogicException; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Translation\PotentiallyTranslatedString; -use LogicException; use Simtabi\Laranail\Validation\Contracts\I18n\CurrencyDataset; /** @@ -50,8 +50,8 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $passes = match (true) { $this->numeric => $this->dataset()->isNumericCode($value), - $this->symbol => $this->dataset()->isSymbol($value), - default => $this->dataset()->isCode($this->caseInsensitive ? strtoupper($value) : $value), + $this->symbol => $this->dataset()->isSymbol($value), + default => $this->dataset()->isCode($this->caseInsensitive ? strtoupper($value) : $value), }; if (! $passes) { @@ -64,8 +64,8 @@ private function fail(Closure $fail): void { $key = match (true) { $this->numeric => 'laranail/validation::validation.currency_code_numeric', - $this->symbol => 'laranail/validation::validation.currency_symbol', - default => 'laranail/validation::validation.currency_code', + $this->symbol => 'laranail/validation::validation.currency_symbol', + default => 'laranail/validation::validation.currency_code', }; $fail($key)->translate(); diff --git a/src/Rules/Identifiers/HashDigest.php b/src/Rules/Identifiers/HashDigest.php index 5e934b7..f626a7c 100644 --- a/src/Rules/Identifiers/HashDigest.php +++ b/src/Rules/Identifiers/HashDigest.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Identifiers; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use InvalidArgumentException; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Contracts\ClientCheckable; /** @@ -29,34 +29,34 @@ { /** Hex-digest widths per admitted algorithm. */ private const array LENGTHS = [ - 'adler32' => 8, - 'crc32' => 8, - 'crc32b' => 8, - 'crc32c' => 8, - 'md4' => 32, - 'md5' => 32, - 'ripemd128' => 32, - 'ripemd160' => 40, - 'ripemd256' => 64, - 'ripemd320' => 80, - 'sha1' => 40, - 'sha224' => 56, - 'sha256' => 64, - 'sha384' => 96, - 'sha512' => 128, + 'adler32' => 8, + 'crc32' => 8, + 'crc32b' => 8, + 'crc32c' => 8, + 'md4' => 32, + 'md5' => 32, + 'ripemd128' => 32, + 'ripemd160' => 40, + 'ripemd256' => 64, + 'ripemd320' => 80, + 'sha1' => 40, + 'sha224' => 56, + 'sha256' => 64, + 'sha384' => 96, + 'sha512' => 128, 'sha512/224' => 56, 'sha512/256' => 64, - 'sha3-224' => 56, - 'sha3-256' => 64, - 'sha3-384' => 96, - 'sha3-512' => 128, - 'tiger128' => 32, - 'tiger160' => 40, - 'tiger192' => 48, - 'whirlpool' => 128, - 'xxh32' => 8, - 'xxh64' => 16, - 'xxh128' => 32, + 'sha3-224' => 56, + 'sha3-256' => 64, + 'sha3-384' => 96, + 'sha3-512' => 128, + 'tiger128' => 32, + 'tiger160' => 40, + 'tiger192' => 48, + 'whirlpool' => 128, + 'xxh32' => 8, + 'xxh64' => 16, + 'xxh128' => 32, ]; private int $length; @@ -85,6 +85,6 @@ public function clientRules(): array private function pattern(): string { - return '/^[a-fA-F0-9]{'.$this->length.'}$/D'; + return '/^[a-fA-F0-9]{' . $this->length . '}$/D'; } } diff --git a/src/Rules/Identifiers/SemVer.php b/src/Rules/Identifiers/SemVer.php index fda1cc3..f05b1af 100644 --- a/src/Rules/Identifiers/SemVer.php +++ b/src/Rules/Identifiers/SemVer.php @@ -27,9 +27,9 @@ final class SemVer implements ClientCheckable, ValidationRule * pathological input in the test suite rather than assumed. */ private const string PATTERN = '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' - .'(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)' - .'(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' - .'(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/D'; + . '(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)' + . '(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' + . '(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/D'; public function validate(string $attribute, mixed $value, Closure $fail): void { diff --git a/src/Rules/Markup/Xml.php b/src/Rules/Markup/Xml.php index 309b9e7..55d0b78 100644 --- a/src/Rules/Markup/Xml.php +++ b/src/Rules/Markup/Xml.php @@ -6,8 +6,8 @@ use Closure; use DOMDocument; -use Illuminate\Contracts\Validation\ValidationRule; use LibXMLError; +use Illuminate\Contracts\Validation\ValidationRule; /** * Well-formed XML, optionally valid against an XSD schema. diff --git a/src/Rules/Net/DomainName.php b/src/Rules/Net/DomainName.php index 308410c..ea7b151 100644 --- a/src/Rules/Net/DomainName.php +++ b/src/Rules/Net/DomainName.php @@ -39,7 +39,7 @@ private const string NR_LDH_LABEL = '/^(?!-)[a-z0-9-]{1,63}(? $networks + * @param list $networks */ public static function passes(mixed $value, array $networks): bool { @@ -97,7 +97,7 @@ private static function unwrap(string $address): string return $address; } - if (! str_starts_with($packed, str_repeat("\x00", 10)."\xff\xff")) { + if (! str_starts_with($packed, str_repeat("\x00", 10) . "\xff\xff")) { return $address; } diff --git a/src/Rules/Net/MacAddress.php b/src/Rules/Net/MacAddress.php index 3542567..1202921 100644 --- a/src/Rules/Net/MacAddress.php +++ b/src/Rules/Net/MacAddress.php @@ -50,11 +50,11 @@ public const string BARE = 'bare'; /** - * @param list $formats Accepted notations; empty accepts all four. - * @param int|null $bytes 6 for EUI-48, 8 for EUI-64; null accepts either. - * @param bool $requireUnicast Reject multicast addresses (I/G bit set). - * @param bool $requireUniversal Reject locally-administered addresses (U/L bit set). - * @param list $ouis Accepted OUI prefixes, in any notation; empty accepts any. + * @param list $formats Accepted notations; empty accepts all four. + * @param int|null $bytes 6 for EUI-48, 8 for EUI-64; null accepts either. + * @param bool $requireUnicast Reject multicast addresses (I/G bit set). + * @param bool $requireUniversal Reject locally-administered addresses (U/L bit set). + * @param list $ouis Accepted OUI prefixes, in any notation; empty accepts any. */ public function __construct( private array $formats = [], @@ -220,8 +220,8 @@ private function isNull(array $octets): bool } /** - * @param list $octets - * @param list $ouis + * @param list $octets + * @param list $ouis */ private function matchesAnyOui(array $octets, array $ouis): bool { diff --git a/src/Rules/Net/Support/HostPattern.php b/src/Rules/Net/Support/HostPattern.php index cc67cf9..1a132d2 100644 --- a/src/Rules/Net/Support/HostPattern.php +++ b/src/Rules/Net/Support/HostPattern.php @@ -30,7 +30,7 @@ final class HostPattern { /** - * @param list $patterns + * @param list $patterns */ public static function matches(string $host, array $patterns): bool { @@ -48,7 +48,7 @@ public static function matches(string $host, array $patterns): bool // The leading dot is the whole point: without it, // `evilexample.com` matches `*.example.com`. - if ($parent !== '' && str_ends_with($host, '.'.$parent)) { + if ($parent !== '' && str_ends_with($host, '.' . $parent)) { return true; } diff --git a/src/Rules/Net/Support/IpClassifier.php b/src/Rules/Net/Support/IpClassifier.php index b3bee11..cbe206b 100644 --- a/src/Rules/Net/Support/IpClassifier.php +++ b/src/Rules/Net/Support/IpClassifier.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Rules\Net\Support; -use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; use Simtabi\Laranail\Validation\Rules\Net\PublicIp; +use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; /** * Decides whether an IP address is publicly routable. @@ -109,7 +109,7 @@ private static function unwrapMappedV4(string $ip): ?string } // ::ffff:a.b.c.d — 80 zero bits, 16 one bits, then the v4 address. - $isMapped = str_starts_with($packed, str_repeat("\x00", 10)."\xff\xff"); + $isMapped = str_starts_with($packed, str_repeat("\x00", 10) . "\xff\xff"); // ::a.b.c.d, the deprecated IPv4-compatible form. `::` and `::1` are // handled by the v6 table, so exclude anything that low. @@ -126,7 +126,7 @@ private static function unwrapMappedV4(string $ip): ?string } /** - * @param list $ranges + * @param list $ranges */ private static function matchesAny(string $ip, array $ranges): bool { diff --git a/src/Rules/Net/Url.php b/src/Rules/Net/Url.php index 1aee736..ccf5e20 100644 --- a/src/Rules/Net/Url.php +++ b/src/Rules/Net/Url.php @@ -60,14 +60,14 @@ private const array DEFAULT_PORTS = ['http' => 80, 'https' => 443, 'ftp' => 21, 'ftps' => 990, 'ws' => 80, 'wss' => 443]; /** - * @param list $schemes Accepted schemes, lowercase, without `://`. - * @param list $hosts Allow-list; `*.example.com` matches subdomains only. - * @param list $blockedHosts Deny-list, same syntax, applied after the allow-list. - * @param list $ports Accepted ports; empty accepts any, including none. - * @param bool $allowCredentials Permit `user:pass@` in the authority. - * @param bool $allowIpHost Permit an IP literal instead of a name. - * @param bool $publicHostOnly Reject reserved IP literals and loopback names. - * @param bool $requireTld Reject single-label hosts such as `intranet`. + * @param list $schemes Accepted schemes, lowercase, without `://`. + * @param list $hosts Allow-list; `*.example.com` matches subdomains only. + * @param list $blockedHosts Deny-list, same syntax, applied after the allow-list. + * @param list $ports Accepted ports; empty accepts any, including none. + * @param bool $allowCredentials Permit `user:pass@` in the authority. + * @param bool $allowIpHost Permit an IP literal instead of a name. + * @param bool $publicHostOnly Reject reserved IP literals and loopback names. + * @param bool $requireTld Reject single-label hosts such as `intranet`. */ public function __construct( private array $schemes = ['http', 'https'], diff --git a/src/Rules/Network/DeliverableEmail.php b/src/Rules/Network/DeliverableEmail.php index 8f6fa5a..cdfce13 100644 --- a/src/Rules/Network/DeliverableEmail.php +++ b/src/Rules/Network/DeliverableEmail.php @@ -8,8 +8,8 @@ use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Concerns\SkipsPrecognition; use Simtabi\Laranail\Validation\Contracts\Email\DnsResolver; -use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; use Simtabi\Laranail\Validation\Rules\Email\Support\Address; +use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; /** * The address's domain can actually receive mail. diff --git a/src/Rules/Network/HasGravatar.php b/src/Rules/Network/HasGravatar.php index 23b1d63..9d0d0ac 100644 --- a/src/Rules/Network/HasGravatar.php +++ b/src/Rules/Network/HasGravatar.php @@ -5,12 +5,12 @@ namespace Simtabi\Laranail\Validation\Rules\Network; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; +use Illuminate\Http\Client\ConnectionException; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Concerns\SkipsPrecognition; -use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; use Simtabi\Laranail\Validation\Rules\Email\Support\Address; +use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; /** * The email address has a Gravatar — a HEAD probe of the `d=404` avatar @@ -61,7 +61,7 @@ private function passes(string $value): bool try { $response = Http::timeout($this->timeoutSeconds) ->connectTimeout($this->timeoutSeconds) - ->head('https://gravatar.com/avatar/'.$hash.'?d=404'); + ->head('https://gravatar.com/avatar/' . $hash . '?d=404'); } catch (ConnectionException) { return true; } diff --git a/src/Rules/Network/ImageUrl.php b/src/Rules/Network/ImageUrl.php index e603376..76ca21c 100644 --- a/src/Rules/Network/ImageUrl.php +++ b/src/Rules/Network/ImageUrl.php @@ -5,12 +5,12 @@ namespace Simtabi\Laranail\Validation\Rules\Network; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; +use Illuminate\Http\Client\ConnectionException; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Concerns\SkipsPrecognition; -use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; use Simtabi\Laranail\Validation\Rules\Net\Support\IpClassifier; +use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; /** * The URL currently serves an image — a HEAD probe expecting 200 with an @@ -40,7 +40,7 @@ private const array LOOPBACK_NAMES = ['localhost', 'localhost.localdomain', 'ip6-localhost', 'ip6-loopback']; /** - * @param list $mimes Accepted `image/*` subtypes; empty accepts any image. + * @param list $mimes Accepted `image/*` subtypes; empty accepts any image. */ public function __construct( private array $mimes = [], diff --git a/src/Rules/Numbers/MonetaryAmount.php b/src/Rules/Numbers/MonetaryAmount.php index 0460378..db3dca7 100644 --- a/src/Rules/Numbers/MonetaryAmount.php +++ b/src/Rules/Numbers/MonetaryAmount.php @@ -69,9 +69,9 @@ public static function passes(mixed $value, int $decimals = 2, bool $allowNegati public static function pattern(int $decimals = 2, bool $allowNegative = false): string { $sign = $allowNegative ? '[+-]?' : '\\+?'; - $fraction = $decimals > 0 ? '(?:\\.\\d{1,'.$decimals.'})?' : ''; + $fraction = $decimals > 0 ? '(?:\\.\\d{1,' . $decimals . '})?' : ''; - return '/^'.$sign.'\\d+'.$fraction.'$/D'; + return '/^' . $sign . '\\d+' . $fraction . '$/D'; } public function validate(string $attribute, mixed $value, Closure $fail): void diff --git a/src/Rules/Numbers/Parity.php b/src/Rules/Numbers/Parity.php index 5d083ab..a243b24 100644 --- a/src/Rules/Numbers/Parity.php +++ b/src/Rules/Numbers/Parity.php @@ -48,15 +48,15 @@ public static function passes(mixed $value, string $parity): bool return match (mb_strtolower(trim($parity))) { self::EVEN => $remainder === 0, - self::ODD => $remainder === 1, - default => false, + self::ODD => $remainder === 1, + default => false, }; } public function validate(string $attribute, mixed $value, Closure $fail): void { if (! self::passes($value, $this->parity)) { - $fail('laranail/validation::validation.parity.'.$this->normalised())->translate(); + $fail('laranail/validation::validation.parity.' . $this->normalised())->translate(); } } diff --git a/src/Rules/Payment/CardCvc.php b/src/Rules/Payment/CardCvc.php index b8b7f6b..6232723 100644 --- a/src/Rules/Payment/CardCvc.php +++ b/src/Rules/Payment/CardCvc.php @@ -5,11 +5,11 @@ namespace Simtabi\Laranail\Validation\Rules\Payment; use Closure; +use Illuminate\Support\Arr; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Arr; -use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; use Simtabi\Laranail\Validation\Support\Payment\CardBrand; +use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; /** * A card security code. Alone, 3 or 4 digits; given `numberField:`, the diff --git a/src/Rules/Payment/CardNumber.php b/src/Rules/Payment/CardNumber.php index 2b2905d..2b8f4be 100644 --- a/src/Rules/Payment/CardNumber.php +++ b/src/Rules/Payment/CardNumber.php @@ -7,8 +7,8 @@ use Closure; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Translation\PotentiallyTranslatedString; -use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; use Simtabi\Laranail\Validation\Support\Payment\CardBrand; +use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; /** * A payment card number: brand identified by IIN range (via the @@ -36,7 +36,7 @@ final class CardNumber implements ValidationRule { /** - * @param list|null $brands Accepted brand slugs; null accepts the whole catalogue. + * @param list|null $brands Accepted brand slugs; null accepts the whole catalogue. */ public function __construct( private readonly ?array $brands = null, @@ -99,7 +99,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void if (! in_array(strlen($digits), $brand->lengths, true)) { $this->fail($fail, 'card_number_length', [ 'lengths' => implode(' or ', $brand->lengths), - 'brand' => $brand->displayName, + 'brand' => $brand->displayName, ]); return; @@ -116,11 +116,11 @@ private function catalogue(): CardBrandCatalogue } /** - * @param Closure(string): PotentiallyTranslatedString $fail - * @param array $parameters + * @param Closure(string): PotentiallyTranslatedString $fail + * @param array $parameters */ private function fail(Closure $fail, string $key, array $parameters = []): void { - $fail('laranail/validation::validation.'.$key)->translate($parameters); + $fail('laranail/validation::validation.' . $key)->translate($parameters); } } diff --git a/src/Rules/Postal/Patterns.php b/src/Rules/Postal/Patterns.php index d563203..c278b28 100644 --- a/src/Rules/Postal/Patterns.php +++ b/src/Rules/Postal/Patterns.php @@ -30,19 +30,19 @@ final class Patterns * @var array */ private const array BY_PATTERN = [ - 'AD, AS, BA, CU, DE, DZ, EE, ES, FI, FM, FR, GF, GP, GU, HR, IC, ID, IT, KR, LT,'. - 'MA, MC, ME, MH, MP, MQ, MY, NC, PK, PR, PW, RE, RS, SM, TH, TR, UA, US, VI, XK,'. + 'AD, AS, BA, CU, DE, DZ, EE, ES, FI, FM, FR, GF, GP, GU, HR, IC, ID, IT, KR, LT,' . + 'MA, MC, ME, MH, MP, MQ, MY, NC, PK, PR, PW, RE, RS, SM, TH, TR, UA, US, VI, XK,' . 'YT' => '/^[0-9]{5}$/D', - 'AM, AR, AT, AU, BD, BE, BG, CH, CY, DK, GE, GL, HU, LI, LU, LV, MD, MK, NO, NZ,'. - 'PH, SI, TN, ZA' => '/^[0-9]{4}$/D', + 'AM, AR, AT, AU, BD, BE, BG, CH, CY, DK, GE, GL, HU, LI, LU, LV, MD, MK, NO, NZ,' . + 'PH, SI, TN, ZA' => '/^[0-9]{4}$/D', 'BY, CN, EC, IN, KG, KZ, RO, RU, SG, TJ' => '/^[0-9]{6}$/D', - 'CZ, GR, SE, SK' => '/^[0-9]{3} [0-9]{2}$/D', - 'FO, IS, MG, PG' => '/^[0-9]{3}$/D', - 'GG, JE' => '/^[a-z]{2}[0-9][0-9]? [0-9][a-z]{2}$/iD', - 'MV, MX' => '/^[0-9]{4}[0-9]?$/D', - 'AZ' => '/^[0-9]{4}([0-9]{2})?$/D', - 'BN' => '/^[a-z]{2}[0-9]{4}$/iD', - 'BR' => '/^[0-9]{5}(-?[0-9]{3})?$/D', + 'CZ, GR, SE, SK' => '/^[0-9]{3} [0-9]{2}$/D', + 'FO, IS, MG, PG' => '/^[0-9]{3}$/D', + 'GG, JE' => '/^[a-z]{2}[0-9][0-9]? [0-9][a-z]{2}$/iD', + 'MV, MX' => '/^[0-9]{4}[0-9]?$/D', + 'AZ' => '/^[0-9]{4}([0-9]{2})?$/D', + 'BN' => '/^[a-z]{2}[0-9]{4}$/iD', + 'BR' => '/^[0-9]{5}(-?[0-9]{3})?$/D', // Corrected from the source table, which made the final digit optional // and so accepted the five-character 'K1A 0B'. A Canadian postal code is // always A1A 1A1. Canada Post also excludes D, F, I, O, Q and U in every diff --git a/src/Rules/Postal/PostalCode.php b/src/Rules/Postal/PostalCode.php index 68e0a04..6c0a536 100644 --- a/src/Rules/Postal/PostalCode.php +++ b/src/Rules/Postal/PostalCode.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\Postal; use Closure; +use Illuminate\Support\Arr; use Illuminate\Contracts\Validation\DataAwareRule; use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Arr; use Simtabi\Laranail\Validation\Contracts\ClientCheckable; /** @@ -45,7 +45,7 @@ final class PostalCode implements ClientCheckable, DataAwareRule, ValidationRule private readonly array $countries; /** - * @param string|list $countries + * @param string|list $countries */ public function __construct(string|array $countries = [], private readonly ?string $countryField = null) { @@ -72,7 +72,7 @@ public static function reference(string $field): self * key type in the implementation is a contravariance violation — a caller holding the interface * may legitimately hand over an integer-keyed array. * - * @param array $data + * @param array $data */ public function setData(array $data): static { @@ -148,11 +148,11 @@ public function clientRules(): array // Several countries: each pattern keeps its own anchors and flags // inside a group, since they do not all share the same flags. $alternation = implode('|', array_map( - static fn (string $p): string => '(?:'.self::inlineFlags($p).')', + static fn (string $p): string => '(?:' . self::inlineFlags($p) . ')', $patterns, )); - return [['rule' => 'regex', 'params' => ['pattern' => '/^(?:'.$alternation.')$/D']]]; + return [['rule' => 'regex', 'params' => ['pattern' => '/^(?:' . $alternation . ')$/D']]]; } /** @@ -171,7 +171,7 @@ private static function inlineFlags(string $pattern): string $body = preg_replace('/^\^|\$$/', '', $m[1]) ?? $m[1]; $flags = str_replace(['u', 'D'], '', $m[2]); - return $flags === '' ? $body : '(?'.$flags.':'.$body.')'; + return $flags === '' ? $body : '(?' . $flags . ':' . $body . ')'; } /** @return list */ diff --git a/src/Rules/Profanity/NoProfanity.php b/src/Rules/Profanity/NoProfanity.php index 6af42b5..dcb2046 100644 --- a/src/Rules/Profanity/NoProfanity.php +++ b/src/Rules/Profanity/NoProfanity.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Profanity; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use Normalizer; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Contracts\TermList; /** @@ -52,8 +52,8 @@ final class NoProfanity implements ValidationRule private ?array $normalisedAllowed = null; /** - * @param TermList|list $terms - * @param list $allowed Ignored when $terms is a TermList, which carries its own. + * @param TermList|list $terms + * @param list $allowed Ignored when $terms is a TermList, which carries its own. */ public function __construct( private readonly TermList|array $terms, @@ -177,11 +177,11 @@ private function pattern(string $term): string } $body = implode($separator, array_map( - static fn (string $c): string => preg_quote($c, '/').'+', + static fn (string $c): string => preg_quote($c, '/') . '+', $characters, )); - return '/\b'.$body.'\b/u'; + return '/\b' . $body . '\b/u'; } /** @return list */ @@ -197,7 +197,8 @@ private function allowed(): array } /** - * @param list $values + * @param list $values + * * @return list */ private function normaliseAll(array $values): array diff --git a/src/Rules/Storage/FileExistsOnDisk.php b/src/Rules/Storage/FileExistsOnDisk.php index 8cd465b..70be442 100644 --- a/src/Rules/Storage/FileExistsOnDisk.php +++ b/src/Rules/Storage/FileExistsOnDisk.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Storage; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Facades\Storage; +use Illuminate\Contracts\Validation\ValidationRule; /** * The value names a file that exists on a Laravel disk, optionally scoped @@ -47,7 +47,7 @@ private function passes(string $value): bool return false; } - $path = $this->directory === '' ? $value : rtrim($this->directory, '/').'/'.$value; + $path = $this->directory === '' ? $value : rtrim($this->directory, '/') . '/' . $value; return Storage::disk($this->disk)->exists($path); } diff --git a/src/Rules/Structure/Delimited.php b/src/Rules/Structure/Delimited.php index 65d080d..836263c 100644 --- a/src/Rules/Structure/Delimited.php +++ b/src/Rules/Structure/Delimited.php @@ -5,9 +5,9 @@ namespace Simtabi\Laranail\Validation\Rules\Structure; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Facades\Validator; use InvalidArgumentException; +use Illuminate\Support\Facades\Validator; +use Illuminate\Contracts\Validation\ValidationRule; /** * Validate every item of a delimited string against the same rule. @@ -47,12 +47,12 @@ private string $separator; /** - * @param list $rules Applied to each item. - * @param string $separator Splits the string; not trimmed itself. - * @param int|null $min Fewest items required. - * @param int|null $max Most items allowed. - * @param bool $distinct Reject repeated items. - * @param bool $trim Trim whitespace around each item. + * @param list $rules Applied to each item. + * @param string $separator Splits the string; not trimmed itself. + * @param int|null $min Fewest items required. + * @param int|null $max Most items allowed. + * @param bool $distinct Reject repeated items. + * @param bool $trim Trim whitespace around each item. * * @throws InvalidArgumentException If the separator is empty. */ diff --git a/src/Rules/Telecom/Phone.php b/src/Rules/Telecom/Phone.php index 9eb88b4..93ea54e 100644 --- a/src/Rules/Telecom/Phone.php +++ b/src/Rules/Telecom/Phone.php @@ -5,13 +5,13 @@ namespace Simtabi\Laranail\Validation\Rules\Telecom; use Closure; -use Illuminate\Contracts\Validation\DataAwareRule; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Arr; use RuntimeException; -use Simtabi\Laranail\Phone\Enums\PhoneNumberType; +use Illuminate\Support\Arr; use Simtabi\Laranail\Phone\PhoneFormatter; use Simtabi\Laranail\Phone\PhoneNumberValue; +use Simtabi\Laranail\Phone\Enums\PhoneNumberType; +use Illuminate\Contracts\Validation\DataAwareRule; +use Illuminate\Contracts\Validation\ValidationRule; /** * A phone number, checked against Google's numbering-plan metadata. @@ -44,8 +44,8 @@ final class Phone implements DataAwareRule, ValidationRule private array $data = []; /** - * @param list $countries ISO 3166-1 alpha-2 codes; empty means any country - * @param list $types Acceptable line types; empty means any + * @param list $countries ISO 3166-1 alpha-2 codes; empty means any country + * @param list $types Acceptable line types; empty means any */ public function __construct( private readonly array $countries = [], @@ -58,7 +58,7 @@ public function __construct( ) {} /** - * @param array $data + * @param array $data */ public function setData(array $data): static { @@ -190,8 +190,8 @@ private function formatter(): PhoneFormatter if (! class_exists(PhoneFormatter::class)) { throw new RuntimeException( 'The phone rule requires laranail/phone. Install it with `composer require laranail/phone`. ' - ."It is a suggested rather than a required dependency because it pulls libphonenumber's " - .'numbering-plan metadata, which a project validating only strings and dates should not carry.', + . "It is a suggested rather than a required dependency because it pulls libphonenumber's " + . 'numbering-plan metadata, which a project validating only strings and dates should not carry.', ); } diff --git a/src/Rules/Telecom/UniquePhone.php b/src/Rules/Telecom/UniquePhone.php index df2a093..773e434 100644 --- a/src/Rules/Telecom/UniquePhone.php +++ b/src/Rules/Telecom/UniquePhone.php @@ -5,13 +5,13 @@ namespace Simtabi\Laranail\Validation\Rules\Telecom; use Closure; -use Illuminate\Contracts\Validation\DataAwareRule; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Database\DatabaseManager; -use Illuminate\Database\Query\Builder; -use Illuminate\Support\Arr; use RuntimeException; +use Illuminate\Support\Arr; +use Illuminate\Database\Query\Builder; +use Illuminate\Database\DatabaseManager; use Simtabi\Laranail\Phone\PhoneFormatter; +use Illuminate\Contracts\Validation\DataAwareRule; +use Illuminate\Contracts\Validation\ValidationRule; /** * Uniqueness for a phone column, compared in E.164 rather than as typed. @@ -39,10 +39,10 @@ final class UniquePhone implements DataAwareRule, ValidationRule private string $ignoreColumn = 'id'; /** - * @param string $table The table to search - * @param string $column The column holding E.164 - * @param string|null $country ISO 3166-1 alpha-2 hint for parsing bare national input - * @param string|null $countryField A sibling field to read that hint from instead + * @param string $table The table to search + * @param string $column The column holding E.164 + * @param string|null $country ISO 3166-1 alpha-2 hint for parsing bare national input + * @param string|null $countryField A sibling field to read that hint from instead */ public function __construct( private readonly string $table, @@ -67,7 +67,7 @@ public function ignore(mixed $id, string $column = 'id'): self } /** - * @param array $data + * @param array $data */ public function setData(array $data): static { @@ -131,8 +131,8 @@ private function formatter(): PhoneFormatter if (! class_exists(PhoneFormatter::class)) { throw new RuntimeException( 'The phone rules require laranail/phone. Install it with `composer require laranail/phone`. ' - ."It is a suggested rather than a required dependency because it carries libphonenumber's " - .'numbering-plan metadata, which a project validating only strings and dates should not have to install.', + . "It is a suggested rather than a required dependency because it carries libphonenumber's " + . 'numbering-plan metadata, which a project validating only strings and dates should not have to install.', ); } diff --git a/src/Rules/Text/CaseStyle.php b/src/Rules/Text/CaseStyle.php index 55dc590..db6806c 100644 --- a/src/Rules/Text/CaseStyle.php +++ b/src/Rules/Text/CaseStyle.php @@ -41,10 +41,10 @@ * separators. */ private const array PATTERNS = [ - self::CAMEL => '/^[a-z][a-zA-Z0-9]*$/D', + self::CAMEL => '/^[a-z][a-zA-Z0-9]*$/D', self::PASCAL => '/^[A-Z][a-zA-Z0-9]*$/D', - self::SNAKE => '/^[a-z0-9]+(?:_[a-z0-9]+)*$/D', - self::KEBAB => '/^[a-z0-9]+(?:-[a-z0-9]+)*$/D', + self::SNAKE => '/^[a-z0-9]+(?:_[a-z0-9]+)*$/D', + self::KEBAB => '/^[a-z0-9]+(?:-[a-z0-9]+)*$/D', // Each space-separated word starts with a capital. Digits may follow. self::TITLE => '/^[A-Z][a-z0-9]*(?: [A-Z][a-z0-9]*)*$/D', ]; @@ -63,7 +63,7 @@ public static function passes(mixed $value, string $style): bool public function validate(string $attribute, mixed $value, Closure $fail): void { if (! self::passes($value, $this->style)) { - $fail('laranail/validation::validation.case_style.'.$this->style)->translate(); + $fail('laranail/validation::validation.case_style.' . $this->style)->translate(); } } diff --git a/src/Rules/Text/MaxWords.php b/src/Rules/Text/MaxWords.php index 7773e2b..ade6e58 100644 --- a/src/Rules/Text/MaxWords.php +++ b/src/Rules/Text/MaxWords.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Text; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use LogicException; +use Illuminate\Contracts\Validation\ValidationRule; /** * At most `$max` words. diff --git a/src/Rules/Text/MinWords.php b/src/Rules/Text/MinWords.php index 6a2e0e9..ad7824a 100644 --- a/src/Rules/Text/MinWords.php +++ b/src/Rules/Text/MinWords.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation\Rules\Text; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; use LogicException; +use Illuminate\Contracts\Validation\ValidationRule; /** * At least `$min` words — the usual guard on a free-text field that must be diff --git a/src/Rules/Text/PersonName.php b/src/Rules/Text/PersonName.php index 6d3bd03..4618f78 100644 --- a/src/Rules/Text/PersonName.php +++ b/src/Rules/Text/PersonName.php @@ -52,8 +52,8 @@ private const string LETTER = '/\p{L}/u'; /** - * @param int $minNames Fewest whitespace-separated names the field may carry. - * @param int|null $maxNames Most it may carry; null for no upper bound. + * @param int $minNames Fewest whitespace-separated names the field may carry. + * @param int|null $maxNames Most it may carry; null for no upper bound. */ public function __construct( private bool $allowDigits = false, @@ -185,6 +185,6 @@ private function namePattern(): ?string $gaps = max(1, $this->minNames) - 1; $limit = $this->maxNames === null ? '' : (string) ($this->maxNames - 1); - return '/^\s*\S+(?:\s+\S+){'.$gaps.','.$limit.'}\s*$/uD'; + return '/^\s*\S+(?:\s+\S+){' . $gaps . ',' . $limit . '}\s*$/uD'; } } diff --git a/src/Rules/Text/Salutation.php b/src/Rules/Text/Salutation.php index b79a2fa..c7c5bdb 100644 --- a/src/Rules/Text/Salutation.php +++ b/src/Rules/Text/Salutation.php @@ -32,7 +32,7 @@ final class Salutation implements ValidationRule private readonly ?array $accepted; /** - * @param list|null $accepted Replacement list, lowercase without dots. + * @param list|null $accepted Replacement list, lowercase without dots. */ public function __construct(?array $accepted = null) { @@ -50,7 +50,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $normalised = rtrim(mb_strtolower(trim($value)), '.'); if ($this->accepted === null) { - self::$bundled ??= CodeFile::load(dirname(__DIR__, 3).'/resources/data/salutations.txt'); + self::$bundled ??= CodeFile::load(dirname(__DIR__, 3) . '/resources/data/salutations.txt'); } $list = $this->accepted ?? self::$bundled ?? []; diff --git a/src/Rules/Text/Username.php b/src/Rules/Text/Username.php index 31e33c5..85eb10d 100644 --- a/src/Rules/Text/Username.php +++ b/src/Rules/Text/Username.php @@ -57,11 +57,11 @@ private const string DEFAULT_SEPARATORS = '._-'; /** - * @param int $min Fewest characters, counting separators. - * @param int $max Most characters, counting separators. - * @param string $separators Which characters may appear between others. - * @param bool $lowercase Reject uppercase rather than accepting and folding it. - * @param list|null $reserved Replaces the default list; `[]` disables the check. + * @param int $min Fewest characters, counting separators. + * @param int $max Most characters, counting separators. + * @param string $separators Which characters may appear between others. + * @param bool $lowercase Reject uppercase rather than accepting and folding it. + * @param list|null $reserved Replaces the default list; `[]` disables the check. */ public function __construct( private int $min = 3, @@ -101,7 +101,7 @@ public static function passes( * per character. That is what makes this rule expressible as a pattern at * all, and why a rule with a Unicode class could not do the same. * - * @param string $separators Escaped for a character class before use. + * @param string $separators Escaped for a character class before use. */ public static function pattern( int $min = 3, @@ -117,13 +117,13 @@ public static function pattern( if ($separators === '') { // No separators at all — a flat alphanumeric handle. The general // pattern below would still work, but this reads as what it is. - return '/^(?=.{'.$min.','.$max.'}$)['.$alphabet.']+$/D'; + return '/^(?=.{' . $min . ',' . $max . '}$)[' . $alphabet . ']+$/D'; } $class = self::escapeForCharacterClass($separators); // Alphanumeric at both ends; separators only between, never doubled. - return '/^(?=.{'.$min.','.$max.'}$)['.$alphabet.']+(?:['.$class.']['.$alphabet.']+)*$/D'; + return '/^(?=.{' . $min . ',' . $max . '}$)[' . $alphabet . ']+(?:[' . $class . '][' . $alphabet . ']+)*$/D'; } /** @@ -133,7 +133,7 @@ public static function pattern( * compared the literal value would let `a.d.m.i.n` and `ad-min` through, * and those are the same claim to anyone reading a profile page. * - * @param list $reserved + * @param list $reserved */ public static function isReserved(string $value, array $reserved, string $separators = self::DEFAULT_SEPARATORS): bool { @@ -148,7 +148,7 @@ public static function isReserved(string $value, array $reserved, string $separa $stripped = strtolower($separators === '' ? $value - : (string) preg_replace('/['.self::escapeForCharacterClass($separators).']/', '', $value)); + : (string) preg_replace('/[' . self::escapeForCharacterClass($separators) . ']/', '', $value)); $literal = strtolower($value); @@ -193,7 +193,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void public function clientRules(): array { return [[ - 'rule' => 'regex', + 'rule' => 'regex', 'params' => ['pattern' => self::pattern($this->min, $this->max, $this->separators, $this->lowercase)], ]]; } diff --git a/src/Rules/Vendor/VendorIdentifier.php b/src/Rules/Vendor/VendorIdentifier.php index bbcfe5a..200dfb9 100644 --- a/src/Rules/Vendor/VendorIdentifier.php +++ b/src/Rules/Vendor/VendorIdentifier.php @@ -54,10 +54,10 @@ /** @var array */ private const array PATTERNS = [ - self::GOOGLE_ANALYTICS => '/^G-[A-Z0-9]{10}$/D', + self::GOOGLE_ANALYTICS => '/^G-[A-Z0-9]{10}$/D', self::GOOGLE_TAG_MANAGER => '/^GTM-[A-Z0-9]{6,8}$/D', - self::FACEBOOK_PIXEL => '/^\d{15,16}$/D', - self::AWS_REGION => '/^[a-z]{2}(?:-gov)?-[a-z]{4,9}-\d$/D', + self::FACEBOOK_PIXEL => '/^\d{15,16}$/D', + self::AWS_REGION => '/^[a-z]{2}(?:-gov)?-[a-z]{4,9}-\d$/D', // No consecutive periods, and cannot start or end with one. self::DISCORD_USERNAME => '/^(?!.*\.\.)[a-z0-9_.]{2,32}$/D', ]; @@ -88,7 +88,7 @@ public static function passes(mixed $value, string $vendor): bool // either way would admit a value Discord itself refuses. $candidate = match ($vendor) { self::GOOGLE_ANALYTICS, self::GOOGLE_TAG_MANAGER => mb_strtoupper($value), - default => $value, + default => $value, }; return preg_match(self::PATTERNS[$vendor], $candidate) === 1; @@ -132,7 +132,7 @@ public function clientRules(): array $foldsCase = in_array($vendor, [self::GOOGLE_ANALYTICS, self::GOOGLE_TAG_MANAGER], true); - return [['rule' => 'regex', 'params' => ['pattern' => $foldsCase ? $pattern.'i' : $pattern]]]; + return [['rule' => 'regex', 'params' => ['pattern' => $foldsCase ? $pattern . 'i' : $pattern]]]; } /** diff --git a/src/Schemas/PersonNameSchema.php b/src/Schemas/PersonNameSchema.php index 16dd34a..39f3147 100644 --- a/src/Schemas/PersonNameSchema.php +++ b/src/Schemas/PersonNameSchema.php @@ -4,12 +4,12 @@ namespace Simtabi\Laranail\Validation\Schemas; -use Illuminate\Support\Traits\Macroable; -use InvalidArgumentException; use LogicException; +use InvalidArgumentException; +use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\Rules\Text\PersonName; -use Simtabi\Laranail\Validation\RuleSet; /** * The rules for a person's name, across however many fields the system has. @@ -93,7 +93,7 @@ public static function make(string ...$fields): self if (count($fields) !== count(array_unique($fields))) { throw new InvalidArgumentException( - 'A name field may only be declared once; got '.implode(', ', $fields).'.', + 'A name field may only be declared once; got ' . implode(', ', $fields) . '.', ); } @@ -204,7 +204,7 @@ public function withoutCharacterCheck(): self * Defaults are derived from the field name (`first_name` → "first name"), * which is what Laravel would have shown anyway. * - * @param array $labels + * @param array $labels */ public function labels(array $labels): self { @@ -249,7 +249,8 @@ public function rules(): array * omitted it, so the caller writes a complete row rather than a partial one. * Keys the caller did not declare are dropped. * - * @param array $input + * @param array $input + * * @return array */ public function normalise(array $input): array @@ -273,7 +274,8 @@ public function normalise(array $input): array /** * The list form of {@see normalise()} — trimmed, blanks dropped, reindexed. * - * @param array $names + * @param array $names + * * @return list */ public function normaliseList(array $names): array @@ -383,7 +385,7 @@ private function atLeastOneMessage(): string $labels = array_map($this->labelFor(...), $this->fields); $last = (string) array_pop($labels); - $values = $labels === [] ? $last : implode(', ', $labels).' or '.$last; + $values = $labels === [] ? $last : implode(', ', $labels) . ' or ' . $last; $key = 'laranail/validation::validation.person_name_required'; $message = trans($key, ['values' => $values]); @@ -411,7 +413,7 @@ private function labelFor(string $field): string * by one of them. The builders it produces are freshly constructed per * call, so nothing downstream shares state either. * - * @param callable(self): void $mutate + * @param callable(self): void $mutate */ private function with(callable $mutate): self { diff --git a/src/Support/Email/BundledDisposableDomainList.php b/src/Support/Email/BundledDisposableDomainList.php index fe9fa3b..5262820 100644 --- a/src/Support/Email/BundledDisposableDomainList.php +++ b/src/Support/Email/BundledDisposableDomainList.php @@ -29,14 +29,14 @@ final class BundledDisposableDomainList implements DisposableDomainList public function contains(string $domain): bool { - $this->domains ??= DomainFile::load(__DIR__.'/../../../resources/data/disposable-domains.txt'); + $this->domains ??= DomainFile::load(__DIR__ . '/../../../resources/data/disposable-domains.txt'); return isset($this->domains[strtolower(trim($domain))]); } public function count(): int { - $this->domains ??= DomainFile::load(__DIR__.'/../../../resources/data/disposable-domains.txt'); + $this->domains ??= DomainFile::load(__DIR__ . '/../../../resources/data/disposable-domains.txt'); return count($this->domains); } diff --git a/src/Support/Email/BundledRoleAccountList.php b/src/Support/Email/BundledRoleAccountList.php index c97366f..cfbf2ed 100644 --- a/src/Support/Email/BundledRoleAccountList.php +++ b/src/Support/Email/BundledRoleAccountList.php @@ -22,7 +22,7 @@ final class BundledRoleAccountList implements RoleAccountList public function contains(string $localPart): bool { - $this->localParts ??= DomainFile::load(__DIR__.'/../../../resources/data/role-accounts.txt'); + $this->localParts ??= DomainFile::load(__DIR__ . '/../../../resources/data/role-accounts.txt'); return isset($this->localParts[strtolower(trim($localPart))]); } diff --git a/src/Support/Encoding/Base64File.php b/src/Support/Encoding/Base64File.php index 21157ce..f1b7b8a 100644 --- a/src/Support/Encoding/Base64File.php +++ b/src/Support/Encoding/Base64File.php @@ -21,7 +21,7 @@ final class Base64File { /** - * @param string $name The client filename the rules will see. + * @param string $name The client filename the rules will see. */ public static function toUploadedFile(mixed $value, string $name = 'upload'): ?UploadedFile { diff --git a/src/Support/I18n/BundledCountryDataset.php b/src/Support/I18n/BundledCountryDataset.php index 707f02d..fcc7e8e 100644 --- a/src/Support/I18n/BundledCountryDataset.php +++ b/src/Support/I18n/BundledCountryDataset.php @@ -26,14 +26,14 @@ final class BundledCountryDataset implements CountryDataset public function isAlpha2(string $code): bool { - $this->alpha2 ??= CodeFile::load(__DIR__.'/../../../resources/data/iso-3166-alpha2.txt'); + $this->alpha2 ??= CodeFile::load(__DIR__ . '/../../../resources/data/iso-3166-alpha2.txt'); return isset($this->alpha2[$code]); } public function isAlpha3(string $code): bool { - $this->alpha3 ??= CodeFile::load(__DIR__.'/../../../resources/data/iso-3166-alpha3.txt'); + $this->alpha3 ??= CodeFile::load(__DIR__ . '/../../../resources/data/iso-3166-alpha3.txt'); return isset($this->alpha3[$code]); } diff --git a/src/Support/I18n/BundledCurrencyDataset.php b/src/Support/I18n/BundledCurrencyDataset.php index 7e3fe88..905ef87 100644 --- a/src/Support/I18n/BundledCurrencyDataset.php +++ b/src/Support/I18n/BundledCurrencyDataset.php @@ -49,7 +49,7 @@ public function isNumericCode(string $code): bool public function isSymbol(string $symbol): bool { - $this->symbols ??= CodeFile::load(__DIR__.'/../../../resources/data/currency-symbols.txt'); + $this->symbols ??= CodeFile::load(__DIR__ . '/../../../resources/data/currency-symbols.txt'); return isset($this->symbols[$symbol]); } @@ -57,6 +57,6 @@ public function isSymbol(string $symbol): bool /** @return array */ private function codes(): array { - return $this->codes ??= CodeFile::loadMap(__DIR__.'/../../../resources/data/iso-4217.txt'); + return $this->codes ??= CodeFile::loadMap(__DIR__ . '/../../../resources/data/iso-4217.txt'); } } diff --git a/src/Support/I18n/BundledLanguageDataset.php b/src/Support/I18n/BundledLanguageDataset.php index c8790a0..8999c6f 100644 --- a/src/Support/I18n/BundledLanguageDataset.php +++ b/src/Support/I18n/BundledLanguageDataset.php @@ -18,7 +18,7 @@ final class BundledLanguageDataset implements LanguageDataset public function isAlpha2(string $code): bool { - $this->alpha2 ??= CodeFile::load(__DIR__.'/../../../resources/data/iso-639-1.txt'); + $this->alpha2 ??= CodeFile::load(__DIR__ . '/../../../resources/data/iso-639-1.txt'); return isset($this->alpha2[$code]); } diff --git a/src/Support/InlineTermList.php b/src/Support/InlineTermList.php index 32c9278..7610283 100644 --- a/src/Support/InlineTermList.php +++ b/src/Support/InlineTermList.php @@ -25,9 +25,9 @@ final readonly class InlineTermList implements TermList { /** - * @param list $terms Lowercase terms to match. - * @param list $allowed Words containing a term that are not one - * — the Scunthorpe problem. + * @param list $terms Lowercase terms to match. + * @param list $allowed Words containing a term that are not one + * — the Scunthorpe problem. */ public function __construct( private array $terms, diff --git a/src/Support/Payment/CardBrand.php b/src/Support/Payment/CardBrand.php index 5f39e17..c3fc227 100644 --- a/src/Support/Payment/CardBrand.php +++ b/src/Support/Payment/CardBrand.php @@ -22,10 +22,10 @@ final readonly class CardBrand { /** - * @param string $name Machine slug (`visa`), used in `brands:` restrictions. - * @param list $ranges Inclusive prefix intervals, equal-width bounds. - * @param list $lengths - * @param list $cvcLengths + * @param string $name Machine slug (`visa`), used in `brands:` restrictions. + * @param list $ranges Inclusive prefix intervals, equal-width bounds. + * @param list $lengths + * @param list $cvcLengths */ public function __construct( public string $name, diff --git a/src/Support/RuleAliases.php b/src/Support/RuleAliases.php index 6594f16..839a9c4 100644 --- a/src/Support/RuleAliases.php +++ b/src/Support/RuleAliases.php @@ -5,94 +5,94 @@ namespace Simtabi\Laranail\Validation\Support; use Closure; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Database\Eloquent\Model; +use Stringable; use InvalidArgumentException; -use Simtabi\Laranail\Validation\Contracts\TermList; -use Simtabi\Laranail\Validation\Rules\AntiSpam\Honeypot; -use Simtabi\Laranail\Validation\Rules\AntiSpam\SubmissionTiming; -use Simtabi\Laranail\Validation\Rules\Banking\Bic; -use Simtabi\Laranail\Validation\Rules\Banking\BsbNumber; -use Simtabi\Laranail\Validation\Rules\Banking\Iban; -use Simtabi\Laranail\Validation\Rules\Banking\Isin; -use Simtabi\Laranail\Validation\Rules\Banking\Luhn; -use Simtabi\Laranail\Validation\Rules\Chrono\DateInterval; -use Simtabi\Laranail\Validation\Rules\Chrono\MaxDateDifference; -use Simtabi\Laranail\Validation\Rules\Chrono\MinimumAge; -use Simtabi\Laranail\Validation\Rules\Chrono\MinuteIn; -use Simtabi\Laranail\Validation\Rules\Chrono\Rfc3339; -use Simtabi\Laranail\Validation\Rules\Chrono\TimeOfDay; -use Simtabi\Laranail\Validation\Rules\Chrono\TimezoneAbbreviation; -use Simtabi\Laranail\Validation\Rules\Chrono\UnixTimestamp; -use Simtabi\Laranail\Validation\Rules\Codes\Asin; +use Illuminate\Database\Eloquent\Model; +use Simtabi\Laranail\Validation\Rules\Net\Url; +use Simtabi\Laranail\Validation\Rules\Net\Cidr; use Simtabi\Laranail\Validation\Rules\Codes\Ean; +use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Simtabi\Laranail\Validation\Rules\Codes\Asin; use Simtabi\Laranail\Validation\Rules\Codes\Gtin; use Simtabi\Laranail\Validation\Rules\Codes\Isbn; use Simtabi\Laranail\Validation\Rules\Codes\Ismn; use Simtabi\Laranail\Validation\Rules\Codes\Issn; use Simtabi\Laranail\Validation\Rules\Codes\UpcE; +use Simtabi\Laranail\Validation\Rules\Geo\LatLng; +use Simtabi\Laranail\Validation\Rules\Markup\Xml; +use Simtabi\Laranail\Validation\Rules\Banking\Bic; +use Simtabi\Laranail\Validation\Rules\Geo\UsState; +use Illuminate\Contracts\Validation\ValidationRule; +use Simtabi\Laranail\Validation\Contracts\TermList; +use Simtabi\Laranail\Validation\Rules\Banking\Iban; +use Simtabi\Laranail\Validation\Rules\Banking\Isin; +use Simtabi\Laranail\Validation\Rules\Banking\Luhn; +use Simtabi\Laranail\Validation\Rules\Geo\Latitude; +use Simtabi\Laranail\Validation\Rules\Net\PublicIp; +use Simtabi\Laranail\Validation\Rules\Geo\Longitude; +use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; +use Simtabi\Laranail\Validation\Rules\Net\Subdomain; +use Simtabi\Laranail\Validation\Rules\Text\MaxWords; +use Simtabi\Laranail\Validation\Rules\Text\MinWords; +use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Rules\Chrono\Rfc3339; +use Simtabi\Laranail\Validation\Rules\Geo\CaProvince; +use Simtabi\Laranail\Validation\Rules\Net\DomainName; +use Simtabi\Laranail\Validation\Rules\Net\MacAddress; +use Simtabi\Laranail\Validation\Rules\Numbers\Parity; +use Simtabi\Laranail\Validation\Rules\Text\CaseStyle; +use Simtabi\Laranail\Validation\Rules\Text\HtmlClean; +use Simtabi\Laranail\Validation\Rules\Chrono\MinuteIn; use Simtabi\Laranail\Validation\Rules\Colour\CssColor; -use Simtabi\Laranail\Validation\Rules\Crypto\BitcoinAddress; -use Simtabi\Laranail\Validation\Rules\Crypto\EthereumAddress; -use Simtabi\Laranail\Validation\Rules\Database\Authorized; -use Simtabi\Laranail\Validation\Rules\Database\CompareToColumn; -use Simtabi\Laranail\Validation\Rules\Database\Comparison; -use Simtabi\Laranail\Validation\Rules\Database\ModelsExist; -use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIs; -use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; -use Simtabi\Laranail\Validation\Rules\Email\NoSubaddressing; -use Simtabi\Laranail\Validation\Rules\Email\NotDisposableEmail; -use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Encoding\Base64; -use Simtabi\Laranail\Validation\Rules\Encoding\Base64Image; +use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; +use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; +use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; +use Simtabi\Laranail\Validation\Rules\Payment\CardCvc; +use Simtabi\Laranail\Validation\Rules\Text\PersonName; +use Simtabi\Laranail\Validation\Rules\Text\Salutation; +use Simtabi\Laranail\Validation\Rules\Chrono\TimeOfDay; use Simtabi\Laranail\Validation\Rules\Encoding\DataUri; -use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; use Simtabi\Laranail\Validation\Rules\Fiscal\VatNumber; -use Simtabi\Laranail\Validation\Rules\Geo\CaProvince; -use Simtabi\Laranail\Validation\Rules\Geo\Latitude; -use Simtabi\Laranail\Validation\Rules\Geo\LatLng; -use Simtabi\Laranail\Validation\Rules\Geo\Longitude; -use Simtabi\Laranail\Validation\Rules\Geo\UsState; use Simtabi\Laranail\Validation\Rules\I18n\CountryCode; +use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; +use Simtabi\Laranail\Validation\Rules\Network\ImageUrl; +use Simtabi\Laranail\Validation\Rules\AntiSpam\Honeypot; +use Simtabi\Laranail\Validation\Rules\Banking\BsbNumber; +use Simtabi\Laranail\Validation\Rules\Chrono\MinimumAge; use Simtabi\Laranail\Validation\Rules\I18n\CurrencyCode; use Simtabi\Laranail\Validation\Rules\I18n\LanguageCode; -use Simtabi\Laranail\Validation\Rules\Identifiers\HashDigest; -use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; -use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; +use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; +use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; -use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; -use Simtabi\Laranail\Validation\Rules\Markup\Xml; -use Simtabi\Laranail\Validation\Rules\Net\Cidr; -use Simtabi\Laranail\Validation\Rules\Net\DomainName; -use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; -use Simtabi\Laranail\Validation\Rules\Net\MacAddress; -use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; -use Simtabi\Laranail\Validation\Rules\Net\PublicIp; -use Simtabi\Laranail\Validation\Rules\Net\Subdomain; -use Simtabi\Laranail\Validation\Rules\Net\Url; -use Simtabi\Laranail\Validation\Rules\Network\DeliverableEmail; -use Simtabi\Laranail\Validation\Rules\Network\HasGravatar; -use Simtabi\Laranail\Validation\Rules\Network\ImageUrl; -use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; -use Simtabi\Laranail\Validation\Rules\Numbers\Parity; -use Simtabi\Laranail\Validation\Rules\Payment\CardCvc; use Simtabi\Laranail\Validation\Rules\Payment\CardExpiry; use Simtabi\Laranail\Validation\Rules\Payment\CardNumber; -use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; -use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; -use Simtabi\Laranail\Validation\Rules\Storage\FileExistsOnDisk; -use Simtabi\Laranail\Validation\Rules\Structure\Delimited; -use Simtabi\Laranail\Validation\Rules\Text\CaseStyle; -use Simtabi\Laranail\Validation\Rules\Text\HtmlClean; -use Simtabi\Laranail\Validation\Rules\Text\MaxWords; -use Simtabi\Laranail\Validation\Rules\Text\MinWords; -use Simtabi\Laranail\Validation\Rules\Text\PersonName; -use Simtabi\Laranail\Validation\Rules\Text\Salutation; -use Simtabi\Laranail\Validation\Rules\Text\Slug; -use Simtabi\Laranail\Validation\Rules\Text\Username; use Simtabi\Laranail\Validation\Rules\Text\WithoutSpaces; +use Simtabi\Laranail\Validation\Rules\Chrono\DateInterval; +use Simtabi\Laranail\Validation\Rules\Database\Authorized; +use Simtabi\Laranail\Validation\Rules\Database\Comparison; +use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIs; +use Simtabi\Laranail\Validation\Rules\Network\HasGravatar; +use Simtabi\Laranail\Validation\Rules\Structure\Delimited; +use Simtabi\Laranail\Validation\Rules\Chrono\UnixTimestamp; +use Simtabi\Laranail\Validation\Rules\Database\ModelsExist; +use Simtabi\Laranail\Validation\Rules\Encoding\Base64Image; +use Simtabi\Laranail\Validation\Rules\Crypto\BitcoinAddress; +use Simtabi\Laranail\Validation\Rules\Email\NoSubaddressing; +use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; +use Simtabi\Laranail\Validation\Rules\Crypto\EthereumAddress; +use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; +use Simtabi\Laranail\Validation\Rules\Identifiers\HashDigest; +use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; use Simtabi\Laranail\Validation\Rules\Vendor\VendorIdentifier; -use Stringable; +use Simtabi\Laranail\Validation\Rules\Chrono\MaxDateDifference; +use Simtabi\Laranail\Validation\Rules\Database\CompareToColumn; +use Simtabi\Laranail\Validation\Rules\Email\NotDisposableEmail; +use Simtabi\Laranail\Validation\Rules\Network\DeliverableEmail; +use Simtabi\Laranail\Validation\Rules\Storage\FileExistsOnDisk; +use Simtabi\Laranail\Validation\Rules\AntiSpam\SubmissionTiming; +use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; +use Simtabi\Laranail\Validation\Rules\Chrono\TimezoneAbbreviation; /** * The alias suffix → rule-factory map behind the opt-in string rules. @@ -165,34 +165,34 @@ public static function map(): array { return [ // Banking - 'iban' => static fn (): ValidationRule => new Iban, - 'bic' => static fn (): ValidationRule => new Bic, - 'isin' => static fn (): ValidationRule => new Isin, - 'luhn' => static fn (): ValidationRule => new Luhn, + 'iban' => static fn (): ValidationRule => new Iban, + 'bic' => static fn (): ValidationRule => new Bic, + 'isin' => static fn (): ValidationRule => new Isin, + 'luhn' => static fn (): ValidationRule => new Luhn, 'bsb_number' => static fn (): ValidationRule => new BsbNumber, // Codes — the parameters narrow which lengths/editions are accepted. - 'ean' => static fn (): ValidationRule => new Ean, - 'issn' => static fn (): ValidationRule => new Issn, - 'isbn' => static fn (array $p): ValidationRule => new Isbn(self::ints($p)), - 'gtin' => static fn (array $p): ValidationRule => new Gtin(self::ints($p)), - 'asin' => static fn (): ValidationRule => new Asin, - 'ismn' => static fn (): ValidationRule => new Ismn, + 'ean' => static fn (): ValidationRule => new Ean, + 'issn' => static fn (): ValidationRule => new Issn, + 'isbn' => static fn (array $p): ValidationRule => new Isbn(self::ints($p)), + 'gtin' => static fn (array $p): ValidationRule => new Gtin(self::ints($p)), + 'asin' => static fn (): ValidationRule => new Asin, + 'ismn' => static fn (): ValidationRule => new Ismn, 'upc_e' => static fn (): ValidationRule => new UpcE, // Identifiers - 'imei' => static fn (): ValidationRule => new Imei, - 'jwt' => static fn (): ValidationRule => new Jwt, - 'semver' => static fn (): ValidationRule => new SemVer, - 'vin' => static fn (array $p): ValidationRule => new Vin(...self::flags($p)), + 'imei' => static fn (): ValidationRule => new Imei, + 'jwt' => static fn (): ValidationRule => new Jwt, + 'semver' => static fn (): ValidationRule => new SemVer, + 'vin' => static fn (array $p): ValidationRule => new Vin(...self::flags($p)), 'hash_digest' => static fn (array $p): ValidationRule => new HashDigest(self::str($p, 0)), // Geo - 'latitude' => static fn (): ValidationRule => new Latitude, - 'longitude' => static fn (): ValidationRule => new Longitude, - 'lat_lng' => static fn (): ValidationRule => new LatLng, + 'latitude' => static fn (): ValidationRule => new Latitude, + 'longitude' => static fn (): ValidationRule => new Longitude, + 'lat_lng' => static fn (): ValidationRule => new LatLng, 'ca_province' => static fn (): ValidationRule => new CaProvince, - 'us_state' => static fn (array $p): ValidationRule => new UsState(...self::flags($p)), + 'us_state' => static fn (array $p): ValidationRule => new UsState(...self::flags($p)), // Postal — `laranail_postal_code:US`, `:US,CA`, or `:@country` // to read the country from a sibling field. @@ -211,29 +211,29 @@ public static function map(): array 'file_exists_on_disk' => static fn (array $p): ValidationRule => new FileExistsOnDisk(self::str($p, 0), self::str($p, 1)), // Networking probes - 'image_url' => static fn (array $p): ValidationRule => new ImageUrl(self::strings($p)), + 'image_url' => static fn (array $p): ValidationRule => new ImageUrl(self::strings($p)), 'has_gravatar' => static fn (): ValidationRule => new HasGravatar, // Payment — `laranail_card_number:visa,mastercard` restricts // brands; `laranail_card_cvc:card_number` names the sibling // number field; `laranail_card_expiry:Europe/Berlin` the zone. 'card_number' => static fn (array $p): ValidationRule => new CardNumber(self::strings($p) === [] ? null : self::strings($p)), - 'card_cvc' => static fn (array $p): ValidationRule => new CardCvc(self::nullableStr($p, 0)), + 'card_cvc' => static fn (array $p): ValidationRule => new CardCvc(self::nullableStr($p, 0)), 'card_expiry' => static fn (array $p): ValidationRule => new CardExpiry(self::nullableStr($p, 0)), // Chrono 'rfc3339' => static fn (): ValidationRule => new Rfc3339, // `laranail_time_of_day:true` = 12-hour; second parameter is the // separator (`laranail_time_of_day:false,.` for `23.59`). - 'time_of_day' => static fn (array $p): ValidationRule => new TimeOfDay(self::flags($p)[0] ?? false, self::str($p, 1, ':')), + 'time_of_day' => static fn (array $p): ValidationRule => new TimeOfDay(self::flags($p)[0] ?? false, self::str($p, 1, ':')), 'unix_timestamp' => static fn (array $p): ValidationRule => new UnixTimestamp(...self::flags($p)), - 'date_interval' => static fn (array $p): ValidationRule => new DateInterval(...self::flags($p)), - 'minute_in' => static fn (array $p): ValidationRule => new MinuteIn(self::ints($p)), + 'date_interval' => static fn (array $p): ValidationRule => new DateInterval(...self::flags($p)), + 'minute_in' => static fn (array $p): ValidationRule => new MinuteIn(self::ints($p)), // `laranail_max_date_difference:48,@start_at` — hours, then the // reference (a date, or @field for a sibling). - 'max_date_difference' => static fn (array $p): ValidationRule => new MaxDateDifference((int) self::str($p, 0, '0'), self::str($p, 1)), + 'max_date_difference' => static fn (array $p): ValidationRule => new MaxDateDifference((int) self::str($p, 0, '0'), self::str($p, 1)), 'timezone_abbreviation' => static fn (): ValidationRule => new TimezoneAbbreviation, - 'minimum_age' => static fn (array $p): ValidationRule => new MinimumAge((int) self::str($p, 0, '0'), self::nullableStr($p, 1)), + 'minimum_age' => static fn (array $p): ValidationRule => new MinimumAge((int) self::str($p, 0, '0'), self::nullableStr($p, 1)), // Encoding — Base64Image and DataUri take lists and a byte cap, // which a flat rule string cannot spell honestly (see UNALIASED). @@ -242,32 +242,32 @@ public static function map(): array // I18n — positional flags mirror the constructors: // `laranail_country_code:true` is alpha-3 mode, // `laranail_currency_code:true` numeric, `:false,true` symbol. - 'country_code' => static fn (array $p): ValidationRule => new CountryCode(...self::flags($p)), + 'country_code' => static fn (array $p): ValidationRule => new CountryCode(...self::flags($p)), 'currency_code' => static fn (array $p): ValidationRule => new CurrencyCode(...self::flags($p)), 'language_code' => static fn (array $p): ValidationRule => new LanguageCode(...self::flags($p)), // Net - 'cidr' => static fn (): ValidationRule => new Cidr, - 'subdomain' => static fn (): ValidationRule => new Subdomain, - 'public_ip' => static fn (): ValidationRule => new PublicIp, - 'private_ip' => static fn (): ValidationRule => new PrivateIp, + 'cidr' => static fn (): ValidationRule => new Cidr, + 'subdomain' => static fn (): ValidationRule => new Subdomain, + 'public_ip' => static fn (): ValidationRule => new PublicIp, + 'private_ip' => static fn (): ValidationRule => new PrivateIp, 'domain_name' => static fn (array $p): ValidationRule => new DomainName(...self::flags($p)), 'mac_address' => static fn (): ValidationRule => new MacAddress, // The networks are the whole rule, so the parameters are the list: // `laranail_in_cidr_range:10.0.0.0/8,192.168.0.0/16`. - 'in_cidr_range' => static fn (array $p): ValidationRule => new InCidrRange(self::strings($p)), + 'in_cidr_range' => static fn (array $p): ValidationRule => new InCidrRange(self::strings($p)), 'no_subaddressing' => static fn (): ValidationRule => new NoSubaddressing, // Text 'slug' => static fn (): ValidationRule => new Slug, // `laranail_html_clean:true` inverts the rule: markup required. - 'html_clean' => static fn (array $p): ValidationRule => new HtmlClean(...self::flags($p)), - 'max_words' => static fn (array $p): ValidationRule => new MaxWords(...self::ints($p)), - 'min_words' => static fn (array $p): ValidationRule => new MinWords(...self::ints($p)), - 'salutation' => static fn (): ValidationRule => new Salutation, + 'html_clean' => static fn (array $p): ValidationRule => new HtmlClean(...self::flags($p)), + 'max_words' => static fn (array $p): ValidationRule => new MaxWords(...self::ints($p)), + 'min_words' => static fn (array $p): ValidationRule => new MinWords(...self::ints($p)), + 'salutation' => static fn (): ValidationRule => new Salutation, 'without_spaces' => static fn (): ValidationRule => new WithoutSpaces, - 'case_style' => static fn (array $p): ValidationRule => new CaseStyle(self::str($p, 0)), - 'person_name' => static fn (array $p): ValidationRule => new PersonName(...self::flags($p)), + 'case_style' => static fn (array $p): ValidationRule => new CaseStyle(self::str($p, 0)), + 'person_name' => static fn (array $p): ValidationRule => new PersonName(...self::flags($p)), // Spread rather than restate the defaults: hardcoding them here // let `laranail_username` drift from `new Username()` once already. 'username' => static fn (array $p): ValidationRule => new Username(...self::ints($p)), @@ -287,7 +287,7 @@ public static function map(): array 'vendor_identifier' => static fn (array $p): ValidationRule => new VendorIdentifier(self::str($p, 0)), // Numbers - 'parity' => static fn (array $p): ValidationRule => new Parity(self::str($p, 0)), + 'parity' => static fn (array $p): ValidationRule => new Parity(self::str($p, 0)), 'monetary_amount' => self::monetary(...), // Colour @@ -295,14 +295,14 @@ public static function map(): array // Crypto 'ethereum_address' => static fn (): ValidationRule => new EthereumAddress, - 'bitcoin_address' => static fn (array $p): ValidationRule => new BitcoinAddress(...self::flags($p)), + 'bitcoin_address' => static fn (array $p): ValidationRule => new BitcoinAddress(...self::flags($p)), // Email — the two list-backed rules resolve their list from the // container, so they need no parameter at all. 'not_disposable_email' => static fn (): ValidationRule => new NotDisposableEmail, - 'not_role_email' => static fn (): ValidationRule => new NotRoleEmail, - 'email_domain_is' => static fn (array $p): ValidationRule => new EmailDomainIs(self::strings($p)), - 'email_domain_is_not' => static fn (array $p): ValidationRule => new EmailDomainIsNot(self::strings($p)), + 'not_role_email' => static fn (): ValidationRule => new NotRoleEmail, + 'email_domain_is' => static fn (array $p): ValidationRule => new EmailDomainIs(self::strings($p)), + 'email_domain_is_not' => static fn (array $p): ValidationRule => new EmailDomainIsNot(self::strings($p)), // Network — the only tier that performs IO. Skipped entirely // during a precognitive request. @@ -310,7 +310,7 @@ public static function map(): array // Database 'models_exist' => static fn (array $p): ValidationRule => new ModelsExist(self::model($p, 0), self::nullableStr($p, 1)), - 'authorized' => static fn (array $p): ValidationRule => new Authorized(self::str($p, 0), self::model($p, 1), self::nullableStr($p, 2)), + 'authorized' => static fn (array $p): ValidationRule => new Authorized(self::str($p, 0), self::model($p, 1), self::nullableStr($p, 2)), ]; } @@ -323,7 +323,7 @@ public static function map(): array * country and a field called `country`. The sigil resolves it explicitly * rather than guessing from the shape of the string. * - * @param array $parameters + * @param array $parameters */ private static function postalCode(array $parameters): PostalCode { @@ -353,7 +353,8 @@ private static function postalCode(array $parameters): PostalCode * the name itself and the user gets a bare "class not found" that never * mentions the alias or the parameter that produced it. * - * @param array $parameters + * @param array $parameters + * * @return class-string */ private static function model(array $parameters, int $index): string @@ -376,7 +377,8 @@ private static function model(array $parameters, int $index): string * promise their shape. Non-scalars are dropped rather than coerced — * `(string) []` is a fatal, and a rule string cannot carry an array. * - * @param array $parameters + * @param array $parameters + * * @return list */ private static function strings(array $parameters): array @@ -411,7 +413,7 @@ private static function nullableStr(array $parameters, int $index): ?string * Spread rather than defaulted, so an omitted parameter leaves the rule's * own default in force. See {@see flags()} for why that matters. * - * @param array $parameters + * @param array $parameters */ private static function monetary(array $parameters): MonetaryAmount { @@ -442,7 +444,8 @@ private static function monetary(array $parameters): MonetaryAmount * rules: `laranail_username` capped at 30 against Username's 32, and * `laranail_vin` demanded a check digit that `new Vin()` does not. * - * @param array $parameters + * @param array $parameters + * * @return list */ private static function flags(array $parameters): array @@ -457,7 +460,8 @@ private static function flags(array $parameters): array } /** - * @param array $parameters + * @param array $parameters + * * @return list */ private static function ints(array $parameters): array diff --git a/src/Support/RuleRegistrar.php b/src/Support/RuleRegistrar.php index a317644..3d5e9bf 100644 --- a/src/Support/RuleRegistrar.php +++ b/src/Support/RuleRegistrar.php @@ -5,12 +5,12 @@ namespace Simtabi\Laranail\Validation\Support; use Closure; +use SplFileInfo; +use RecursiveIteratorIterator; +use RecursiveDirectoryIterator; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Validation\ValidationRule; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; use Simtabi\Laranail\Validation\Contracts\ClientCheckable; -use SplFileInfo; /** * The one registry of rule classes (§5.2.2) — the alias wiring, the console @@ -52,9 +52,9 @@ public function __construct(private readonly Container $container) {} /** * Register a consumer rule, optionally with a string alias. * - * @param class-string $rule - * @param Closure(list): ValidationRule|null $factory Builds the rule from - * string-rule parameters; required for an alias. + * @param class-string $rule + * @param Closure(list): ValidationRule|null $factory Builds the rule from + * string-rule parameters; required for an alias. */ public function register(string $rule, ?string $alias = null, ?Closure $factory = null): self { @@ -119,7 +119,7 @@ private function discover(): array return $this->discovered; } - $root = __DIR__.'/../Rules'; + $root = __DIR__ . '/../Rules'; $classes = []; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) { @@ -128,7 +128,7 @@ private function discover(): array } $relative = substr((string) realpath($file->getPathname()), strlen((string) realpath($root)) + 1, -4); - $class = 'Simtabi\\Laranail\\Validation\\Rules\\'.str_replace(DIRECTORY_SEPARATOR, '\\', $relative); + $class = 'Simtabi\\Laranail\\Validation\\Rules\\' . str_replace(DIRECTORY_SEPARATOR, '\\', $relative); if (class_exists($class) && is_subclass_of($class, ValidationRule::class)) { $classes[] = $class; diff --git a/src/Support/Text/DefaultReservedUsernameList.php b/src/Support/Text/DefaultReservedUsernameList.php index 00a91db..fbbc577 100644 --- a/src/Support/Text/DefaultReservedUsernameList.php +++ b/src/Support/Text/DefaultReservedUsernameList.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Support\Text; -use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; /** * The default binding: {@see Username}'s own 35-name floor, unchanged, so diff --git a/src/Support/Text/ExtendedReservedUsernameList.php b/src/Support/Text/ExtendedReservedUsernameList.php index 5b4b142..be46326 100644 --- a/src/Support/Text/ExtendedReservedUsernameList.php +++ b/src/Support/Text/ExtendedReservedUsernameList.php @@ -4,8 +4,8 @@ namespace Simtabi\Laranail\Validation\Support\Text; -use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; use Simtabi\Laranail\Validation\Support\I18n\CodeFile; +use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; /** * The full bundled reserved set: ~350 infrastructure, route and product @@ -25,7 +25,7 @@ final class ExtendedReservedUsernameList implements ReservedUsernameList public function names(): array { return $this->names ??= array_keys( - CodeFile::load(dirname(__DIR__, 3).'/resources/data/reserved-usernames.txt'), + CodeFile::load(dirname(__DIR__, 3) . '/resources/data/reserved-usernames.txt'), ); } } diff --git a/src/Testing/Arch/BansFieldRuleTypeMethods.php b/src/Testing/Arch/BansFieldRuleTypeMethods.php index 88d140d..63f5d56 100644 --- a/src/Testing/Arch/BansFieldRuleTypeMethods.php +++ b/src/Testing/Arch/BansFieldRuleTypeMethods.php @@ -4,23 +4,23 @@ namespace Simtabi\Laranail\Validation\Testing\Arch; -use FilesystemIterator; -use PhpParser\Error; +use SplFileInfo; use PhpParser\Node; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Identifier; +use PhpParser\Error; +use RuntimeException; +use FilesystemIterator; use PhpParser\Node\Name; use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor\NameResolver; -use PhpParser\NodeVisitorAbstract; use PhpParser\ParserFactory; -use RecursiveDirectoryIterator; +use PhpParser\Node\Identifier; use RecursiveIteratorIterator; -use RuntimeException; -use Simtabi\Laranail\Validation\Exceptions\TypedBuilderHint; +use RecursiveDirectoryIterator; +use PhpParser\NodeVisitorAbstract; +use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\StaticCall; +use PhpParser\NodeVisitor\NameResolver; use Simtabi\Laranail\Validation\FluentRule; -use SplFileInfo; +use Simtabi\Laranail\Validation\Exceptions\TypedBuilderHint; /** * Pest / PHPUnit arch helper that returns every PHP file under the given @@ -49,7 +49,7 @@ public static function scope(string ...$paths): array if (! class_exists(ParserFactory::class)) { throw new RuntimeException( 'BansFieldRuleTypeMethods requires nikic/php-parser ^5.0. ' - .'Add it to your dev dependencies: composer require --dev "nikic/php-parser:^5.0"', + . 'Add it to your dev dependencies: composer require --dev "nikic/php-parser:^5.0"', ); } @@ -61,8 +61,8 @@ public static function scope(string ...$paths): array $parser = new ParserFactory()->createForHostVersion(); } catch (\Error $error) { throw new RuntimeException('BansFieldRuleTypeMethods requires nikic/php-parser ^5.0 ' - .'(installed version is too old). ' - .'Upgrade: composer require --dev "nikic/php-parser:^5.0"', $error->getCode(), previous: $error); + . '(installed version is too old). ' + . 'Upgrade: composer require --dev "nikic/php-parser:^5.0"', $error->getCode(), previous: $error); } $banned = array_flip(TypedBuilderHint::knownMethods()); @@ -125,8 +125,8 @@ private static function phpFiles(string $path): iterable } /** - * @param array $ast - * @param array $banned + * @param array $ast + * @param array $banned */ private static function containsViolation(array $ast, array $banned): bool { diff --git a/src/Testing/FluentRulesTester.php b/src/Testing/FluentRulesTester.php index 24656d0..28583d0 100644 --- a/src/Testing/FluentRulesTester.php +++ b/src/Testing/FluentRulesTester.php @@ -5,31 +5,32 @@ namespace Simtabi\Laranail\Validation\Testing; use Closure; -use Illuminate\Auth\Access\AuthorizationException; -use Illuminate\Contracts\Auth\Authenticatable; -use Illuminate\Contracts\Auth\Factory; -use Illuminate\Contracts\Translation\Translator; -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Contracts\Validation\Validator as ValidatorContract; -use Illuminate\Foundation\Http\FormRequest; +use LogicException; +use Livewire\Livewire; +use Livewire\Component; +use ReflectionProperty; +use Illuminate\Support\Str; + +use function Livewire\store; + use Illuminate\Http\Request; +use PHPUnit\Framework\Assert; use Illuminate\Routing\Redirector; -use Illuminate\Support\Facades\Validator as ValidatorFacade; use Illuminate\Support\MessageBag; -use Illuminate\Support\Str; -use Illuminate\Validation\ValidationException; use Illuminate\Validation\Validator; -use Livewire\Component; -use Livewire\Features\SupportTesting\Testable; -use Livewire\Livewire; -use LogicException; -use PHPUnit\Framework\Assert; -use ReflectionProperty; -use Simtabi\Laranail\Validation\FluentValidator; +use Illuminate\Contracts\Auth\Factory; use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\Validated; - -use function Livewire\store; +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Validation\ValidationException; +use Livewire\Features\SupportTesting\Testable; +use Illuminate\Contracts\Translation\Translator; +use Simtabi\Laranail\Validation\FluentValidator; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Contracts\Validation\ValidationRule; +use Illuminate\Support\Facades\Validator as ValidatorFacade; +use Illuminate\Contracts\Validation\Validator as ValidatorContract; /** * Fluent tester for FluentRule chains, RuleSets, FormRequests, and FluentValidator @@ -71,8 +72,8 @@ final class FluentRulesTester private array $callQueue = []; /** - * @param class-string|class-string|class-string|RuleSet|ValidationRule|array $target - * @param list $constructorArgs + * @param class-string|class-string|class-string|RuleSet|ValidationRule|array $target + * @param list $constructorArgs */ private function __construct( private readonly mixed $target, @@ -82,8 +83,8 @@ private function __construct( /** * @api * - * @param class-string|class-string|class-string|RuleSet|ValidationRule|array $target - * @param mixed ...$constructorArgs Forwarded to FluentValidator subclass constructors after `$data`. Ignored for non-class targets. + * @param class-string|class-string|class-string|RuleSet|ValidationRule|array $target + * @param mixed ...$constructorArgs Forwarded to FluentValidator subclass constructors after `$data`. Ignored for non-class targets. */ public static function for(mixed $target, mixed ...$constructorArgs): self { @@ -93,7 +94,7 @@ public static function for(mixed $target, mixed ...$constructorArgs): self /** * @api * - * @param array $data + * @param array $data */ public function with(array $data): self { @@ -118,7 +119,7 @@ public function with(array $data): self * * @api * - * @param array $parameters + * @param array $parameters */ public function withRoute(array $parameters): self { @@ -158,7 +159,7 @@ public function actingAs(Authenticatable $user, ?string $guard = null): self * * @api * - * @param string|array $key + * @param string|array $key */ public function set(string|array $key, mixed $value = null): self { @@ -224,7 +225,7 @@ public function andCall(string $method, mixed ...$args): self * * @api * - * @param array $parameters + * @param array $parameters */ public function mount(array $parameters): self { @@ -259,7 +260,7 @@ public function passes(): self Assert::assertTrue( $result->passes(), 'Failed asserting that validation passes. Errors: ' - .$this->formatFirstErrors($result->errors()), + . $this->formatFirstErrors($result->errors()), ); return $this; @@ -273,7 +274,7 @@ public function fails(): self Assert::assertTrue( $result->fails(), 'Failed asserting that validation fails. Input data: ' - .$this->shortJson($this->data ?? []), + . $this->shortJson($this->data ?? []), ); return $this; @@ -301,7 +302,7 @@ public function failsWith(string $field, ?string $rule = null): self Assert::assertTrue( $result->errors()->has($field), "Expected error on field [{$field}]. Got errors on: [" - .implode(', ', $result->errors()->keys()).'].', + . implode(', ', $result->errors()->keys()) . '].', ); if ($rule !== null) { @@ -314,7 +315,7 @@ public function failsWith(string $field, ?string $rule = null): self $expected, $rulesForField, "Expected field [{$field}] to fail rule [{$expected}]. Failed rules: [" - .implode(', ', array_keys($rulesForField)).'].', + . implode(', ', array_keys($rulesForField)) . '].', ); } @@ -353,7 +354,7 @@ public function failsOnly(string $field, ?string $rule = null): self [$field], $keys, "Expected exactly [{$field}] to fail. Got errors on: [" - .implode(', ', $keys).'].', + . implode(', ', $keys) . '].', ); if ($rule !== null) { @@ -366,7 +367,7 @@ public function failsOnly(string $field, ?string $rule = null): self $expected, $rulesForField, "Expected field [{$field}] to fail rule [{$expected}]. Failed rules: [" - .implode(', ', array_keys($rulesForField)).'].', + . implode(', ', array_keys($rulesForField)) . '].', ); } @@ -395,7 +396,7 @@ public function doesNotFailOn(string ...$fields): self Assert::assertFalse( $errors->has($field), "Expected field [{$field}] to NOT have an error. Got errors on: [" - .implode(', ', $errors->keys()).'].', + . implode(', ', $errors->keys()) . '].', ); } @@ -429,7 +430,7 @@ public function failsWithAny(string $prefix): self return $this; } - $needle = $prefix.'.'; + $needle = $prefix . '.'; foreach ($result->errors()->keys() as $key) { if (str_starts_with($key, $needle)) { return $this; @@ -438,7 +439,7 @@ public function failsWithAny(string $prefix): self Assert::fail( "Expected an error matching [{$prefix}] or [{$prefix}.*]. Got errors on: [" - .implode(', ', $result->errors()->keys()).'].', + . implode(', ', $result->errors()->keys()) . '].', ); } @@ -453,7 +454,7 @@ public function failsWithAny(string $prefix): self * * @api * - * @param array $replacements + * @param array $replacements */ public function failsWithMessage(string $field, string $translationKey, array $replacements = []): self { @@ -464,7 +465,7 @@ public function failsWithMessage(string $field, string $translationKey, array $r Assert::assertTrue( $result->errors()->has($field), "Expected error on field [{$field}]. Got errors on: [" - .implode(', ', $result->errors()->keys()).'].', + . implode(', ', $result->errors()->keys()) . '].', ); $expected = $this->renderTranslation($translationKey, $replacements); @@ -567,8 +568,8 @@ private function isLivewireTarget(): bool * set container + redirector + user resolver, call validateResolved(). * Internals shift subtly across Laravel majors; CI matrix exercises it. * - * @param class-string $class - * @param array $data + * @param class-string $class + * @param array $data */ private function runFormRequest(string $class, array $data): Validated { @@ -610,7 +611,7 @@ private function runFormRequest(string $class, array $data): Validated * exposes the same surface (`parameter()` + `parameters()`) so FormRequests * that introspect routes during `authorize()` or `rules()` Just Work. * - * @param array $parameters + * @param array $parameters */ private function makeRouteShim(array $parameters): object { @@ -647,8 +648,8 @@ private function extractValidator(FormRequest $request): ValidatorContract } /** - * @param class-string $class - * @param array $data + * @param class-string $class + * @param array $data */ private function runFluentValidator(string $class, array $data): Validated { @@ -673,7 +674,7 @@ private function runFluentValidator(string $class, array $data): Validated * `$this->target` is a `class-string<\Livewire\Component>` at this point. * Asserted at runtime to satisfy PHPStan without inline `@var` overrides. * - * @param array $data + * @param array $data */ private function runLivewire(array $data): Validated { @@ -738,7 +739,7 @@ private function runLivewire(array $data): Validated * — in that case the caller falls back to a synthetic Validator so the * `Validated` DTO contract still holds. * - * @param Testable $component + * @param Testable $component */ private function extractLivewireValidator(Testable $component): ?ValidatorContract { diff --git a/src/Testing/PestExpectations.php b/src/Testing/PestExpectations.php index 0e97e4c..347b251 100644 --- a/src/Testing/PestExpectations.php +++ b/src/Testing/PestExpectations.php @@ -24,12 +24,12 @@ * `FluentRulesTester`. The internal narrowing helpers in this file are not. */ -use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Foundation\Http\FormRequest; use Pest\Expectation; use PHPUnit\Framework\Assert; -use Simtabi\Laranail\Validation\FluentValidator; use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Foundation\Http\FormRequest; +use Simtabi\Laranail\Validation\FluentValidator; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Testing\FluentRulesTester; if (! class_exists(Expectation::class)) { @@ -41,7 +41,8 @@ * the narrowers below to satisfy FluentRulesTester's stricter PHPStan types * without weakening the public API. * - * @param array $array + * @param array $array + * * @return array */ $assertStringKeyed = static function (array $array, string $context): array { @@ -77,7 +78,8 @@ }; /** - * @param array $data + * @param array $data + * * @return array */ $narrowData = static fn (array $data): array => $assertStringKeyed($data, 'Validation data'); diff --git a/src/Testing/ValidationFake.php b/src/Testing/ValidationFake.php index 43e517a..17e578f 100644 --- a/src/Testing/ValidationFake.php +++ b/src/Testing/ValidationFake.php @@ -5,11 +5,11 @@ namespace Simtabi\Laranail\Validation\Testing; use Closure; -use Illuminate\Support\Facades\Event; -use Illuminate\Support\MessageBag; use PHPUnit\Framework\Assert; -use Simtabi\Laranail\Validation\Events\ValidationCompleted; +use Illuminate\Support\MessageBag; +use Illuminate\Support\Facades\Event; use Simtabi\Laranail\Validation\Events\ValidationFailed; +use Simtabi\Laranail\Validation\Events\ValidationCompleted; /** * Records RuleSet runs for assertion — created via `Validation::fake()`. diff --git a/src/Validated.php b/src/Validated.php index 79be213..d22137f 100644 --- a/src/Validated.php +++ b/src/Validated.php @@ -4,10 +4,10 @@ namespace Simtabi\Laranail\Validation; -use Illuminate\Contracts\Validation\Validator as ValidatorContract; use Illuminate\Support\MessageBag; use Illuminate\Support\ValidatedInput; use Illuminate\Validation\ValidationException; +use Illuminate\Contracts\Validation\Validator as ValidatorContract; /** * Result of RuleSet::check(). Immutable view over validation outcome. @@ -24,7 +24,7 @@ final readonly class Validated { /** - * @param array $validated + * @param array $validated */ public function __construct( private bool $passes, diff --git a/src/ValueConditionalReducer.php b/src/ValueConditionalReducer.php index eb5a684..e811908 100644 --- a/src/ValueConditionalReducer.php +++ b/src/ValueConditionalReducer.php @@ -5,8 +5,8 @@ namespace Simtabi\Laranail\Validation; use Illuminate\Support\Arr; -use Simtabi\Laranail\Validation\Internal\AbstractConditionalReducer; use Simtabi\Laranail\Validation\Internal\ConditionalValueMatcher; +use Simtabi\Laranail\Validation\Internal\AbstractConditionalReducer; /** * Per-item pre-evaluation of Laravel's value-conditional rules @@ -44,9 +44,9 @@ final class ValueConditionalReducer extends AbstractConditionalReducer * @var array */ private const array REWRITE_TARGET = [ - 'required_if' => 'required', - 'required_unless' => 'required', - 'prohibited_if' => 'prohibited', + 'required_if' => 'required', + 'required_unless' => 'required', + 'prohibited_if' => 'prohibited', 'prohibited_unless' => 'prohibited', ]; @@ -58,9 +58,9 @@ final class ValueConditionalReducer extends AbstractConditionalReducer * can detect a `boolean` declaration on the dependent path and mirror * Laravel's `shouldConvertToBoolean`. * - * @param array $itemData - * @param array $itemMessages - * @param array $itemRules + * @param array $itemData + * @param array $itemMessages + * @param array $itemRules */ public static function apply(mixed $rule, string $field, array $itemData, array $itemMessages, array $itemRules): mixed { @@ -79,7 +79,7 @@ public static function apply(mixed $rule, string $field, array $itemData, array protected static function parse(string $rule): ?array { foreach (self::RULE_NAMES as $name) { - $prefix = $name.':'; + $prefix = $name . ':'; if (str_starts_with($rule, $prefix)) { return [$name, substr($rule, strlen($prefix))]; } @@ -93,9 +93,9 @@ protected static function parse(string $rule): ?array * collapse to plain `required` / `prohibited` when active and no custom * message overrides the original rule name, otherwise return unchanged. * - * @param array $itemData - * @param array $itemMessages - * @param array $itemRules + * @param array $itemData + * @param array $itemMessages + * @param array $itemRules */ private static function rewriteOne(string $rule, string $field, array $itemData, array $itemMessages, array $itemRules): ?string { @@ -133,9 +133,9 @@ private static function rewriteOne(string $rule, string $field, array $itemData, $match = ConditionalValueMatcher::matches($depPath, $rawValues, $itemData, $itemRules); $active = match ($ruleName) { - 'required_if', 'prohibited_if' => $match, + 'required_if', 'prohibited_if' => $match, 'required_unless', 'prohibited_unless' => ! $match, - default => false, + default => false, }; if (! $active) { diff --git a/src/WildcardExpander.php b/src/WildcardExpander.php index 5017a59..cfe23eb 100644 --- a/src/WildcardExpander.php +++ b/src/WildcardExpander.php @@ -16,7 +16,8 @@ class WildcardExpander * regex-matching every key (Laravel's O(n²) approach), this traverses * the data structure directly — O(n) where n = matching paths. * - * @param array $data + * @param array $data + * * @return list */ public static function expand(string $pattern, array $data): array @@ -29,9 +30,10 @@ public static function expand(string $pattern, array $data): array } /** - * @param list $segments - * @param list $path - * @param bool $afterWildcard Whether we've passed through a * segment + * @param list $segments + * @param list $path + * @param bool $afterWildcard Whether we've passed through a * segment + * * @return list */ private static function resolve(array $segments, mixed $current, array $path, bool $afterWildcard = false): array diff --git a/tests/AddRuleMessageParamTest.php b/tests/AddRuleMessageParamTest.php index 822a3fd..3428ca8 100644 --- a/tests/AddRuleMessageParamTest.php +++ b/tests/AddRuleMessageParamTest.php @@ -24,7 +24,7 @@ function exposeAddRule(): object protected ?string $compiledCache = null; /** - * @param array|string|object $rules + * @param array|string|object $rules */ public function callAddRule(array|string|object $rules, ?string $message = null): static { @@ -79,6 +79,6 @@ public function compiledRules(): string expect($rule->getCustomMessages())->toBe([ 'required' => 'Name required.', - 'min' => 'Too short.', + 'min' => 'Too short.', ]); }); diff --git a/tests/ArchTest.php b/tests/ArchTest.php index 998832d..007f7af 100644 --- a/tests/ArchTest.php +++ b/tests/ArchTest.php @@ -1,8 +1,8 @@ expect(['dd', 'dump', 'ray']) diff --git a/tests/ArrayRuleExtendHelpersTest.php b/tests/ArrayRuleExtendHelpersTest.php index 7343343..3399394 100644 --- a/tests/ArrayRuleExtendHelpersTest.php +++ b/tests/ArrayRuleExtendHelpersTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Exceptions\CannotExtendListShapedEach; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Phase 1 — ArrayRule::addEachRule / mergeEachRules. diff --git a/tests/BatchCollationParityTest.php b/tests/BatchCollationParityTest.php index cf33012..7882fff 100644 --- a/tests/BatchCollationParityTest.php +++ b/tests/BatchCollationParityTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); +use Illuminate\Validation\Rule; use Illuminate\Support\Facades\DB; +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentValidator; -use Simtabi\Laranail\Validation\RuleSet; /** * The batched presence check must agree with the database, not with PHP. @@ -89,7 +89,7 @@ function vanillaUniquePasses(string $email): bool 'exact' => 'alice@example.com', 'upper' => 'ALICE@EXAMPLE.COM', 'mixed' => 'Alice@Example.com', - 'free' => 'dave@example.com', + 'free' => 'dave@example.com', ]); it('agrees with the database on exists, for the same reason inverted', function (string $email): void { @@ -106,7 +106,7 @@ function vanillaUniquePasses(string $email): bool expect($batched)->toBe($vanilla); })->with([ - 'exact' => 'alice@example.com', - 'upper' => 'ALICE@EXAMPLE.COM', + 'exact' => 'alice@example.com', + 'upper' => 'ALICE@EXAMPLE.COM', 'absent' => 'nobody@example.com', ]); diff --git a/tests/BatchDatabaseValidationTest.php b/tests/BatchDatabaseValidationTest.php index 4c627fd..c947f5f 100644 --- a/tests/BatchDatabaseValidationTest.php +++ b/tests/BatchDatabaseValidationTest.php @@ -2,22 +2,22 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Request; +use Illuminate\Validation\Rule; use Illuminate\Routing\Redirector; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\DatabasePresenceVerifier; -use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\BatchDatabaseChecker; +use Illuminate\Foundation\Http\FormRequest; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Validation\Factory; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\HasFluentRules; +use Illuminate\Validation\DatabasePresenceVerifier; +use Simtabi\Laranail\Validation\BatchDatabaseChecker; use Simtabi\Laranail\Validation\PrecomputedPresenceVerifier; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Helper: set up in-memory SQLite for DB tests @@ -27,7 +27,7 @@ function setupTestDatabase(): void { config(['database.default' => 'testing']); config(['database.connections.testing' => [ - 'driver' => 'sqlite', + 'driver' => 'sqlite', 'database' => ':memory:', ]]); @@ -86,7 +86,7 @@ function setupTestDatabase(): void $validator = Validator::make( ['email' => 'unknown@example.com'], - ['email' => ['required', Rule::exists('users', 'email')]], + ['email' => ['required', Rule::exists('users', 'email')]], ['email.exists' => 'Custom exists message'], ); $validator->setPresenceVerifier($verifier); @@ -783,8 +783,8 @@ function setupTestDatabase(): void // ========================================================================= /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function createBatchFormRequest(array $rules, array $data): FormRequest { diff --git a/tests/BatchExistsUniqueSameFieldParityTest.php b/tests/BatchExistsUniqueSameFieldParityTest.php index 0bd55d7..32fc64d 100644 --- a/tests/BatchExistsUniqueSameFieldParityTest.php +++ b/tests/BatchExistsUniqueSameFieldParityTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); +use Illuminate\Validation\Rule; use Illuminate\Support\Facades\DB; +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\RuleSet; /** * One field carrying BOTH `exists` and `unique` against the same @@ -43,8 +43,8 @@ function setupExistsUniqueUsers(): void } /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function existsUniqueRuleSetVerdict(array $rules, array $data): bool { @@ -58,8 +58,8 @@ function existsUniqueRuleSetVerdict(array $rules, array $data): bool } /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function existsUniqueVanillaVerdict(array $rules, array $data): bool { diff --git a/tests/BatchSharedColumnParityTest.php b/tests/BatchSharedColumnParityTest.php index 3c04d8a..91884ba 100644 --- a/tests/BatchSharedColumnParityTest.php +++ b/tests/BatchSharedColumnParityTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); +use Illuminate\Validation\Rule; use Illuminate\Support\Facades\DB; +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\Rule; use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; /** * Two fields pointing at the same table and column. @@ -42,14 +42,15 @@ function setupSharedColumnUsers(): void } /** - * @param array $data + * @param array $data + * * @return array */ function vanillaSharedColumnErrors(array $data): array { return Validator::make($data, [ 'items.*.primary' => ['required', 'string', Rule::unique('testing.users', 'email')], - 'items.*.backup' => ['required', 'string', Rule::unique('testing.users', 'email')], + 'items.*.backup' => ['required', 'string', Rule::unique('testing.users', 'email')], ])->errors()->keys(); } @@ -70,7 +71,7 @@ function vanillaSharedColumnErrors(array $data): array expect(RuleSet::from($rules)->check($data)->fails())->toBeTrue(); })->with([ 'primary declared first' => true, - 'backup declared first' => false, + 'backup declared first' => false, ]); it('flags every taken value when both fields carry one', function (): void { @@ -80,7 +81,7 @@ function vanillaSharedColumnErrors(array $data): array $result = RuleSet::from(['items' => FluentRule::array()->required()->each([ 'primary' => FluentRule::string()->required()->unique('testing.users', 'email'), - 'backup' => FluentRule::string()->required()->unique('testing.users', 'email'), + 'backup' => FluentRule::string()->required()->unique('testing.users', 'email'), ])])->check($data); expect($result->errors()->keys())->toHaveSameSize(vanillaSharedColumnErrors($data)); @@ -92,7 +93,7 @@ function vanillaSharedColumnErrors(array $data): array $result = RuleSet::from(['items' => FluentRule::array()->required()->each([ 'primary' => FluentRule::string()->required()->unique('testing.users', 'email'), - 'backup' => FluentRule::string()->required()->unique('testing.users', 'email'), + 'backup' => FluentRule::string()->required()->unique('testing.users', 'email'), ])])->check(['items' => [['primary' => 'free@example.com', 'backup' => 'spare@example.com']]]); expect($result->passes())->toBeTrue(); diff --git a/tests/BatchValidationGuardsTest.php b/tests/BatchValidationGuardsTest.php index 2319e7c..6d502f0 100644 --- a/tests/BatchValidationGuardsTest.php +++ b/tests/BatchValidationGuardsTest.php @@ -2,21 +2,21 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Contracts\Validation\Validator; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Request; +use Illuminate\Validation\Rule; use Illuminate\Routing\Redirector; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Validation\Rule; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Http\FormRequest; +use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Validation\Factory; +use Illuminate\Contracts\Validation\Validator; use Illuminate\Validation\ValidationException; +use Simtabi\Laranail\Validation\HasFluentRules; use Simtabi\Laranail\Validation\BatchDatabaseChecker; use Simtabi\Laranail\Validation\Exceptions\BatchLimitExceededException; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\HasFluentRules; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Helper: set up in-memory SQLite with mixed integer + uuid tables @@ -26,7 +26,7 @@ function setupGuardsDatabase(): void { config(['database.default' => 'testing']); config(['database.connections.testing' => [ - 'driver' => 'sqlite', + 'driver' => 'sqlite', 'database' => ':memory:', ]]); @@ -44,8 +44,8 @@ function setupGuardsDatabase(): void } /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function createGuardsFormRequest(array $rules, array $data): FormRequest { @@ -372,11 +372,11 @@ public function __toString(): string $groups = [ 'widgets:uuid:exists' => [ - 'rule' => $existsRule, + 'rule' => $existsRule, 'values' => ['11111111-2222-3333-4444-555555555555'], ], 'widgets:uuid:unique' => [ - 'rule' => $uniqueRule, + 'rule' => $uniqueRule, 'values' => ['99999999-0000-0000-0000-000000000000'], ], ]; @@ -396,11 +396,11 @@ public function __toString(): string $groups = [ 'widgets:uuid:exists' => [ - 'rule' => $existsRule, + 'rule' => $existsRule, 'values' => ['11111111-2222-3333-4444-555555555555'], ], 'widgets:id:unique' => [ - 'rule' => $uniqueRule, + 'rule' => $uniqueRule, 'values' => ['1'], ], ]; @@ -889,7 +889,7 @@ public function __toString(): string $formRequest = createGuardsFormRequest( rules: [ - 'items' => 'required|array|max:5', + 'items' => 'required|array|max:5', 'items.*.id' => ['required', 'integer', Rule::exists('testing.widgets', 'id')], ], data: ['items' => $items], diff --git a/tests/Builder/EmailNodeRulesTest.php b/tests/Builder/EmailNodeRulesTest.php index eb571c2..09a5909 100644 --- a/tests/Builder/EmailNodeRulesTest.php +++ b/tests/Builder/EmailNodeRulesTest.php @@ -3,12 +3,12 @@ declare(strict_types=1); use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; use Simtabi\Laranail\Validation\FluentRule; +use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; +use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIs; use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; use Simtabi\Laranail\Validation\Rules\Email\NotDisposableEmail; -use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; /** * The email node's own rule methods. @@ -38,9 +38,9 @@ expect($matching)->toHaveCount(1); })->with([ 'notDisposable' => [fn (EmailRule $e): EmailRule => $e->notDisposable(), NotDisposableEmail::class], - 'notRole' => [fn (EmailRule $e): EmailRule => $e->notRole(), NotRoleEmail::class], - 'domainIs' => [fn (EmailRule $e): EmailRule => $e->domainIs(['example.com']), EmailDomainIs::class], - 'domainIsNot' => [fn (EmailRule $e): EmailRule => $e->domainIsNot(['spam.test']), EmailDomainIsNot::class], + 'notRole' => [fn (EmailRule $e): EmailRule => $e->notRole(), NotRoleEmail::class], + 'domainIs' => [fn (EmailRule $e): EmailRule => $e->domainIs(['example.com']), EmailDomainIs::class], + 'domainIsNot' => [fn (EmailRule $e): EmailRule => $e->domainIsNot(['spam.test']), EmailDomainIsNot::class], ]); it('accepts a single domain as a string', function (): void { diff --git a/tests/CheckAndFakeTest.php b/tests/CheckAndFakeTest.php index 6a67f26..922ca6c 100644 --- a/tests/CheckAndFakeTest.php +++ b/tests/CheckAndFakeTest.php @@ -3,11 +3,11 @@ declare(strict_types=1); use Illuminate\Support\MessageBag; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\Check; -use Simtabi\Laranail\Validation\Rules\Banking\Iban; use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\Validation; +use Illuminate\Validation\ValidationException; +use Simtabi\Laranail\Validation\Rules\Banking\Iban; /** * §6.10 ergonomics: Check for one-off boolean guards without building a @@ -82,7 +82,7 @@ it('toSchema() exports the wire schema in one call', function (): void { $schema = RuleSet::from([ 'email' => 'required|email|unique:users', - 'age' => 'nullable|integer|min:18', + 'age' => 'nullable|integer|min:18', ])->toSchema(attributes: ['email' => 'work email']); expect($schema['version'])->toBe(1) diff --git a/tests/ConditionalEvaluationPhaseTest.php b/tests/ConditionalEvaluationPhaseTest.php index 117ad57..f650f32 100644 --- a/tests/ConditionalEvaluationPhaseTest.php +++ b/tests/ConditionalEvaluationPhaseTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Internal\ConditionalEvaluationPhase; use Simtabi\Laranail\Validation\Internal\ConditionalVerdict; +use Simtabi\Laranail\Validation\Internal\ConditionalEvaluationPhase; /** * Direct unit coverage for {@see ConditionalEvaluationPhase}. Previously @@ -15,7 +15,7 @@ $rules = [ 'name' => [['exclude_unless', 'type', 'A', 'B'], 'string'], - 'age' => ['integer'], + 'age' => ['integer'], ]; expect($phase->indexConditionalAttrs($rules))->toBe([ @@ -44,7 +44,7 @@ $rules = [ 'name' => [['required_if', 'other', 'Y']], - 'age' => 'integer', + 'age' => 'integer', ]; expect($phase->indexConditionalAttrs($rules)) diff --git a/tests/ConditionalOptimizationTest.php b/tests/ConditionalOptimizationTest.php index a3f87c9..f49bcdb 100644 --- a/tests/ConditionalOptimizationTest.php +++ b/tests/ConditionalOptimizationTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Validation\ValidationException; /** * Tests for conditional pre-evaluation optimizations in RuleSet::validate(). @@ -23,7 +23,7 @@ it('does not error on excluded conditional fields in RuleSet::validate()', function (): void { // isbn is required, but excluded for dvd. Should pass without error. $validated = RuleSet::from([ - 'items' => 'required|array', + 'items' => 'required|array', 'items.*.type' => ['required', 'string', Rule::in(['book', 'dvd'])], 'items.*.isbn' => [['exclude_unless', 'items.*.type', 'book'], 'required', 'string', 'min:10'], ])->validate([ @@ -38,7 +38,7 @@ it('errors on surviving conditional fields that fail validation', function (): void { expect(fn () => RuleSet::from([ - 'items' => 'required|array', + 'items' => 'required|array', 'items.*.type' => ['required', 'string', Rule::in(['book', 'dvd'])], 'items.*.isbn' => [['exclude_unless', 'items.*.type', 'book'], 'required', 'string', 'min:10'], ])->validate([ @@ -51,8 +51,8 @@ it('handles exclude_if conditions correctly', function (): void { // price is excluded when type=free. Should pass. $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string'], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string'], 'items.*.price' => [['exclude_if', 'items.*.type', 'free'], 'required', 'numeric', 'min:1'], ])->validate([ 'items' => [ @@ -68,8 +68,8 @@ // style_top excluded unless type is button, text, or image. // frame type should pass even with invalid style_top. $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string', Rule::in(['button', 'text', 'image', 'frame'])], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string', Rule::in(['button', 'text', 'image', 'frame'])], 'items.*.style_top' => [['exclude_unless', 'items.*.type', 'button', 'text', 'image'], 'required', 'string', 'min:3'], ])->validate([ 'items' => [ @@ -89,13 +89,13 @@ it('validates 20 items with 2 types using dispatch table', function (): void { $items = array_map(fn (int $i): array => [ - 'type' => $i % 2 === 0 ? 'chapter' : 'button', + 'type' => $i % 2 === 0 ? 'chapter' : 'button', 'title' => "Item {$i}", ], range(1, 20)); $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], 'items.*.title' => [['exclude_unless', 'items.*.type', 'chapter'], 'required', 'string'], ])->validate(['items' => $items]); @@ -110,10 +110,10 @@ it('validates conditional fields with fast-checkable rules after stripping', function (): void { // After stripping exclude_unless, "boolean" and "nullable|string" are fast-checkable. $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], 'items.*.collapsed' => [['exclude_unless', 'items.*.type', 'chapter'], 'boolean'], - 'items.*.text' => [['exclude_unless', 'items.*.type', 'button'], 'nullable', 'string'], + 'items.*.text' => [['exclude_unless', 'items.*.type', 'button'], 'nullable', 'string'], ])->validate([ 'items' => [ ['type' => 'chapter', 'collapsed' => false, 'text' => null], @@ -128,8 +128,8 @@ it('fast-checks stringified In/NotIn objects', function (): void { $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], 'items.*.speed' => [['exclude_unless', 'items.*.type', 'button'], 'string', Rule::in(['slow', 'normal', 'fast'])], ])->validate([ 'items' => [ @@ -143,8 +143,8 @@ it('errors when stringified In rule fails on non-excluded item', function (): void { expect(fn () => RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], + 'items' => 'required|array', + 'items.*.type' => ['required', 'string', Rule::in(['chapter', 'button'])], 'items.*.speed' => [['exclude_unless', 'items.*.type', 'button'], 'string', Rule::in(['slow', 'normal', 'fast'])], ])->validate([ 'items' => [ @@ -159,9 +159,9 @@ it('handles mix of conditional and unconditional rules', function (): void { $validated = RuleSet::from([ - 'items' => 'required|array', - 'items.*.type' => ['required', 'string'], - 'items.*.name' => 'required|string|max:255', // unconditional + 'items' => 'required|array', + 'items.*.type' => ['required', 'string'], + 'items.*.name' => 'required|string|max:255', // unconditional 'items.*.chapters' => [['exclude_unless', 'items.*.type', 'chapter'], 'array'], ])->validate([ 'items' => [ @@ -176,9 +176,9 @@ it('handles items with no conditional rules', function (): void { $validated = RuleSet::from([ - 'items' => 'required|array', + 'items' => 'required|array', 'items.*.name' => 'required|string|max:255', - 'items.*.age' => 'required|numeric|min:0', + 'items.*.age' => 'required|numeric|min:0', ])->validate([ 'items' => [ ['name' => 'Alice', 'age' => 30], @@ -193,7 +193,7 @@ it('all items excluded for a conditional field still pass validation', function (): void { // All items are "dvd" — isbn excluded for all. required doesn't trigger. $validated = RuleSet::from([ - 'items' => 'required|array', + 'items' => 'required|array', 'items.*.type' => ['required', 'string'], 'items.*.isbn' => [['exclude_unless', 'items.*.type', 'book'], 'required', 'string'], ])->validate([ diff --git a/tests/ConsumerExtensionTest.php b/tests/ConsumerExtensionTest.php index fdec453..e0f3cc9 100644 --- a/tests/ConsumerExtensionTest.php +++ b/tests/ConsumerExtensionTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\Contracts\TermList; -use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; -use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; use Simtabi\Laranail\Validation\Tests\Fixtures\Consumer\AcmeServiceProvider; /** diff --git a/tests/DependentBooleanRuleRemovalParityTest.php b/tests/DependentBooleanRuleRemovalParityTest.php index 237dca3..73e7b34 100644 --- a/tests/DependentBooleanRuleRemovalParityTest.php +++ b/tests/DependentBooleanRuleRemovalParityTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentValidator; /** @@ -32,7 +32,7 @@ public function __construct(array $data) { parent::__construct($data, [ 'items.*.notify' => FluentRule::boolean(), - 'items.*.email' => FluentRule::string()->requiredIf('items.*.notify', 'true'), + 'items.*.email' => FluentRule::string()->requiredIf('items.*.notify', 'true'), ]); } } @@ -43,7 +43,7 @@ function vanillaWildcardFails(mixed $notify): bool ['items' => [['notify' => $notify]]], [ 'items.*.notify' => ['boolean'], - 'items.*.email' => ['required_if:items.*.notify,true', 'string'], + 'items.*.email' => ['required_if:items.*.notify,true', 'string'], ], )->fails(); } @@ -64,12 +64,12 @@ function optimizedWildcardFails(mixed $notify): bool // the fast-check phase, and not booleans, so the conversion mattered. expect(optimizedWildcardFails($notify))->toBe(vanillaWildcardFails($notify)); })->with([ - 'int one' => 1, - 'string one' => '1', - 'real true' => true, - 'int zero' => 0, + 'int one' => 1, + 'string one' => '1', + 'real true' => true, + 'int zero' => 0, 'string zero' => '0', - 'real false' => false, + 'real false' => false, ]); it('does not start requiring the field when the condition is genuinely unmet', function (): void { @@ -85,7 +85,7 @@ function optimizedWildcardFails(mixed $notify): bool $vanilla = Validator::make($data, [ 'items.*.notify' => ['string'], - 'items.*.email' => ['required_if:items.*.notify,true', 'string'], + 'items.*.email' => ['required_if:items.*.notify,true', 'string'], ]); expect($vanilla->fails())->toBeFalse(); diff --git a/tests/DocCountsTest.php b/tests/DocCountsTest.php index 0984943..131a224 100644 --- a/tests/DocCountsTest.php +++ b/tests/DocCountsTest.php @@ -16,7 +16,7 @@ /** @return list> */ function liveRuleClasses(): array { - $root = dirname(__DIR__).'/src/Rules'; + $root = dirname(__DIR__) . '/src/Rules'; $classes = []; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) { @@ -25,7 +25,7 @@ function liveRuleClasses(): array } $relative = substr($file->getPathname(), strlen($root) + 1, -4); - $class = 'Simtabi\\Laranail\\Validation\\Rules\\'.str_replace('/', '\\', $relative); + $class = 'Simtabi\\Laranail\\Validation\\Rules\\' . str_replace('/', '\\', $relative); if (class_exists($class) && is_subclass_of($class, ValidationRule::class)) { $classes[] = $class; @@ -39,7 +39,7 @@ function liveRuleClasses(): array it('states the real rule count in the CHANGELOG', function (): void { $actual = count(liveRuleClasses()); - $prose = (string) file_get_contents(dirname(__DIR__).'/CHANGELOG.md'); + $prose = (string) file_get_contents(dirname(__DIR__) . '/CHANGELOG.md'); preg_match_all('/(\d+) rules across/', $prose, $m); @@ -56,17 +56,17 @@ function liveRuleClasses(): array it('documents every rule family with a section in the rule reference', function (): void { // Directory name → the reference page's heading, where they differ. $headings = ['AntiSpam' => 'Anti-spam', 'Vendor' => 'Vendor identifiers']; - $reference = (string) file_get_contents(dirname(__DIR__).'/docs/tools/rule-library.md'); + $reference = (string) file_get_contents(dirname(__DIR__) . '/docs/tools/rule-library.md'); - $entries = scandir(dirname(__DIR__).'/src/Rules'); + $entries = scandir(dirname(__DIR__) . '/src/Rules'); $families = array_values(array_filter( $entries === false ? [] : $entries, static fn (string $entry): bool => ! str_starts_with($entry, '.') - && is_dir(dirname(__DIR__).'/src/Rules/'.$entry), + && is_dir(dirname(__DIR__) . '/src/Rules/' . $entry), )); foreach ($families as $family) { - $heading = '## '.($headings[$family] ?? $family); + $heading = '## ' . ($headings[$family] ?? $family); expect(str_contains($reference, "\n{$heading}\n"))->toBeTrue( "src/Rules/{$family} has no \"{$heading}\" section in docs/tools/rule-library.md — the whole family is undocumented.", @@ -75,7 +75,7 @@ function liveRuleClasses(): array }); it('names every ClientCheckable implementer in the CHANGELOG', function (): void { - $prose = (string) file_get_contents(dirname(__DIR__).'/CHANGELOG.md'); + $prose = (string) file_get_contents(dirname(__DIR__) . '/CHANGELOG.md'); $implementers = array_values(array_filter( liveRuleClasses(), diff --git a/tests/EmailRuleTest.php b/tests/EmailRuleTest.php index e555ae9..6ae9466 100644 --- a/tests/EmailRuleTest.php +++ b/tests/EmailRuleTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Contracts\Database\Query\Builder; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Validation\Rules\Email; use Illuminate\Validation\Rules\Exists; use Illuminate\Validation\Rules\Unique; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Database\Query\Builder; +use Illuminate\Contracts\Validation\ValidationRule; // ========================================================================= // EmailRule diff --git a/tests/Exceptions/UnknownFluentRuleMethodTest.php b/tests/Exceptions/UnknownFluentRuleMethodTest.php index 60acaea..412458f 100644 --- a/tests/Exceptions/UnknownFluentRuleMethodTest.php +++ b/tests/Exceptions/UnknownFluentRuleMethodTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; use Simtabi\Laranail\Validation\Exceptions\TypedBuilderHint; use Simtabi\Laranail\Validation\Exceptions\UnknownFluentRuleMethod; -use Simtabi\Laranail\Validation\FluentRule; afterEach(function (): void { FieldRule::flushMacros(); @@ -41,7 +41,7 @@ }); it('preserves registered macros after the override', function (): void { - FieldRule::macro('customMacro', fn (string $arg): string => 'macro:'.$arg); + FieldRule::macro('customMacro', fn (string $arg): string => 'macro:' . $arg); $builder = FluentRule::field(); @@ -63,7 +63,7 @@ { public function __invoke(string $arg): string { - return 'invokable:'.$arg; + return 'invokable:' . $arg; } }; diff --git a/tests/ExcludeBooleanDependentParityTest.php b/tests/ExcludeBooleanDependentParityTest.php index 69026b7..94e9323 100644 --- a/tests/ExcludeBooleanDependentParityTest.php +++ b/tests/ExcludeBooleanDependentParityTest.php @@ -30,7 +30,7 @@ public function __construct(array $data) { parent::__construct($data, [ 'notify' => FluentRule::boolean(), - 'email' => FluentRule::string()->excludeUnless('notify', 'true')->required(), + 'email' => FluentRule::string()->excludeUnless('notify', 'true')->required(), ]); } } @@ -42,7 +42,7 @@ public function __construct(array $data) { parent::__construct($data, [ 'notify' => FluentRule::boolean(), - 'email' => FluentRule::string()->excludeIf('notify', 'true')->required(), + 'email' => FluentRule::string()->excludeIf('notify', 'true')->required(), ]); } } @@ -63,12 +63,12 @@ function vanillaExcluding(string $rule, mixed $notify): array expect(array_key_exists('email', $optimized))->toBe(array_key_exists('email', $vanilla)); })->with([ - 'int one' => 1, - 'string one' => '1', - 'real true' => true, - 'int zero' => 0, + 'int one' => 1, + 'string one' => '1', + 'real true' => true, + 'int zero' => 0, 'string zero' => '0', - 'real false' => false, + 'real false' => false, ]); it('keeps exclude_if in step with Laravel for the same values', function (mixed $notify): void { @@ -77,12 +77,12 @@ function vanillaExcluding(string $rule, mixed $notify): array expect(array_key_exists('email', $optimized))->toBe(array_key_exists('email', $vanilla)); })->with([ - 'int one' => 1, - 'string one' => '1', - 'real true' => true, - 'int zero' => 0, + 'int one' => 1, + 'string one' => '1', + 'real true' => true, + 'int zero' => 0, 'string zero' => '0', - 'real false' => false, + 'real false' => false, ]); it('still excludes when the dependent is not declared boolean', function (): void { @@ -93,7 +93,7 @@ function vanillaExcluding(string $rule, mixed $notify): array $vanilla = Validator::make($data, [ 'notify' => ['string'], - 'email' => ['exclude_unless:notify,true', 'required', 'string'], + 'email' => ['exclude_unless:notify,true', 'required', 'string'], ])->validate(); expect($vanilla)->not->toHaveKey('email'); diff --git a/tests/ExcludeCoercionParityTest.php b/tests/ExcludeCoercionParityTest.php index 31b844b..b28c455 100644 --- a/tests/ExcludeCoercionParityTest.php +++ b/tests/ExcludeCoercionParityTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\FluentValidator; use Simtabi\Laranail\Validation\Internal\ConditionalValueMatcher; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // exclude_unless / exclude_if dependent-value coercion in RuleSet's per-item @@ -16,9 +16,9 @@ // ========================================================================= /** - * @param array $native - * @param array $fluent - * @param array $data + * @param array $native + * @param array $fluent + * @param array $data */ function assertExcludeParity(array $native, array $fluent, array $data): void { @@ -34,7 +34,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.detail' => ['exclude_unless:items.*.state,null', 'required', 'string']], ['items.*.detail' => [['exclude_unless', 'items.*.state', 'null'], 'required', 'string']], - ['items' => [['state' => null]]], // state IS null → not excluded → required fires + ['items' => [['state' => null]]], // state IS null → not excluded → required fires ); }); @@ -42,7 +42,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.detail' => ['exclude_if:items.*.state,null', 'required', 'string']], ['items.*.detail' => [['exclude_if', 'items.*.state', 'null'], 'required', 'string']], - ['items' => [['state' => null]]], // state IS null → excluded → no error + ['items' => [['state' => null]]], // state IS null → excluded → no error ); }); @@ -78,7 +78,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.x' => ['exclude_unless:items.*.n,1', 'required', 'string']], ['items.*.x' => [['exclude_unless', 'items.*.n', '1'], 'required', 'string']], - ['items' => [['n' => 1]]], // int 1 ↔ '1' loose match → not excluded → required fires + ['items' => [['n' => 1]]], // int 1 ↔ '1' loose match → not excluded → required fires ); }); @@ -145,7 +145,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.name' => ['exclude_if:items.*.flag,null', 'required', 'string']], ['items.*.name' => [['exclude_if', 'items.*.flag', 'null'], 'required', 'string']], - ['items' => [['other' => 'x']]], // flag absent + ['items' => [['other' => 'x']]], // flag absent ); }); @@ -153,7 +153,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.name' => ['exclude_if:items.*.flag,null', 'required', 'string']], ['items.*.name' => [['exclude_if', 'items.*.flag', 'null'], 'required', 'string']], - ['items' => [['flag' => null]]], // flag present and null → excluded → no error + ['items' => [['flag' => null]]], // flag present and null → excluded → no error ); }); @@ -195,7 +195,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.x' => ['exclude_unless:items.*.type,a', 'exclude_if:items.*.other,z', 'required', 'string']], ['items.*.x' => [['exclude_unless', 'items.*.type', 'a'], ['exclude_if', 'items.*.other', 'z'], 'required', 'string']], - ['items' => [['type' => 'a', 'other' => 'z']]], + ['items' => [['type' => 'a', 'other' => 'z']]], ); }); @@ -203,7 +203,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.x' => ['exclude_unless:items.*.type,a', 'exclude_if:items.*.other,z', 'required', 'string']], ['items.*.x' => [['exclude_unless', 'items.*.type', 'a'], ['exclude_if', 'items.*.other', 'z'], 'required', 'string']], - ['items' => [['type' => 'a', 'other' => 'keep']]], // type=a (unless ok), other≠z → kept → required fires + ['items' => [['type' => 'a', 'other' => 'keep']]], // type=a (unless ok), other≠z → kept → required fires ); }); @@ -216,7 +216,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.x' => ['exclude_unless:items.*.type,a', 'required_if:items.*.other,y', 'string']], ['items.*.x' => [['exclude_unless', 'items.*.type', 'a'], ['required_if', 'items.*.other', 'y'], 'string']], - ['items' => [['type' => 'a', 'other' => 'y']]], // x missing + ['items' => [['type' => 'a', 'other' => 'y']]], // x missing ); }); @@ -224,7 +224,7 @@ function assertExcludeParity(array $native, array $fluent, array $data): void assertExcludeParity( ['items.*.x' => ['exclude_unless:items.*.type,a', 'required_unless:items.*.other,skip', 'string']], ['items.*.x' => [['exclude_unless', 'items.*.type', 'a'], ['required_unless', 'items.*.other', 'skip'], 'string']], - ['items' => [['type' => 'a', 'other' => 'go']]], // other≠skip → x required, missing → error + ['items' => [['type' => 'a', 'other' => 'go']]], // other≠skip → x required, missing → error ); }); diff --git a/tests/ExcludeStringFormPruningTest.php b/tests/ExcludeStringFormPruningTest.php index 8c51403..22e411b 100644 --- a/tests/ExcludeStringFormPruningTest.php +++ b/tests/ExcludeStringFormPruningTest.php @@ -24,8 +24,8 @@ enum PruneType: string } /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function makePruneValidator(array $rules, array $data): FluentValidator { @@ -43,14 +43,14 @@ function pauseItems(int $n): array $tuple = makePruneValidator([ 'items.*.type' => ['required', 'string'], - 'items.*.a' => [['exclude_unless', 'items.*.type', 'chapter'], 'required', 'string'], - 'items.*.b' => [['exclude_unless', 'items.*.type', 'chapter'], 'required', 'string'], + 'items.*.a' => [['exclude_unless', 'items.*.type', 'chapter'], 'required', 'string'], + 'items.*.b' => [['exclude_unless', 'items.*.type', 'chapter'], 'required', 'string'], ], $data); $fluent = makePruneValidator([ 'items.*.type' => ['required', 'string'], - 'items.*.a' => FluentRule::field()->excludeUnless('items.*.type', 'chapter')->required()->rule('string'), - 'items.*.b' => FluentRule::field()->excludeUnless('items.*.type', 'chapter')->required()->rule('string'), + 'items.*.a' => FluentRule::field()->excludeUnless('items.*.type', 'chapter')->required()->rule('string'), + 'items.*.b' => FluentRule::field()->excludeUnless('items.*.type', 'chapter')->required()->rule('string'), ], $data); // All 'pause' items exclude a + b → only the 10 `type` rules remain. @@ -62,9 +62,9 @@ function pauseItems(int $n): array $data = pauseItems(10); $validator = makePruneValidator([ - 'items.*.type' => ['required', 'string'], + 'items.*.type' => ['required', 'string'], 'items.*.style' => FluentRule::array()->children([ - 'top' => FluentRule::string()->excludeUnless('items.*.type', 'chapter')->required(), + 'top' => FluentRule::string()->excludeUnless('items.*.type', 'chapter')->required(), 'left' => FluentRule::string()->excludeUnless('items.*.type', 'chapter')->required(), ]), ], $data); @@ -85,7 +85,7 @@ function pauseItems(int $n): array $validator = makePruneValidator([ 'items.*.type' => ['required', 'string'], - 'items.*.a' => FluentRule::field()->excludeUnless('items.*.type', PruneType::CHAPTER, PruneType::MENU)->required()->rule('string'), + 'items.*.a' => FluentRule::field()->excludeUnless('items.*.type', PruneType::CHAPTER, PruneType::MENU)->required()->rule('string'), ], $data); // enum cases serialize to their ->value ('chapter','menu'); 'pause' is in diff --git a/tests/ExpandsWildcardsTest.php b/tests/ExpandsWildcardsTest.php index b2b3ffc..ebd15fc 100644 --- a/tests/ExpandsWildcardsTest.php +++ b/tests/ExpandsWildcardsTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\Factory; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Validation\Factory; +use Illuminate\Validation\ValidationException; // ========================================================================= // Unit: createDefaultValidator() directly @@ -64,7 +64,7 @@ $formRequest = createFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2), + 'name' => FluentRule::string()->required()->min(2), 'email' => FluentRule::string()->required()->rule('email'), ]), ], diff --git a/tests/FastCheck/FamilyCompilerTest.php b/tests/FastCheck/FamilyCompilerTest.php index f020552..da51430 100644 --- a/tests/FastCheck/FamilyCompilerTest.php +++ b/tests/FastCheck/FamilyCompilerTest.php @@ -6,9 +6,9 @@ use Closure; use Simtabi\Laranail\Validation\FastCheck\CoreValueCompiler; +use Simtabi\Laranail\Validation\FastCheck\ProhibitedCompiler; use Simtabi\Laranail\Validation\FastCheck\ItemContextCompiler; use Simtabi\Laranail\Validation\FastCheck\PresenceConditionalCompiler; -use Simtabi\Laranail\Validation\FastCheck\ProhibitedCompiler; it('CoreValueCompiler returns null for rule strings it does not handle', function (): void { expect(CoreValueCompiler::compile('prohibited'))->toBeNull() diff --git a/tests/FastCheckArrayValueParityTest.php b/tests/FastCheckArrayValueParityTest.php index d36a97e..2ccc397 100644 --- a/tests/FastCheckArrayValueParityTest.php +++ b/tests/FastCheckArrayValueParityTest.php @@ -33,8 +33,8 @@ final class ArrayValueParityValidator extends FluentValidator { /** - * @param array $data - * @param array $rules + * @param array $data + * @param array $rules */ public function __construct(array $data, array $rules) { diff --git a/tests/FastCheckCompilerCacheTest.php b/tests/FastCheckCompilerCacheTest.php index a62b499..c5d81ef 100644 --- a/tests/FastCheckCompilerCacheTest.php +++ b/tests/FastCheckCompilerCacheTest.php @@ -39,12 +39,12 @@ ->and($second)->toBeInstanceOf(Closure::class) ->and($second)->not->toBe($first); })->with([ - 'after:today' => ['required|date|after:today'], - 'before:now' => ['required|date|before:now'], + 'after:today' => ['required|date|after:today'], + 'before:now' => ['required|date|before:now'], 'after_or_equal:tomorrow' => ['required|date|after_or_equal:tomorrow'], 'before_or_equal:+1 week' => ['required|date|before_or_equal:+1 week'], - 'date_equals:today' => ['required|date|date_equals:today'], - 'absolute date literal' => ['required|date|after:2030-01-01'], + 'date_equals:today' => ['required|date|date_equals:today'], + 'absolute date literal' => ['required|date|after:2030-01-01'], ]); /** @@ -58,7 +58,7 @@ // count >= MAX is checked BEFORE insert: at the 1024th call, // pre-insert count = 1023 (no reset), post-insert count = 1024. for ($i = 1; $i <= 1024; $i++) { - FastCheckCompiler::compile('required|string|max:'.$i); + FastCheckCompiler::compile('required|string|max:' . $i); } // Last entry must still be cached. @@ -74,7 +74,7 @@ // Fill to 1024 (rule #1 already counts as one of those). for ($i = 2; $i <= 1024; $i++) { - FastCheckCompiler::compile('required|string|max:'.$i); + FastCheckCompiler::compile('required|string|max:' . $i); } // The 1025th distinct compile triggers the cap reset before insert. diff --git a/tests/FastCheckParityTest.php b/tests/FastCheckParityTest.php index 5ec3703..d99f823 100644 --- a/tests/FastCheckParityTest.php +++ b/tests/FastCheckParityTest.php @@ -218,15 +218,15 @@ function itemAwareDateParityGrid(): iterable ]; $items = [ - 'both-valid-after' => ['value' => '2030-06-05', 'start_date' => '2030-06-01'], - 'both-valid-before' => ['value' => '2030-05-15', 'start_date' => '2030-06-01'], - 'both-valid-equal' => ['value' => '2030-06-01', 'start_date' => '2030-06-01'], + 'both-valid-after' => ['value' => '2030-06-05', 'start_date' => '2030-06-01'], + 'both-valid-before' => ['value' => '2030-05-15', 'start_date' => '2030-06-01'], + 'both-valid-equal' => ['value' => '2030-06-01', 'start_date' => '2030-06-01'], 'value-invalid-date' => ['value' => 'not-a-date', 'start_date' => '2030-06-01'], - 'ref-invalid-date' => ['value' => '2030-06-01', 'start_date' => 'not-a-date'], - 'value-null' => ['value' => null, 'start_date' => '2030-06-01'], - 'value-empty' => ['value' => '', 'start_date' => '2030-06-01'], - 'ref-null' => ['value' => '2030-06-01', 'start_date' => null], - 'ref-missing' => ['value' => '2030-06-01'], + 'ref-invalid-date' => ['value' => '2030-06-01', 'start_date' => 'not-a-date'], + 'value-null' => ['value' => null, 'start_date' => '2030-06-01'], + 'value-empty' => ['value' => '', 'start_date' => '2030-06-01'], + 'ref-null' => ['value' => '2030-06-01', 'start_date' => null], + 'ref-missing' => ['value' => '2030-06-01'], ]; foreach ($rules as $rule) { @@ -290,17 +290,17 @@ function itemAwareSameDifferentParityGrid(): iterable ]; $items = [ - 'equal-strings' => ['value' => 'foo', 'other' => 'foo'], - 'different-strings' => ['value' => 'foo', 'other' => 'bar'], - 'equal-ints' => ['value' => 7, 'other' => 7], - 'int-vs-string' => ['value' => 1, 'other' => '1'], - 'string-vs-int' => ['value' => '1', 'other' => 1], - 'both-null' => ['value' => null, 'other' => null], + 'equal-strings' => ['value' => 'foo', 'other' => 'foo'], + 'different-strings' => ['value' => 'foo', 'other' => 'bar'], + 'equal-ints' => ['value' => 7, 'other' => 7], + 'int-vs-string' => ['value' => 1, 'other' => '1'], + 'string-vs-int' => ['value' => '1', 'other' => 1], + 'both-null' => ['value' => null, 'other' => null], 'value-null-other-string' => ['value' => null, 'other' => 'foo'], 'value-string-other-null' => ['value' => 'foo', 'other' => null], - 'value-empty' => ['value' => '', 'other' => 'foo'], - 'other-missing' => ['value' => 'foo'], - 'value-and-other-empty' => ['value' => '', 'other' => ''], + 'value-empty' => ['value' => '', 'other' => 'foo'], + 'other-missing' => ['value' => 'foo'], + 'value-and-other-empty' => ['value' => '', 'other' => ''], ]; foreach ($rules as $rule) { @@ -361,10 +361,10 @@ function itemAwareSameDifferentParityGrid(): iterable expect(FastCheckCompiler::compileWithItemContext($rule))->toBeNull(); })->with([ 'plain string rule' => ['required|string|max:255'], - 'numeric rule' => ['required|numeric|min:0'], - 'email rule' => ['nullable|email'], - 'in-list rule' => ['required|in:a,b,c'], - 'regex rule' => ['required|regex:/^[a-z]+$/'], + 'numeric rule' => ['required|numeric|min:0'], + 'email rule' => ['nullable|email'], + 'in-list rule' => ['required|in:a,b,c'], + 'regex rule' => ['required|regex:/^[a-z]+$/'], 'integer with size' => ['required|integer|min:1|max:100'], ]); @@ -489,21 +489,21 @@ function itemAwarePresenceConditionalParityGrid(): iterable $items = [ // Basic presence shapes. - 'a-present-target-ok' => ['value' => 'label', 'a' => 'x'], + 'a-present-target-ok' => ['value' => 'label', 'a' => 'x'], 'a-present-target-missing' => ['value' => null, 'a' => 'x', 'b' => 'y'], - 'a-absent-target-missing' => ['value' => null], + 'a-absent-target-missing' => ['value' => null], // Whitespace-only sibling (Laravel treats as absent). - 'a-whitespace-target-ok' => ['value' => 'label', 'a' => ' '], + 'a-whitespace-target-ok' => ['value' => 'label', 'a' => ' '], 'a-whitespace-target-missing' => ['value' => null, 'a' => ' '], // Whitespace-only target with sibling present. 'a-present-target-whitespace' => ['value' => ' ', 'a' => 'x'], // Multi-param for _all variants. - 'ab-present-target-ok' => ['value' => 'label', 'a' => 'x', 'b' => 'y'], + 'ab-present-target-ok' => ['value' => 'label', 'a' => 'x', 'b' => 'y'], 'ab-one-missing-target-missing' => ['value' => null, 'a' => 'x'], - 'ab-whitespace-target-missing' => ['value' => null, 'a' => ' ', 'b' => ' '], + 'ab-whitespace-target-missing' => ['value' => null, 'a' => ' ', 'b' => ' '], // Empty array sibling (Laravel treats as absent). 'a-empty-array-target-missing' => ['value' => null, 'a' => []], @@ -692,26 +692,26 @@ function itemAwareSizeComparisonParityGrid(): iterable $items = [ // Numeric shapes - 'num:5-vs-3' => ['value' => 5, 'other' => 3], - 'num:3-vs-5' => ['value' => 3, 'other' => 5], - 'num:5-vs-5' => ['value' => 5, 'other' => 5], - 'num:5-vs-str5' => ['value' => 5, 'other' => '5'], - 'num:5-vs-abc' => ['value' => 5, 'other' => 'abc'], // ref falls through to mb_strlen - 'num:5-vs-null' => ['value' => 5, 'other' => null], + 'num:5-vs-3' => ['value' => 5, 'other' => 3], + 'num:3-vs-5' => ['value' => 3, 'other' => 5], + 'num:5-vs-5' => ['value' => 5, 'other' => 5], + 'num:5-vs-str5' => ['value' => 5, 'other' => '5'], + 'num:5-vs-abc' => ['value' => 5, 'other' => 'abc'], // ref falls through to mb_strlen + 'num:5-vs-null' => ['value' => 5, 'other' => null], 'num:5-vs-missing' => ['value' => 5], // String shapes (lengths) - 'str:hello-vs-hi' => ['value' => 'hello', 'other' => 'hi'], // 5 vs 2 - 'str:hi-vs-hello' => ['value' => 'hi', 'other' => 'hello'], // 2 vs 5 + 'str:hello-vs-hi' => ['value' => 'hello', 'other' => 'hi'], // 5 vs 2 + 'str:hi-vs-hello' => ['value' => 'hi', 'other' => 'hello'], // 2 vs 5 'str:same-vs-same' => ['value' => 'same', 'other' => 'same'], // 4 vs 4 - 'str:hi-vs-null' => ['value' => 'hi', 'other' => null], // 2 vs 0 - 'str:hi-vs-array' => ['value' => 'hi', 'other' => ['a', 'b']], // 2 vs count=2 + 'str:hi-vs-null' => ['value' => 'hi', 'other' => null], // 2 vs 0 + 'str:hi-vs-array' => ['value' => 'hi', 'other' => ['a', 'b']], // 2 vs count=2 // Array shapes (counts) - 'arr:3-vs-1' => ['value' => [1, 2, 3], 'other' => [1]], + 'arr:3-vs-1' => ['value' => [1, 2, 3], 'other' => [1]], 'arr:empty-vs-1' => ['value' => [], 'other' => [1]], - 'arr:1-vs-1' => ['value' => [1], 'other' => [1]], - 'arr:2-vs-str3' => ['value' => [1, 2], 'other' => 'abc'], // count 2 vs mb_strlen 3 + 'arr:1-vs-1' => ['value' => [1], 'other' => [1]], + 'arr:2-vs-str3' => ['value' => [1, 2], 'other' => 'abc'], // count 2 vs mb_strlen 3 // Nullable-value-null (should skip on nullable + non-implicit). 'null-value' => ['value' => null, 'other' => 3], diff --git a/tests/FieldRuleExtendHelpersTest.php b/tests/FieldRuleExtendHelpersTest.php index bc42f8c..45a6450 100644 --- a/tests/FieldRuleExtendHelpersTest.php +++ b/tests/FieldRuleExtendHelpersTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Phase 2 — FieldRule::addChildRule / mergeChildRules. diff --git a/tests/FluentFormRequestTest.php b/tests/FluentFormRequestTest.php index 005eca3..e27b425 100644 --- a/tests/FluentFormRequestTest.php +++ b/tests/FluentFormRequestTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -use Illuminate\Contracts\Translation\Translator; -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Request; +use Illuminate\Support\Fluent; use Illuminate\Routing\Redirector; +use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Fluent; use Illuminate\Validation\Rules\Unique; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Foundation\Http\FormRequest; +use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Validation\Factory; use Illuminate\Validation\ValidationException; -use Illuminate\Validation\Validator; +use Illuminate\Contracts\Translation\Translator; use Simtabi\Laranail\Validation\FluentFormRequest; -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\OptimizedValidator; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Unit: OptimizedValidator fast check compilation @@ -148,8 +148,8 @@ it('builds fast checks for multiple patterns independently', function (): void { $checks = OptimizedValidator::buildFastChecks([ - 'items.*.name' => 'string|required|max:255', - 'items.*.qty' => 'numeric|required|min:1', + 'items.*.name' => 'string|required|max:255', + 'items.*.qty' => 'numeric|required|min:1', 'items.*.email' => 'required|string|email', // not fast-checkable ]); @@ -220,7 +220,7 @@ rules: [ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string('Name')->required()->max(255), - 'qty' => FluentRule::numeric('Quantity')->required()->min(1), + 'qty' => FluentRule::numeric('Quantity')->required()->min(1), ]), ], data: [ @@ -333,7 +333,7 @@ rules: [ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->max(255), - 'qty' => FluentRule::numeric()->required()->min(1), + 'qty' => FluentRule::numeric()->required()->min(1), ]), ], data: [ @@ -413,7 +413,7 @@ rules: [ 'items' => FluentRule::array()->required()->each([ 'start' => FluentRule::numeric()->required(), - 'end' => FluentRule::numeric()->required()->greaterThanOrEqualTo('items.*.start'), + 'end' => FluentRule::numeric()->required()->greaterThanOrEqualTo('items.*.start'), ]), ], data: [ @@ -437,11 +437,11 @@ it('handles rules without wildcards', function (): void { $formRequest = createFluentFormRequest( rules: [ - 'name' => FluentRule::string('Name')->required()->max(255), + 'name' => FluentRule::string('Name')->required()->max(255), 'email' => FluentRule::email('Email')->required(), ], data: [ - 'name' => 'John', + 'name' => 'John', 'email' => 'john@example.com', ], ); @@ -636,7 +636,7 @@ $formRequest = createFluentFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->max(255), + 'name' => FluentRule::string()->required()->max(255), 'user_id' => FluentRule::numeric()->bail()->required()->integer() ->rule(function (string $attribute, mixed $value, Closure $fail) use (&$closureCalled): void { $closureCalled = true; @@ -669,7 +669,7 @@ $formRequest = createFluentFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->max(255), + 'name' => FluentRule::string()->required()->max(255), 'user_id' => FluentRule::numeric()->bail()->required()->integer() ->rule(function (string $attribute, mixed $value, Closure $fail) use (&$closureCalled, &$closureValue): void { $closureCalled = true; @@ -699,7 +699,7 @@ $formRequest = createFluentFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->max(255), // fast-checkable + 'name' => FluentRule::string()->required()->max(255), // fast-checkable 'score' => FluentRule::numeric()->required()->rule(function (string $attribute, mixed $value, Closure $fail) use (&$closureCalled): void { $closureCalled = true; if ($value > 100) { @@ -865,7 +865,7 @@ $formRequest = createFluentFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->max(255), // fast-checkable + 'name' => FluentRule::string()->required()->max(255), // fast-checkable 'email' => FluentRule::email()->required(), // NOT fast-checkable (email rule) ]), ], @@ -895,7 +895,7 @@ rules: [ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->max(255), - 'qty' => FluentRule::numeric()->required()->min(1), + 'qty' => FluentRule::numeric()->required()->min(1), ]), ], data: [ @@ -924,13 +924,13 @@ rules: [ 'address' => FluentRule::field()->children([ 'street' => FluentRule::string()->required()->max(255), - 'city' => FluentRule::string()->required()->max(100), + 'city' => FluentRule::string()->required()->max(100), ]), ], data: [ 'address' => [ 'street' => '123 Main St', - 'city' => 'Springfield', + 'city' => 'Springfield', ], ], ); @@ -947,13 +947,13 @@ rules: [ 'address' => FluentRule::field()->children([ 'street' => FluentRule::string()->required()->max(255), - 'city' => FluentRule::string()->required()->max(100), + 'city' => FluentRule::string()->required()->max(100), ]), ], data: [ 'address' => [ 'street' => '', - 'city' => 'Springfield', + 'city' => 'Springfield', ], ], ); @@ -1048,7 +1048,7 @@ it('correctly identifies the specific invalid item among many valid ones', function (): void { $items = []; for ($i = 0; $i < 50; $i++) { - $items[] = ['name' => 'Valid Name '.$i, 'qty' => $i + 1]; + $items[] = ['name' => 'Valid Name ' . $i, 'qty' => $i + 1]; } // Make item 37 invalid. @@ -1058,7 +1058,7 @@ rules: [ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->max(255), - 'qty' => FluentRule::numeric()->required()->min(1), + 'qty' => FluentRule::numeric()->required()->min(1), ]), ], data: ['items' => $items], @@ -1268,7 +1268,7 @@ public function rules(): array { return [ - 'name' => FluentRule::string()->required()->max(255), + 'name' => FluentRule::string()->required()->max(255), 'email' => FluentRule::email()->required(), ]; } @@ -1295,7 +1295,7 @@ public function authorize(): bool it('does not stop on first failure by default', function (): void { $formRequest = createFluentFormRequest( rules: [ - 'name' => FluentRule::string()->required()->max(255), + 'name' => FluentRule::string()->required()->max(255), 'email' => FluentRule::email()->required(), ], data: [], @@ -1389,8 +1389,8 @@ public function authorize(): bool }); /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function createFluentFormRequest(array $rules, array $data): FormRequest { diff --git a/tests/FluentRuleContractTest.php b/tests/FluentRuleContractTest.php index 4f43408..4f82ef8 100644 --- a/tests/FluentRuleContractTest.php +++ b/tests/FluentRuleContractTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Contracts\Validation\ValidationRule; -use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; -use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; -use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; use Simtabi\Laranail\Validation\Builder\Nodes\DateRule; +use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; +use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\EmailRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\Builder\Nodes\FileRule; use Simtabi\Laranail\Validation\Builder\Nodes\ImageRule; +use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; +use Simtabi\Laranail\Validation\Builder\Nodes\BooleanRule; use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; +use Simtabi\Laranail\Validation\Builder\Nodes\AcceptedRule; use Simtabi\Laranail\Validation\Builder\Nodes\PasswordRule; -use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\FluentRule; // ========================================================================= // FluentRuleContract — medium-contract marker interface implemented by @@ -74,8 +74,8 @@ function allRuleClasses(): array // `array` instead of a concrete-type union. /** @var array $rules */ $rules = [ - 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->nullable()->integer()->min(0), + 'name' => FluentRule::string()->required()->min(2), + 'age' => FluentRule::numeric()->nullable()->integer()->min(0), 'email' => FluentRule::email()->required(), 'agree' => FluentRule::accepted(), ]; diff --git a/tests/FluentRuleTest.php b/tests/FluentRuleTest.php index 75afbcc..56b662a 100644 --- a/tests/FluentRuleTest.php +++ b/tests/FluentRuleTest.php @@ -2,25 +2,25 @@ declare(strict_types=1); -use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Fluent; use Illuminate\Validation\Rule; -use Illuminate\Validation\Rules\Contains; +use Illuminate\Validation\Rules\In; use Illuminate\Validation\Rules\Enum; -use Illuminate\Validation\Rules\ExcludeIf; use Illuminate\Validation\Rules\Exists; -use Illuminate\Validation\Rules\In; use Illuminate\Validation\Rules\Unique; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Validation\Rules\Contains; +use Illuminate\Validation\Rules\ExcludeIf; +use Symfony\Component\VarDumper\VarDumper; +use Illuminate\Contracts\Support\Arrayable; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Validation\ValidationException; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; -use Simtabi\Laranail\Validation\Tests\Fixtures\TestArrayKeyEnum; use Simtabi\Laranail\Validation\Tests\Fixtures\TestIntEnum; -use Simtabi\Laranail\Validation\Tests\Fixtures\TestStringEnum; use Simtabi\Laranail\Validation\Tests\Fixtures\TestUnitEnum; -use Symfony\Component\VarDumper\VarDumper; +use Simtabi\Laranail\Validation\Tests\Fixtures\TestStringEnum; +use Simtabi\Laranail\Validation\Tests\Fixtures\TestArrayKeyEnum; // ========================================================================= // BooleanRule @@ -152,7 +152,7 @@ ['items' => [['name' => 'John', 'age' => 25], ['name' => 'Jane', 'age' => 30]]], ['items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->required()->min(0), + 'age' => FluentRule::numeric()->required()->min(0), ])], ); @@ -162,7 +162,7 @@ ['items' => [['name' => 'J', 'age' => 25]]], ['items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->required()->min(0), + 'age' => FluentRule::numeric()->required()->min(0), ])], ); @@ -273,7 +273,7 @@ ]], ['items' => FluentRule::array()->required()->each([ 'action' => FluentRule::array()->required()->children([ - 'type' => FluentRule::string()->required(), + 'type' => FluentRule::string()->required(), 'target' => FluentRule::string()->required(), ]), ])], @@ -288,7 +288,7 @@ ]], ['items' => FluentRule::array()->required()->each([ 'action' => FluentRule::array()->required()->children([ - 'type' => FluentRule::string()->required(), + 'type' => FluentRule::string()->required(), 'target' => FluentRule::string()->required(), ]), ])], @@ -1500,7 +1500,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('compiles fluent rules to native format', function (): void { $compiled = RuleSet::compile([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->integer(), + 'age' => FluentRule::numeric()->integer(), ]); expect($compiled)->toMatchArray(['name' => 'required|string|min:2', 'age' => 'numeric|integer']); }); @@ -1520,14 +1520,14 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('compileToArrays returns arrays for fluent rules', function (): void { $compiled = RuleSet::compileToArrays([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->integer(), + 'age' => FluentRule::numeric()->integer(), ]); expect($compiled)->toMatchArray(['name' => ['required', 'string', 'min:2'], 'age' => ['numeric', 'integer']]); }); it('compileToArrays explodes string rules into arrays', function (): void { $compiled = RuleSet::compileToArrays([ - 'name' => 'required|string|max:255', + 'name' => 'required|string|max:255', 'email' => 'required|email', ]); expect($compiled)->toMatchArray(['name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email']]); @@ -1556,9 +1556,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void { it('compileToArrays handles mixed fluent and string rules', function (): void { $compiled = RuleSet::compileToArrays([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => 'required|email', - 'tags' => ['required', 'array'], + 'tags' => ['required', 'array'], ]); expect($compiled)->toMatchArray(['name' => ['required', 'string'], 'email' => ['required', 'email'], 'tags' => ['required', 'array']]); }); @@ -2181,14 +2181,14 @@ public function toArray(): array expect((string) $fluentContains)->toBe((string) $directRule); })->with([ - 'single string' => 'php', - 'single int' => 42, + 'single string' => 'php', + 'single int' => 42, 'single array unwrapped' => [['a', 'b', 'c']], 'Arrayable (Collection)' => fn () => collect(['a', 'b']), - 'BackedEnum single' => fn () => TestStringEnum::Active, - 'UnitEnum single' => fn () => TestUnitEnum::Foo, - 'embedded comma' => 'a,b', - 'embedded quote' => 'he said "hi"', + 'BackedEnum single' => fn () => TestStringEnum::Active, + 'UnitEnum single' => fn () => TestUnitEnum::Foo, + 'embedded comma' => 'a,b', + 'embedded quote' => 'he said "hi"', ]); // ========================================================================= @@ -2457,7 +2457,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void { it('RuleSet dump returns rules messages and attributes', function (): void { $dump = RuleSet::from([ - 'name' => FluentRule::string('Full Name')->required()->max(255), + 'name' => FluentRule::string('Full Name')->required()->max(255), 'email' => FluentRule::email()->required(), ])->dump(); diff --git a/tests/FluentSchemaTest.php b/tests/FluentSchemaTest.php index 33b8f5a..4948424 100644 --- a/tests/FluentSchemaTest.php +++ b/tests/FluentSchemaTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); -use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Support\Traits\Macroable; use Illuminate\Validation\Rules\AnyOf; -use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; +use Illuminate\Support\Traits\Macroable; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Foundation\Http\FormRequest; use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\FluentSchema; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\HasFluentRules; -use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; use Simtabi\Laranail\Validation\Tests\Fixtures\TestStringEnum; /** @@ -26,8 +26,8 @@ function preparedSnapshot(mixed $rule): array $prepared = RuleSet::from(['field' => $rule])->prepare(['field' => 'value']); return [ - 'rules' => $prepared->rules, - 'messages' => $prepared->messages, + 'rules' => $prepared->rules, + 'messages' => $prepared->messages, 'attributes' => $prepared->attributes, ]; } @@ -36,7 +36,7 @@ function preparedSnapshot(mixed $rule): array * A FormRequest that defines BOTH schema() and rules() — used to pin the * precedence contract (schema() wins). * - * @param array $data + * @param array $data */ function bootDualFormRequest(array $data): FormRequest { @@ -123,7 +123,7 @@ function fluentRuleFactoryMethods(): array '%s $%s%s', (string) $p->getType(), $p->getName(), - $p->isDefaultValueAvailable() ? '='.var_export($p->getDefaultValue(), true) : '', + $p->isDefaultValueAvailable() ? '=' . var_export($p->getDefaultValue(), true) : '', ), $m->getParameters(), ); @@ -198,26 +198,26 @@ function fluentRuleFactoryMethods(): array // each one proves it forwards to the RIGHT FluentRule factory — a wrong // target (e.g. url() calling uuid()) survives the signature parity check but // produces a different compiled rule here. - 'numeric' => [fn (FluentSchema $s): mixed => $s->numeric('L', 'm')->required(), fn (): mixed => FluentRule::numeric('L', 'm')->required()], - 'date' => [fn (FluentSchema $s): mixed => $s->date('L', 'm')->required(), fn (): mixed => FluentRule::date('L', 'm')->required()], - 'boolean' => [fn (FluentSchema $s): mixed => $s->boolean('L', 'm')->required(), fn (): mixed => FluentRule::boolean('L', 'm')->required()], - 'accepted' => [fn (FluentSchema $s): mixed => $s->accepted('L', 'm')->required(), fn (): mixed => FluentRule::accepted('L', 'm')->required()], - 'declined' => [fn (FluentSchema $s): mixed => $s->declined('L', 'm')->required(), fn (): mixed => FluentRule::declined('L', 'm')->required()], - 'file' => [fn (FluentSchema $s): mixed => $s->file('L', 'm')->required(), fn (): mixed => FluentRule::file('L', 'm')->required()], - 'image' => [fn (FluentSchema $s): mixed => $s->image('L', 'm')->required(), fn (): mixed => FluentRule::image('L', 'm')->required()], - 'url' => [fn (FluentSchema $s): mixed => $s->url('L', 'm')->required(), fn (): mixed => FluentRule::url('L', 'm')->required()], - 'uuid' => [fn (FluentSchema $s): mixed => $s->uuid('L', 'm')->required(), fn (): mixed => FluentRule::uuid('L', 'm')->required()], - 'ulid' => [fn (FluentSchema $s): mixed => $s->ulid('L', 'm')->required(), fn (): mixed => FluentRule::ulid('L', 'm')->required()], - 'ip' => [fn (FluentSchema $s): mixed => $s->ip('L', 'm')->required(), fn (): mixed => FluentRule::ip('L', 'm')->required()], - 'ipv4' => [fn (FluentSchema $s): mixed => $s->ipv4('L', 'm')->required(), fn (): mixed => FluentRule::ipv4('L', 'm')->required()], - 'ipv6' => [fn (FluentSchema $s): mixed => $s->ipv6('L', 'm')->required(), fn (): mixed => FluentRule::ipv6('L', 'm')->required()], + 'numeric' => [fn (FluentSchema $s): mixed => $s->numeric('L', 'm')->required(), fn (): mixed => FluentRule::numeric('L', 'm')->required()], + 'date' => [fn (FluentSchema $s): mixed => $s->date('L', 'm')->required(), fn (): mixed => FluentRule::date('L', 'm')->required()], + 'boolean' => [fn (FluentSchema $s): mixed => $s->boolean('L', 'm')->required(), fn (): mixed => FluentRule::boolean('L', 'm')->required()], + 'accepted' => [fn (FluentSchema $s): mixed => $s->accepted('L', 'm')->required(), fn (): mixed => FluentRule::accepted('L', 'm')->required()], + 'declined' => [fn (FluentSchema $s): mixed => $s->declined('L', 'm')->required(), fn (): mixed => FluentRule::declined('L', 'm')->required()], + 'file' => [fn (FluentSchema $s): mixed => $s->file('L', 'm')->required(), fn (): mixed => FluentRule::file('L', 'm')->required()], + 'image' => [fn (FluentSchema $s): mixed => $s->image('L', 'm')->required(), fn (): mixed => FluentRule::image('L', 'm')->required()], + 'url' => [fn (FluentSchema $s): mixed => $s->url('L', 'm')->required(), fn (): mixed => FluentRule::url('L', 'm')->required()], + 'uuid' => [fn (FluentSchema $s): mixed => $s->uuid('L', 'm')->required(), fn (): mixed => FluentRule::uuid('L', 'm')->required()], + 'ulid' => [fn (FluentSchema $s): mixed => $s->ulid('L', 'm')->required(), fn (): mixed => FluentRule::ulid('L', 'm')->required()], + 'ip' => [fn (FluentSchema $s): mixed => $s->ip('L', 'm')->required(), fn (): mixed => FluentRule::ip('L', 'm')->required()], + 'ipv4' => [fn (FluentSchema $s): mixed => $s->ipv4('L', 'm')->required(), fn (): mixed => FluentRule::ipv4('L', 'm')->required()], + 'ipv6' => [fn (FluentSchema $s): mixed => $s->ipv6('L', 'm')->required(), fn (): mixed => FluentRule::ipv6('L', 'm')->required()], 'macAddress' => [fn (FluentSchema $s): mixed => $s->macAddress('L', 'm')->required(), fn (): mixed => FluentRule::macAddress('L', 'm')->required()], - 'json' => [fn (FluentSchema $s): mixed => $s->json('L', 'm')->required(), fn (): mixed => FluentRule::json('L', 'm')->required()], - 'timezone' => [fn (FluentSchema $s): mixed => $s->timezone('L', 'm')->required(), fn (): mixed => FluentRule::timezone('L', 'm')->required()], - 'hexColor' => [fn (FluentSchema $s): mixed => $s->hexColor('L', 'm')->required(), fn (): mixed => FluentRule::hexColor('L', 'm')->required()], - 'activeUrl' => [fn (FluentSchema $s): mixed => $s->activeUrl('L', 'm')->required(), fn (): mixed => FluentRule::activeUrl('L', 'm')->required()], - 'list' => [fn (FluentSchema $s): mixed => $s->list('L', 'm')->required(), fn (): mixed => FluentRule::list('L', 'm')->required()], - 'field' => [fn (FluentSchema $s): mixed => $s->field('L')->required(), fn (): mixed => FluentRule::field('L')->required()], + 'json' => [fn (FluentSchema $s): mixed => $s->json('L', 'm')->required(), fn (): mixed => FluentRule::json('L', 'm')->required()], + 'timezone' => [fn (FluentSchema $s): mixed => $s->timezone('L', 'm')->required(), fn (): mixed => FluentRule::timezone('L', 'm')->required()], + 'hexColor' => [fn (FluentSchema $s): mixed => $s->hexColor('L', 'm')->required(), fn (): mixed => FluentRule::hexColor('L', 'm')->required()], + 'activeUrl' => [fn (FluentSchema $s): mixed => $s->activeUrl('L', 'm')->required(), fn (): mixed => FluentRule::activeUrl('L', 'm')->required()], + 'list' => [fn (FluentSchema $s): mixed => $s->list('L', 'm')->required(), fn (): mixed => FluentRule::list('L', 'm')->required()], + 'field' => [fn (FluentSchema $s): mixed => $s->field('L')->required(), fn (): mixed => FluentRule::field('L')->required()], ]); it('forwards anyOf to FluentRule when AnyOf is available', function (): void { @@ -231,14 +231,14 @@ function fluentRuleFactoryMethods(): array it('validates through RuleSet::define', function (): void { $validated = RuleSet::define(fn (FluentSchema $rules): array => [ - 'name' => $rules->string('Full Name')->required()->min(2)->max(255), + 'name' => $rules->string('Full Name')->required()->min(2)->max(255), 'email' => $rules->email()->required(), 'items' => $rules->array()->required()->each([ - 'id' => $rules->integer()->required(), + 'id' => $rules->integer()->required(), 'name' => $rules->string()->required(), ]), ])->validate([ - 'name' => 'Amara', + 'name' => 'Amara', 'email' => 'amara@example.com', 'items' => [['id' => 1, 'name' => 'Widget']], ]); @@ -336,7 +336,7 @@ public function authorize(): bool public function schema(FluentSchema $rules): array { return [ - 'title' => $rules->string()->required()->max(10), + 'title' => $rules->string()->required()->max(10), 'author' => $rules->string()->required(), ]; } diff --git a/tests/FluentValidatorBatchDatabaseTest.php b/tests/FluentValidatorBatchDatabaseTest.php index 9a3a2d2..10eee3d 100644 --- a/tests/FluentValidatorBatchDatabaseTest.php +++ b/tests/FluentValidatorBatchDatabaseTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Validation\ValidationException; +use Illuminate\Database\Schema\Blueprint; use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentValidator; // ========================================================================= @@ -20,7 +20,7 @@ function setupFluentValidatorDatabase(): void { config(['database.default' => 'testing']); config(['database.connections.testing' => [ - 'driver' => 'sqlite', + 'driver' => 'sqlite', 'database' => ':memory:', ]]); diff --git a/tests/FluentValidatorBenchTest.php b/tests/FluentValidatorBenchTest.php index d0a54cc..0f2bd18 100644 --- a/tests/FluentValidatorBenchTest.php +++ b/tests/FluentValidatorBenchTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule as LaravelRule; use Simtabi\Laranail\Validation\FluentValidator; -use Simtabi\Laranail\Validation\RuleSet; /** * Concrete FluentValidator for the benchmark — the import-style conditional @@ -19,17 +19,17 @@ public function __construct(array $data) $types = ['button', 'hotspot', 'image', 'text', 'chapter', 'menu', 'pause']; parent::__construct($data, [ - 'interactions' => 'required|array|min:1', - 'interactions.*.type' => ['required', 'string', LaravelRule::in($types)], - 'interactions.*.title' => ['nullable', 'string'], - 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], - 'interactions.*.end_time' => [['required_unless', 'interactions.*.type', 'pause'], 'numeric', 'gte:interactions.*.start_time'], - 'interactions.*.position' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], - 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], - 'interactions.*.image_url' => [['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], + 'interactions' => 'required|array|min:1', + 'interactions.*.type' => ['required', 'string', LaravelRule::in($types)], + 'interactions.*.title' => ['nullable', 'string'], + 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], + 'interactions.*.end_time' => [['required_unless', 'interactions.*.type', 'pause'], 'numeric', 'gte:interactions.*.start_time'], + 'interactions.*.position' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], + 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], + 'interactions.*.image_url' => [['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], 'interactions.*.should_fade_in' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], - 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'string'], - 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'string'], + 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'string'], + 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'string'], ]); } } @@ -43,15 +43,15 @@ function benchItems(int $n): array $type = $types[$i % count($types)]; return [ - 'type' => $type, - 'title' => 'Item '.$i, - 'start_time' => 1.0, - 'end_time' => 2.0, - 'position' => 'top', - 'text' => 'hello', - 'image_url' => $type === 'image' ? 'https://example.com/x.png' : null, + 'type' => $type, + 'title' => 'Item ' . $i, + 'start_time' => 1.0, + 'end_time' => 2.0, + 'position' => 'top', + 'text' => 'hello', + 'image_url' => $type === 'image' ? 'https://example.com/x.png' : null, 'should_fade_in' => true, - 'style' => ['top' => '0px', 'left' => '0px'], + 'style' => ['top' => '0px', 'left' => '0px'], ]; }, range(0, $n - 1))]; } @@ -62,17 +62,17 @@ function benchNativeRules(): array $types = ['button', 'hotspot', 'image', 'text', 'chapter', 'menu', 'pause']; return [ - 'interactions' => 'required|array|min:1', - 'interactions.*.type' => ['required', 'string', LaravelRule::in($types)], - 'interactions.*.title' => ['nullable', 'string'], - 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], - 'interactions.*.end_time' => ['required_unless:interactions.*.type,pause', 'numeric', 'gte:interactions.*.start_time'], - 'interactions.*.position' => ['exclude_unless:interactions.*.type,chapter,menu', 'string'], - 'interactions.*.text' => ['exclude_unless:interactions.*.type,button,hotspot,text', 'nullable', 'string'], - 'interactions.*.image_url' => ['exclude_unless:interactions.*.type,image,hotspot', 'required_if:interactions.*.type,image', 'nullable', 'string'], + 'interactions' => 'required|array|min:1', + 'interactions.*.type' => ['required', 'string', LaravelRule::in($types)], + 'interactions.*.title' => ['nullable', 'string'], + 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], + 'interactions.*.end_time' => ['required_unless:interactions.*.type,pause', 'numeric', 'gte:interactions.*.start_time'], + 'interactions.*.position' => ['exclude_unless:interactions.*.type,chapter,menu', 'string'], + 'interactions.*.text' => ['exclude_unless:interactions.*.type,button,hotspot,text', 'nullable', 'string'], + 'interactions.*.image_url' => ['exclude_unless:interactions.*.type,image,hotspot', 'required_if:interactions.*.type,image', 'nullable', 'string'], 'interactions.*.should_fade_in' => ['exclude_unless:interactions.*.type,button,hotspot,image,text', 'boolean'], - 'interactions.*.style.top' => ['exclude_unless:interactions.*.type,button,hotspot,image,text', 'string'], - 'interactions.*.style.left' => ['exclude_unless:interactions.*.type,button,hotspot,image,text', 'string'], + 'interactions.*.style.top' => ['exclude_unless:interactions.*.type,button,hotspot,image,text', 'string'], + 'interactions.*.style.left' => ['exclude_unless:interactions.*.type,button,hotspot,image,text', 'string'], ]; } diff --git a/tests/FluentValidatorConditionalParityTest.php b/tests/FluentValidatorConditionalParityTest.php index 62e253a..e31e614 100644 --- a/tests/FluentValidatorConditionalParityTest.php +++ b/tests/FluentValidatorConditionalParityTest.php @@ -21,7 +21,7 @@ final class ConditionalParityValidator extends FluentValidator public function __construct(array $data) { parent::__construct($data, [ - 'interactions.*.type' => ['required', 'string'], + 'interactions.*.type' => ['required', 'string'], 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], // requiredUnless against the wildcard sibling path, multi-value. 'interactions.*.end_time' => [['required_unless', 'interactions.*.type', 'pause', 'stop'], 'numeric'], @@ -34,17 +34,18 @@ public function __construct(array $data) } /** - * @param array $data + * @param array $data + * * @return list sorted failing attribute keys */ function nativeConditionalFailures(array $data): array { $errors = validator($data, [ - 'interactions.*.type' => ['required', 'string'], + 'interactions.*.type' => ['required', 'string'], 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], - 'interactions.*.end_time' => ['required_unless:interactions.*.type,pause,stop', 'numeric'], - 'interactions.*.position' => ['exclude_unless:interactions.*.type,chapter,menu', 'required', 'string'], - 'interactions.*.image_url' => ['required_if:interactions.*.type,image', 'nullable', 'string'], + 'interactions.*.end_time' => ['required_unless:interactions.*.type,pause,stop', 'numeric'], + 'interactions.*.position' => ['exclude_unless:interactions.*.type,chapter,menu', 'required', 'string'], + 'interactions.*.image_url' => ['required_if:interactions.*.type,image', 'nullable', 'string'], ])->errors()->keys(); sort($errors); @@ -53,7 +54,8 @@ function nativeConditionalFailures(array $data): array } /** - * @param array $data + * @param array $data + * * @return list sorted failing attribute keys */ function fluentConditionalFailures(array $data): array @@ -74,22 +76,22 @@ function fluentConditionalFailures(array $data): array $data = ['interactions' => [$item]]; expect(fluentConditionalFailures($data)) - ->toBe(nativeConditionalFailures($data), 'item: '.json_encode($item)); + ->toBe(nativeConditionalFailures($data), 'item: ' . json_encode($item)); })->with([ // requiredUnless: type in (pause,stop) → end_time not required 'pause, no end_time' => [['type' => 'pause', 'start_time' => 1.0]], - 'stop, no end_time' => [['type' => 'stop', 'start_time' => 1.0]], + 'stop, no end_time' => [['type' => 'stop', 'start_time' => 1.0]], // requiredUnless: type not in list → end_time required 'play, no end_time → error' => [['type' => 'play', 'start_time' => 1.0]], - 'play, with end_time' => [['type' => 'play', 'start_time' => 1.0, 'end_time' => 2.0]], + 'play, with end_time' => [['type' => 'play', 'start_time' => 1.0, 'end_time' => 2.0]], // exclude_unless: position only required for chapter/menu 'chapter, no position → error' => [['type' => 'chapter', 'start_time' => 1.0, 'end_time' => 2.0]], - 'chapter, with position' => [['type' => 'chapter', 'start_time' => 1.0, 'end_time' => 2.0, 'position' => 'top']], - 'button, no position ok' => [['type' => 'button', 'start_time' => 1.0, 'end_time' => 2.0]], - 'menu, no position → error' => [['type' => 'menu', 'start_time' => 1.0, 'end_time' => 2.0]], + 'chapter, with position' => [['type' => 'chapter', 'start_time' => 1.0, 'end_time' => 2.0, 'position' => 'top']], + 'button, no position ok' => [['type' => 'button', 'start_time' => 1.0, 'end_time' => 2.0]], + 'menu, no position → error' => [['type' => 'menu', 'start_time' => 1.0, 'end_time' => 2.0]], // required_if: image_url required when type=image 'image, no url → error' => [['type' => 'image', 'start_time' => 1.0, 'end_time' => 2.0]], - 'image, with url' => [['type' => 'image', 'start_time' => 1.0, 'end_time' => 2.0, 'image_url' => 'x.png']], + 'image, with url' => [['type' => 'image', 'start_time' => 1.0, 'end_time' => 2.0, 'image_url' => 'x.png']], // missing start_time always fails 'missing start_time' => [['type' => 'pause']], ]); @@ -103,9 +105,9 @@ function fluentConditionalFailures(array $data): array // ========================================================================= /** - * @param array $rules native rule strings keyed by wildcard path - * @param array $fluentRules - * @param array $data + * @param array $rules native rule strings keyed by wildcard path + * @param array $fluentRules + * @param array $data */ function assertFvExcludeParity(array $rules, array $fluentRules, array $data): void { @@ -130,7 +132,7 @@ function assertFvExcludeParity(array $rules, array $fluentRules, array $data): v assertFvExcludeParity( ['items.*.detail' => ['exclude_unless:items.*.state,null', 'required', 'string']], ['items.*.detail' => [['exclude_unless', 'items.*.state', 'null'], 'required', 'string']], - ['items' => [['state' => null]]], // state IS null → not excluded → required fires + ['items' => [['state' => null]]], // state IS null → not excluded → required fires ); }); @@ -152,7 +154,7 @@ function assertFvExcludeParity(array $rules, array $fluentRules, array $data): v assertFvExcludeParity( ['items.*.extra' => ['exclude_unless:items.*.type,a', 'required', 'string']], ['items.*.extra' => [['exclude_unless', 'items.*.type', 'a'], 'required', 'string']], - ['items' => ['foo' => ['type' => 'a']]], // type matches → not excluded → required fires + ['items' => ['foo' => ['type' => 'a']]], // type matches → not excluded → required fires ); }); @@ -163,7 +165,7 @@ function assertFvExcludeParity(array $rules, array $fluentRules, array $data): v assertFvExcludeParity( ['items.*.rows.*.detail' => ['exclude_unless:items.*.type,keep', 'required', 'string']], ['items.*.rows.*.detail' => [['exclude_unless', 'items.*.type', 'keep'], 'required', 'string']], - ['items' => ['foo' => ['type' => 'drop', 'rows' => [['x' => 1]]]]], + ['items' => ['foo' => ['type' => 'drop', 'rows' => [['x' => 1]]]]], ); }); @@ -177,9 +179,9 @@ function assertFvExcludeParity(array $rules, array $fluentRules, array $data): v $data = ['items' => [['flag' => true, 'name' => 'present', 'label' => 'ok']]]; $rules = static fn (mixed $excludeIf): array => [ - 'items.*.flag' => ['boolean'], + 'items.*.flag' => ['boolean'], 'items.*.label' => ['string'], - 'items.*.name' => [$excludeIf, 'string'], + 'items.*.name' => [$excludeIf, 'string'], ]; $native = validator($data, $rules('exclude_if:items.*.flag,true'))->validate(); diff --git a/tests/HasFluentValidationForFilamentTest.php b/tests/HasFluentValidationForFilamentTest.php index 63f34b3..3680fb1 100644 --- a/tests/HasFluentValidationForFilamentTest.php +++ b/tests/HasFluentValidationForFilamentTest.php @@ -24,8 +24,8 @@ class FakeFilamentBase public function validate(mixed $rules = null, mixed $messages = [], mixed $attributes = []): mixed { $this->lastValidateCall = [ - 'rules' => $rules, - 'messages' => is_array($messages) ? $messages : [], + 'rules' => $rules, + 'messages' => is_array($messages) ? $messages : [], 'attributes' => is_array($attributes) ? $attributes : [], ]; @@ -35,9 +35,9 @@ public function validate(mixed $rules = null, mixed $messages = [], mixed $attri public function validateOnly(mixed $field, mixed $rules = null, mixed $messages = [], mixed $attributes = [], mixed $dataOverrides = []): mixed { $this->lastValidateOnlyCall = [ - 'field' => is_string($field) ? $field : '', - 'rules' => $rules, - 'messages' => is_array($messages) ? $messages : [], + 'field' => is_string($field) ? $field : '', + 'rules' => $rules, + 'messages' => is_array($messages) ? $messages : [], 'attributes' => is_array($attributes) ? $attributes : [], ]; @@ -63,8 +63,8 @@ public function getValidationAttributes(): array class FakeFilamentForm { /** - * @param array $validationRules - * @param array $validationAttributes + * @param array $validationRules + * @param array $validationAttributes */ public function __construct( private readonly array $validationRules = [], @@ -89,9 +89,9 @@ class TestableFilamentComponent extends FakeFilamentBase use HasFluentValidationForFilament; /** - * @param array $fluentRules - * @param array $data - * @param list $forms + * @param array $fluentRules + * @param array $data + * @param list $forms */ public function __construct( private array $fluentRules = [], @@ -106,7 +106,8 @@ public function rules(): array } /** - * @param array $rules + * @param array $rules + * * @return array */ public function getDataForValidation(array $rules): array diff --git a/tests/HasFluentValidationTest.php b/tests/HasFluentValidationTest.php index f54fe29..0d361b4 100644 --- a/tests/HasFluentValidationTest.php +++ b/tests/HasFluentValidationTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\HasFluentValidation; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // Test doubles @@ -55,8 +55,8 @@ class TestableComponent extends FakeLivewireBase use HasFluentValidation; /** - * @param array $data - * @param array $fluentRules + * @param array $data + * @param array $fluentRules */ public function __construct(public array $data = [], private array $fluentRules = []) {} @@ -69,7 +69,8 @@ public function rules(): array /** * Simulates Livewire's data resolution. * - * @param array $rules + * @param array $rules + * * @return array */ public function getDataForValidation(array $rules): array @@ -80,9 +81,10 @@ public function getDataForValidation(array $rules): array /** * Expose the protected compilation step for testing. * - * @param array|null $rules - * @param array $messages - * @param array $attributes + * @param array|null $rules + * @param array $messages + * @param array $attributes + * * @return array{0: array|null, 1: array, 2: array} */ public function compile(?array $rules = null, array $messages = [], array $attributes = []): array @@ -109,9 +111,10 @@ public function all(): array } /** - * @param array|null $rules - * @param array $messages - * @param array $attributes + * @param array|null $rules + * @param array $messages + * @param array $attributes + * * @return array{0: array|null, 1: array, 2: array} */ public function compile(?array $rules = null, array $messages = [], array $attributes = []): array @@ -129,9 +132,9 @@ class TestableComponentWithUnwrap extends FakeLivewireBase use HasFluentValidation; /** - * @param array $rawData - * @param array $unwrappedData - * @param array|RuleSet $rules + * @param array $rawData + * @param array $unwrappedData + * @param array|RuleSet $rules */ public function __construct(public array $rawData, public array $unwrappedData, private array|RuleSet $rules = []) {} @@ -142,7 +145,8 @@ public function rules(): array|RuleSet } /** - * @param array $rules + * @param array $rules + * * @return array */ public function getDataForValidation(array $rules): array @@ -157,9 +161,10 @@ public function unwrapDataForValidation(mixed $data): array } /** - * @param array|null $rules - * @param array $messages - * @param array $attributes + * @param array|null $rules + * @param array $messages + * @param array $attributes + * * @return array{0: array|null, 1: array, 2: array} */ public function compile(?array $rules = null, array $messages = [], array $attributes = []): array @@ -339,8 +344,8 @@ public function compile(?array $rules = null, array $messages = [], array $attri $component = new TestableComponent( data: ['items' => [['name' => 'Foo'], ['name' => 'Bar']]], fluentRules: [ - 'items' => FluentRule::array()->required(), - 'items.*' => FluentRule::array()->required(), + 'items' => FluentRule::array()->required(), + 'items.*' => FluentRule::array()->required(), 'items.*.name' => FluentRule::string()->required()->max(255), ], ); @@ -374,8 +379,8 @@ public function compile(?array $rules = null, array $messages = [], array $attri rawData: ['items' => 'model-object'], unwrappedData: ['items' => [['name' => 'Foo'], ['name' => 'Bar']]], rules: [ - 'items' => FluentRule::array()->required(), - 'items.*' => FluentRule::array()->required(), + 'items' => FluentRule::array()->required(), + 'items.*' => FluentRule::array()->required(), 'items.*.name' => FluentRule::string()->required(), ], ); @@ -435,7 +440,7 @@ public function compile(?array $rules = null, array $messages = [], array $attri $component = new TestableComponent( data: ['items' => [['name' => 'Foo']]], fluentRules: [ - 'items' => FluentRule::array()->required(), + 'items' => FluentRule::array()->required(), 'items.*.name' => FluentRule::string()->required(), ], ); @@ -530,7 +535,7 @@ public function compile(?array $rules = null, array $messages = [], array $attri data: ['credentials' => ['base_uri' => 'https://example.com', 'client_id' => '123']], fluentRules: [ 'credentials' => FluentRule::array()->children([ - 'base_uri' => FluentRule::string()->nullable()->url(), + 'base_uri' => FluentRule::string()->nullable()->url(), 'client_id' => FluentRule::string()->required()->uuid(), ]), ], @@ -589,7 +594,7 @@ public function compile(?array $rules = null, array $messages = [], array $attri $component = new TestableComponent( data: ['name' => 'John', 'email' => 'john@example.com'], fluentRules: [ - 'name' => FluentRule::string('Full Name')->required(), + 'name' => FluentRule::string('Full Name')->required(), 'email' => FluentRule::email()->required(), ], ); diff --git a/tests/Helpers.php b/tests/Helpers.php index c178987..eb26067 100644 --- a/tests/Helpers.php +++ b/tests/Helpers.php @@ -30,8 +30,9 @@ function laravelSupportsIntegerStrict(): bool * be stringified during compilation — __toString() silently drops them. Use * this to assert the object itself survived rather than its lossy string form. * - * @param list $rules - * @param class-string $type + * @param list $rules + * @param class-string $type + * * @return list */ function rulesOfType(array $rules, string $type): array @@ -83,12 +84,13 @@ function compiledArray(mixed $compiled): array * failed outright. Every Windows CI cell failed that way while every Linux one * passed. * - * @param class-string|null $implementing Restrict to classes of this type. + * @param class-string|null $implementing Restrict to classes of this type. + * * @return list */ function ruleClassesUnder(?string $implementing = null): array { - $base = dirname(__DIR__).'/src/Rules'; + $base = dirname(__DIR__) . '/src/Rules'; $classes = []; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base)) as $file) { @@ -100,7 +102,7 @@ function ruleClassesUnder(?string $implementing = null): array $path = str_replace('\\', '/', $file->getPathname()); $relative = substr($path, strlen(str_replace('\\', '/', $base)) + 1, -4); - $class = 'Simtabi\\Laranail\\Validation\\Rules\\'.str_replace('/', '\\', $relative); + $class = 'Simtabi\\Laranail\\Validation\\Rules\\' . str_replace('/', '\\', $relative); if (! class_exists($class)) { continue; diff --git a/tests/ImplicitAttributesCanaryTest.php b/tests/ImplicitAttributesCanaryTest.php index 14d0cbc..d56cc20 100644 --- a/tests/ImplicitAttributesCanaryTest.php +++ b/tests/ImplicitAttributesCanaryTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Illuminate\Support\Facades\Validator as ValidatorFacade; use Illuminate\Validation\Validator; +use Illuminate\Support\Facades\Validator as ValidatorFacade; /** * Canary for the package's one reach into Laravel's internals. diff --git a/tests/ImportBenchTest.php b/tests/ImportBenchTest.php index 4609881..c217c0d 100644 --- a/tests/ImportBenchTest.php +++ b/tests/ImportBenchTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule as LaravelRule; -use Simtabi\Laranail\Validation\RuleSet; /** * Simulates a complex JSON import validator: @@ -19,34 +19,34 @@ // Generate 300 realistic interactions $interactions = array_map(fn (int $i): array => [ - 'type' => $interactionTypes[$i % count($interactionTypes)], - 'title' => "Interaction {$i}", - 'start_time' => $i * 10, - 'end_time' => $i * 10 + 5, - 'position' => 'bottom', - 'should_start_collapsed' => false, + 'type' => $interactionTypes[$i % count($interactionTypes)], + 'title' => "Interaction {$i}", + 'start_time' => $i * 10, + 'end_time' => $i * 10 + 5, + 'position' => 'bottom', + 'should_start_collapsed' => false, 'should_collapse_after_menu_item_click' => true, - 'should_pause_when_shown' => false, - 'should_not_use_time' => false, - 'should_use_menu_layout' => false, - 'text' => '

Sample text

', - 'text_stroke' => null, - 'should_fade_volume' => false, - 'sound_url' => null, - 'should_enable_sound' => false, - 'should_fade_in' => true, - 'should_fade_out' => true, - 'image_url' => $interactionTypes[$i % count($interactionTypes)] === 'image' ? 'https://example.com/image.png' : null, - 'style' => [ - 'top' => '10%', - 'left' => '20%', - 'height' => '30%', - 'width' => '40%', + 'should_pause_when_shown' => false, + 'should_not_use_time' => false, + 'should_use_menu_layout' => false, + 'text' => '

Sample text

', + 'text_stroke' => null, + 'should_fade_volume' => false, + 'sound_url' => null, + 'should_enable_sound' => false, + 'should_fade_in' => true, + 'should_fade_out' => true, + 'image_url' => $interactionTypes[$i % count($interactionTypes)] === 'image' ? 'https://example.com/image.png' : null, + 'style' => [ + 'top' => '10%', + 'left' => '20%', + 'height' => '30%', + 'width' => '40%', 'background_color' => '#ff0000', - 'border_radius' => 5, - 'padding_top' => 10, - 'padding_bottom' => 10, - 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], + 'border_radius' => 5, + 'padding_top' => 10, + 'padding_bottom' => 10, + 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], ], 'action' => [ 'type' => 'link', @@ -54,17 +54,17 @@ 'time' => 0, ], 'attributes' => [ - 'show_indicator' => true, + 'show_indicator' => true, 'indicator_color' => '#00ff00', - 'blinking_speed' => 'normal', - 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], + 'blinking_speed' => 'normal', + 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], ], 'chapters' => $i % count($interactionTypes) === 4 ? array_map(fn (int $j): array => [ - 'title' => "Chapter {$j}", + 'title' => "Chapter {$j}", 'title_short' => null, - 'start_time' => $j * 5, - 'end_time' => $j * 5 + 4, - 'sort_order' => $j, + 'start_time' => $j * 5, + 'end_time' => $j * 5 + 4, + 'sort_order' => $j, ], range(1, 4)) : [], ], range(1, 100)); @@ -73,58 +73,58 @@ // Build rules that match hihaho's complexity: ~30 wildcard patterns, // conditional rules, nested wildcards, custom rule objects $rules = [ - 'interactions' => 'required|array|min:1', - 'interactions.*.type' => ['required', 'string', LaravelRule::in($interactionTypes)], - 'interactions.*.title' => ['nullable', 'string'], - 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], - 'interactions.*.end_time' => ['required', 'numeric', 'gte:interactions.*.start_time'], - 'interactions.*.position' => ['bail', ['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], - 'interactions.*.should_start_collapsed' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], + 'interactions' => 'required|array|min:1', + 'interactions.*.type' => ['required', 'string', LaravelRule::in($interactionTypes)], + 'interactions.*.title' => ['nullable', 'string'], + 'interactions.*.start_time' => ['required', 'numeric', 'min:0'], + 'interactions.*.end_time' => ['required', 'numeric', 'gte:interactions.*.start_time'], + 'interactions.*.position' => ['bail', ['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'string'], + 'interactions.*.should_start_collapsed' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], 'interactions.*.should_collapse_after_menu_item_click' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'boolean'], - 'interactions.*.should_pause_when_shown' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'nullable', 'boolean'], - 'interactions.*.should_not_use_time' => [['exclude_unless', 'interactions.*.type', 'menu'], 'nullable', 'boolean'], - 'interactions.*.should_use_menu_layout' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'boolean'], - 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], - 'interactions.*.text_stroke' => [['exclude_unless', 'interactions.*.type', 'text'], 'nullable', 'string'], - 'interactions.*.should_fade_volume' => ['boolean'], - 'interactions.*.sound_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'nullable', 'string'], - 'interactions.*.should_enable_sound' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'boolean'], - 'interactions.*.should_fade_in' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], - 'interactions.*.should_fade_out' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], - 'interactions.*.image_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], + 'interactions.*.should_pause_when_shown' => [['exclude_unless', 'interactions.*.type', 'chapter', 'menu'], 'nullable', 'boolean'], + 'interactions.*.should_not_use_time' => [['exclude_unless', 'interactions.*.type', 'menu'], 'nullable', 'boolean'], + 'interactions.*.should_use_menu_layout' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'boolean'], + 'interactions.*.text' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'string'], + 'interactions.*.text_stroke' => [['exclude_unless', 'interactions.*.type', 'text'], 'nullable', 'string'], + 'interactions.*.should_fade_volume' => ['boolean'], + 'interactions.*.sound_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'nullable', 'string'], + 'interactions.*.should_enable_sound' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image'], 'boolean'], + 'interactions.*.should_fade_in' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], + 'interactions.*.should_fade_out' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text'], 'boolean'], + 'interactions.*.image_url' => ['bail', ['exclude_unless', 'interactions.*.type', 'image', 'hotspot'], ['required_if', 'interactions.*.type', 'image'], 'nullable', 'string'], // Style rules - 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.height' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], - 'interactions.*.style.width' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.left' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.height' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], + 'interactions.*.style.width' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'image', 'text', 'iframe'], 'string'], 'interactions.*.style.background_color' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'string'], - 'interactions.*.style.border_radius' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot'], 'nullable', 'numeric', 'integer', 'between:0,360'], - 'interactions.*.style.padding_top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], - 'interactions.*.style.padding_bottom' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], - 'interactions.*.style.border' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'array'], - 'interactions.*.style.border.width' => ['nullable', 'numeric', 'integer', 'min:0'], - 'interactions.*.style.border.style' => ['nullable', 'string', LaravelRule::in(['solid', 'dashed', 'dotted', 'none'])], - 'interactions.*.style.border.color' => ['nullable', 'string'], + 'interactions.*.style.border_radius' => ['bail', ['exclude_unless', 'interactions.*.type', 'button', 'hotspot'], 'nullable', 'numeric', 'integer', 'between:0,360'], + 'interactions.*.style.padding_top' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], + 'interactions.*.style.padding_bottom' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'numeric', 'integer'], + 'interactions.*.style.border' => [['exclude_unless', 'interactions.*.type', 'button', 'hotspot', 'text'], 'nullable', 'array'], + 'interactions.*.style.border.width' => ['nullable', 'numeric', 'integer', 'min:0'], + 'interactions.*.style.border.style' => ['nullable', 'string', LaravelRule::in(['solid', 'dashed', 'dotted', 'none'])], + 'interactions.*.style.border.color' => ['nullable', 'string'], // Action rules - 'interactions.*.action' => ['nullable', 'array'], + 'interactions.*.action' => ['nullable', 'array'], 'interactions.*.action.type' => ['nullable', 'string', LaravelRule::in(['link', 'time', 'video', 'none'])], 'interactions.*.action.link' => ['nullable', 'string'], 'interactions.*.action.time' => ['nullable', 'numeric'], // Attribute rules - 'interactions.*.attributes' => ['nullable', 'array'], - 'interactions.*.attributes.show_indicator' => ['nullable', 'boolean'], - 'interactions.*.attributes.indicator_color' => ['nullable', 'string'], - 'interactions.*.attributes.blinking_speed' => ['nullable', 'string', LaravelRule::in(['slow', 'normal', 'fast', 'none'])], - 'interactions.*.attributes.options' => ['nullable', 'array'], + 'interactions.*.attributes' => ['nullable', 'array'], + 'interactions.*.attributes.show_indicator' => ['nullable', 'boolean'], + 'interactions.*.attributes.indicator_color' => ['nullable', 'string'], + 'interactions.*.attributes.blinking_speed' => ['nullable', 'string', LaravelRule::in(['slow', 'normal', 'fast', 'none'])], + 'interactions.*.attributes.options' => ['nullable', 'array'], 'interactions.*.attributes.options.menu_button_location' => ['nullable', 'string'], - 'interactions.*.attributes.options.menu_button_name' => ['nullable', 'string'], + 'interactions.*.attributes.options.menu_button_name' => ['nullable', 'string'], // Chapters (nested wildcard) - 'interactions.*.chapters' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'array', 'min:1', 'max:16'], - 'interactions.*.chapters.*.title' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'string'], + 'interactions.*.chapters' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'array', 'min:1', 'max:16'], + 'interactions.*.chapters.*.title' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'string'], 'interactions.*.chapters.*.title_short' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'nullable', 'string'], - 'interactions.*.chapters.*.start_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric', 'min:0'], - 'interactions.*.chapters.*.end_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric'], - 'interactions.*.chapters.*.sort_order' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'integer', 'min:0'], + 'interactions.*.chapters.*.start_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric', 'min:0'], + 'interactions.*.chapters.*.end_time' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'numeric'], + 'interactions.*.chapters.*.sort_order' => [['exclude_unless', 'interactions.*.type', 'chapter'], 'required', 'integer', 'min:0'], ]; // ── Warmup ── diff --git a/tests/InlineMessageParamPhase3aTest.php b/tests/InlineMessageParamPhase3aTest.php index 9e1faff..a99a489 100644 --- a/tests/InlineMessageParamPhase3aTest.php +++ b/tests/InlineMessageParamPhase3aTest.php @@ -17,72 +17,72 @@ dataset('phase3a_methods', [ 'required' => [ fn () => FluentRule::string()->required(message: 'x'), - fn () => FluentRule::string()->required()->message('x'), + fn () => FluentRule::string()->required()->message('x'), ['required' => 'x'], ], 'sometimes' => [ fn () => FluentRule::string()->sometimes(message: 'x'), - fn () => FluentRule::string()->sometimes()->message('x'), + fn () => FluentRule::string()->sometimes()->message('x'), ['sometimes' => 'x'], ], 'filled' => [ fn () => FluentRule::string()->filled(message: 'x'), - fn () => FluentRule::string()->filled()->message('x'), + fn () => FluentRule::string()->filled()->message('x'), ['filled' => 'x'], ], 'present' => [ fn () => FluentRule::field()->present(message: 'x'), - fn () => FluentRule::field()->present()->message('x'), + fn () => FluentRule::field()->present()->message('x'), ['present' => 'x'], ], 'prohibited' => [ fn () => FluentRule::string()->prohibited(message: 'x'), - fn () => FluentRule::string()->prohibited()->message('x'), + fn () => FluentRule::string()->prohibited()->message('x'), ['prohibited' => 'x'], ], 'missing' => [ fn () => FluentRule::string()->missing(message: 'x'), - fn () => FluentRule::string()->missing()->message('x'), + fn () => FluentRule::string()->missing()->message('x'), ['missing' => 'x'], ], 'requiredIfAccepted' => [ fn () => FluentRule::string()->requiredIfAccepted('tos', message: 'x'), - fn () => FluentRule::string()->requiredIfAccepted('tos')->message('x'), + fn () => FluentRule::string()->requiredIfAccepted('tos')->message('x'), ['required_if_accepted' => 'x'], ], 'requiredIfDeclined' => [ fn () => FluentRule::string()->requiredIfDeclined('tos', message: 'x'), - fn () => FluentRule::string()->requiredIfDeclined('tos')->message('x'), + fn () => FluentRule::string()->requiredIfDeclined('tos')->message('x'), ['required_if_declined' => 'x'], ], 'prohibitedIfAccepted' => [ fn () => FluentRule::string()->prohibitedIfAccepted('tos', message: 'x'), - fn () => FluentRule::string()->prohibitedIfAccepted('tos')->message('x'), + fn () => FluentRule::string()->prohibitedIfAccepted('tos')->message('x'), ['prohibited_if_accepted' => 'x'], ], 'prohibitedIfDeclined' => [ fn () => FluentRule::string()->prohibitedIfDeclined('tos', message: 'x'), - fn () => FluentRule::string()->prohibitedIfDeclined('tos')->message('x'), + fn () => FluentRule::string()->prohibitedIfDeclined('tos')->message('x'), ['prohibited_if_declined' => 'x'], ], 'rule-string' => [ fn () => FluentRule::string()->rule('max:100', message: 'x'), - fn () => FluentRule::string()->rule('max:100')->message('x'), + fn () => FluentRule::string()->rule('max:100')->message('x'), ['max' => 'x'], ], 'unique' => [ fn () => FluentRule::string()->unique('users', 'email', message: 'x'), - fn () => FluentRule::string()->unique('users', 'email')->message('x'), + fn () => FluentRule::string()->unique('users', 'email')->message('x'), ['unique' => 'x'], ], 'exists' => [ fn () => FluentRule::string()->exists('users', 'email', message: 'x'), - fn () => FluentRule::string()->exists('users', 'email')->message('x'), + fn () => FluentRule::string()->exists('users', 'email')->message('x'), ['exists' => 'x'], ], 'enum' => [ fn () => FluentRule::string()->enum(TestStringEnum::class, message: 'x'), - fn () => FluentRule::string()->enum(TestStringEnum::class)->message('x'), + fn () => FluentRule::string()->enum(TestStringEnum::class)->message('x'), ['enum' => 'x'], ], 'in' => [ @@ -92,7 +92,7 @@ ], 'notIn' => [ fn () => FluentRule::string()->notIn(['banned'], message: 'x'), - fn () => FluentRule::string()->notIn(['banned'])->message('x'), + fn () => FluentRule::string()->notIn(['banned'])->message('x'), ['not_in' => 'x'], ], ]); diff --git a/tests/InlineMessageParamPhase3bTest.php b/tests/InlineMessageParamPhase3bTest.php index 56ff863..bf2339d 100644 --- a/tests/InlineMessageParamPhase3bTest.php +++ b/tests/InlineMessageParamPhase3bTest.php @@ -13,67 +13,67 @@ dataset('phase3b_string', [ 'alpha' => [ fn () => FluentRule::string()->alpha(message: 'x'), - fn () => FluentRule::string()->alpha()->message('x'), + fn () => FluentRule::string()->alpha()->message('x'), ['alpha' => 'x'], ], 'ascii' => [ fn () => FluentRule::string()->ascii(message: 'x'), - fn () => FluentRule::string()->ascii()->message('x'), + fn () => FluentRule::string()->ascii()->message('x'), ['ascii' => 'x'], ], 'between' => [ fn () => FluentRule::string()->between(2, 10, message: 'x'), - fn () => FluentRule::string()->between(2, 10)->message('x'), + fn () => FluentRule::string()->between(2, 10)->message('x'), ['between' => 'x'], ], 'min' => [ fn () => FluentRule::string()->min(2, message: 'x'), - fn () => FluentRule::string()->min(2)->message('x'), + fn () => FluentRule::string()->min(2)->message('x'), ['min' => 'x'], ], 'max' => [ fn () => FluentRule::string()->max(100, message: 'x'), - fn () => FluentRule::string()->max(100)->message('x'), + fn () => FluentRule::string()->max(100)->message('x'), ['max' => 'x'], ], 'exactly' => [ fn () => FluentRule::string()->exactly(5, message: 'x'), - fn () => FluentRule::string()->exactly(5)->message('x'), + fn () => FluentRule::string()->exactly(5)->message('x'), ['size' => 'x'], ], 'url' => [ fn () => FluentRule::string()->url(message: 'x'), - fn () => FluentRule::string()->url()->message('x'), + fn () => FluentRule::string()->url()->message('x'), ['url' => 'x'], ], 'uuid' => [ fn () => FluentRule::string()->uuid(message: 'x'), - fn () => FluentRule::string()->uuid()->message('x'), + fn () => FluentRule::string()->uuid()->message('x'), ['uuid' => 'x'], ], 'regex' => [ fn () => FluentRule::string()->regex('/^[a-z]+$/', message: 'x'), - fn () => FluentRule::string()->regex('/^[a-z]+$/')->message('x'), + fn () => FluentRule::string()->regex('/^[a-z]+$/')->message('x'), ['regex' => 'x'], ], 'same' => [ fn () => FluentRule::string()->same('other', message: 'x'), - fn () => FluentRule::string()->same('other')->message('x'), + fn () => FluentRule::string()->same('other')->message('x'), ['same' => 'x'], ], 'dateFormat' => [ fn () => FluentRule::string()->dateFormat('Y-m-d', message: 'x'), - fn () => FluentRule::string()->dateFormat('Y-m-d')->message('x'), + fn () => FluentRule::string()->dateFormat('Y-m-d')->message('x'), ['date_format' => 'x'], ], 'confirmed' => [ fn () => FluentRule::string()->confirmed(message: 'x'), - fn () => FluentRule::string()->confirmed()->message('x'), + fn () => FluentRule::string()->confirmed()->message('x'), ['confirmed' => 'x'], ], 'currentPassword' => [ fn () => FluentRule::string()->currentPassword(message: 'x'), - fn () => FluentRule::string()->currentPassword()->message('x'), + fn () => FluentRule::string()->currentPassword()->message('x'), ['current_password' => 'x'], ], ]); @@ -81,27 +81,27 @@ dataset('phase3b_numeric', [ 'min' => [ fn () => FluentRule::numeric()->min(0, message: 'x'), - fn () => FluentRule::numeric()->min(0)->message('x'), + fn () => FluentRule::numeric()->min(0)->message('x'), ['min' => 'x'], ], 'max' => [ fn () => FluentRule::numeric()->max(100, message: 'x'), - fn () => FluentRule::numeric()->max(100)->message('x'), + fn () => FluentRule::numeric()->max(100)->message('x'), ['max' => 'x'], ], 'between' => [ fn () => FluentRule::numeric()->between(0, 100, message: 'x'), - fn () => FluentRule::numeric()->between(0, 100)->message('x'), + fn () => FluentRule::numeric()->between(0, 100)->message('x'), ['between' => 'x'], ], 'integer' => [ fn () => FluentRule::numeric()->integer(message: 'x'), - fn () => FluentRule::numeric()->integer()->message('x'), + fn () => FluentRule::numeric()->integer()->message('x'), ['integer' => 'x'], ], 'decimal' => [ fn () => FluentRule::numeric()->decimal(2, message: 'x'), - fn () => FluentRule::numeric()->decimal(2)->message('x'), + fn () => FluentRule::numeric()->decimal(2)->message('x'), ['decimal' => 'x'], ], 'positive' => [ @@ -116,7 +116,7 @@ ], 'multipleOf' => [ fn () => FluentRule::numeric()->multipleOf(5, message: 'x'), - fn () => FluentRule::numeric()->multipleOf(5)->message('x'), + fn () => FluentRule::numeric()->multipleOf(5)->message('x'), ['multiple_of' => 'x'], ], ]); @@ -161,7 +161,7 @@ ->messageFor('integer', 'Must be a whole number.'); expect($rule->getCustomMessages())->toBe([ - 'digits' => 'Must be 5 digits.', + 'digits' => 'Must be 5 digits.', 'integer' => 'Must be a whole number.', ]); }); diff --git a/tests/InlineMessageParamPhase3cTest.php b/tests/InlineMessageParamPhase3cTest.php index ec2cf71..33d231f 100644 --- a/tests/InlineMessageParamPhase3cTest.php +++ b/tests/InlineMessageParamPhase3cTest.php @@ -12,32 +12,32 @@ dataset('phase3c_array', [ 'min' => [ fn () => FluentRule::array()->min(1, message: 'x'), - fn () => FluentRule::array()->min(1)->message('x'), + fn () => FluentRule::array()->min(1)->message('x'), ['min' => 'x'], ], 'max' => [ fn () => FluentRule::array()->max(10, message: 'x'), - fn () => FluentRule::array()->max(10)->message('x'), + fn () => FluentRule::array()->max(10)->message('x'), ['max' => 'x'], ], 'between' => [ fn () => FluentRule::array()->between(1, 5, message: 'x'), - fn () => FluentRule::array()->between(1, 5)->message('x'), + fn () => FluentRule::array()->between(1, 5)->message('x'), ['between' => 'x'], ], 'exactly' => [ fn () => FluentRule::array()->exactly(3, message: 'x'), - fn () => FluentRule::array()->exactly(3)->message('x'), + fn () => FluentRule::array()->exactly(3)->message('x'), ['size' => 'x'], ], 'list' => [ fn () => FluentRule::array()->list(message: 'x'), - fn () => FluentRule::array()->list()->message('x'), + fn () => FluentRule::array()->list()->message('x'), ['list' => 'x'], ], 'distinct' => [ fn () => FluentRule::array()->distinct(message: 'x'), - fn () => FluentRule::array()->distinct()->message('x'), + fn () => FluentRule::array()->distinct()->message('x'), ['distinct' => 'x'], ], ]); @@ -45,42 +45,42 @@ dataset('phase3c_date', [ 'before' => [ fn () => FluentRule::date()->before('2026-12-31', message: 'x'), - fn () => FluentRule::date()->before('2026-12-31')->message('x'), + fn () => FluentRule::date()->before('2026-12-31')->message('x'), ['before' => 'x'], ], 'after' => [ fn () => FluentRule::date()->after('2020-01-01', message: 'x'), - fn () => FluentRule::date()->after('2020-01-01')->message('x'), + fn () => FluentRule::date()->after('2020-01-01')->message('x'), ['after' => 'x'], ], 'beforeOrEqual' => [ fn () => FluentRule::date()->beforeOrEqual('2026-12-31', message: 'x'), - fn () => FluentRule::date()->beforeOrEqual('2026-12-31')->message('x'), + fn () => FluentRule::date()->beforeOrEqual('2026-12-31')->message('x'), ['before_or_equal' => 'x'], ], 'afterOrEqual' => [ fn () => FluentRule::date()->afterOrEqual('2020-01-01', message: 'x'), - fn () => FluentRule::date()->afterOrEqual('2020-01-01')->message('x'), + fn () => FluentRule::date()->afterOrEqual('2020-01-01')->message('x'), ['after_or_equal' => 'x'], ], 'beforeToday' => [ fn () => FluentRule::date()->beforeToday(message: 'x'), - fn () => FluentRule::date()->beforeToday()->message('x'), + fn () => FluentRule::date()->beforeToday()->message('x'), ['before' => 'x'], ], 'past' => [ fn () => FluentRule::date()->past(message: 'x'), - fn () => FluentRule::date()->past()->message('x'), + fn () => FluentRule::date()->past()->message('x'), ['before' => 'x'], ], 'dateEquals' => [ fn () => FluentRule::date()->dateEquals('2026-04-22', message: 'x'), - fn () => FluentRule::date()->dateEquals('2026-04-22')->message('x'), + fn () => FluentRule::date()->dateEquals('2026-04-22')->message('x'), ['date_equals' => 'x'], ], 'same' => [ fn () => FluentRule::date()->same('other', message: 'x'), - fn () => FluentRule::date()->same('other')->message('x'), + fn () => FluentRule::date()->same('other')->message('x'), ['same' => 'x'], ], ]); @@ -88,22 +88,22 @@ dataset('phase3c_file', [ 'min' => [ fn () => FluentRule::file()->min(100, message: 'x'), - fn () => FluentRule::file()->min(100)->message('x'), + fn () => FluentRule::file()->min(100)->message('x'), ['min' => 'x'], ], 'max' => [ fn () => FluentRule::file()->max('2mb', message: 'x'), - fn () => FluentRule::file()->max('2mb')->message('x'), + fn () => FluentRule::file()->max('2mb')->message('x'), ['max' => 'x'], ], 'between' => [ fn () => FluentRule::file()->between(1, '2mb', message: 'x'), - fn () => FluentRule::file()->between(1, '2mb')->message('x'), + fn () => FluentRule::file()->between(1, '2mb')->message('x'), ['between' => 'x'], ], 'exactly' => [ fn () => FluentRule::file()->exactly(500, message: 'x'), - fn () => FluentRule::file()->exactly(500)->message('x'), + fn () => FluentRule::file()->exactly(500)->message('x'), ['size' => 'x'], ], ]); @@ -161,7 +161,7 @@ expect($rule->getCustomMessages())->toBe([ 'before' => 'Must be before end.', - 'after' => 'Must be after start.', + 'after' => 'Must be after start.', ]); }); diff --git a/tests/InlineMessageParamPhase3dTest.php b/tests/InlineMessageParamPhase3dTest.php index 870ad2f..6385fd7 100644 --- a/tests/InlineMessageParamPhase3dTest.php +++ b/tests/InlineMessageParamPhase3dTest.php @@ -12,22 +12,22 @@ dataset('phase3d_email', [ 'max' => [ fn () => FluentRule::email()->max(255, message: 'x'), - fn () => FluentRule::email()->max(255)->message('x'), + fn () => FluentRule::email()->max(255)->message('x'), ['max' => 'x'], ], 'confirmed' => [ fn () => FluentRule::email()->confirmed(message: 'x'), - fn () => FluentRule::email()->confirmed()->message('x'), + fn () => FluentRule::email()->confirmed()->message('x'), ['confirmed' => 'x'], ], 'same' => [ fn () => FluentRule::email()->same('other', message: 'x'), - fn () => FluentRule::email()->same('other')->message('x'), + fn () => FluentRule::email()->same('other')->message('x'), ['same' => 'x'], ], 'different' => [ fn () => FluentRule::email()->different('other', message: 'x'), - fn () => FluentRule::email()->different('other')->message('x'), + fn () => FluentRule::email()->different('other')->message('x'), ['different' => 'x'], ], ]); @@ -35,7 +35,7 @@ dataset('phase3d_password', [ 'confirmed' => [ fn () => FluentRule::password()->confirmed(message: 'x'), - fn () => FluentRule::password()->confirmed()->message('x'), + fn () => FluentRule::password()->confirmed()->message('x'), ['confirmed' => 'x'], ], ]); @@ -43,12 +43,12 @@ dataset('phase3d_boolean', [ 'accepted' => [ fn () => FluentRule::boolean()->accepted(message: 'x'), - fn () => FluentRule::boolean()->accepted()->message('x'), + fn () => FluentRule::boolean()->accepted()->message('x'), ['accepted' => 'x'], ], 'declined' => [ fn () => FluentRule::boolean()->declined(message: 'x'), - fn () => FluentRule::boolean()->declined()->message('x'), + fn () => FluentRule::boolean()->declined()->message('x'), ['declined' => 'x'], ], ]); @@ -56,17 +56,17 @@ dataset('phase3d_field', [ 'same' => [ fn () => FluentRule::field()->same('other', message: 'x'), - fn () => FluentRule::field()->same('other')->message('x'), + fn () => FluentRule::field()->same('other')->message('x'), ['same' => 'x'], ], 'different' => [ fn () => FluentRule::field()->different('other', message: 'x'), - fn () => FluentRule::field()->different('other')->message('x'), + fn () => FluentRule::field()->different('other')->message('x'), ['different' => 'x'], ], 'confirmed' => [ fn () => FluentRule::field()->confirmed(message: 'x'), - fn () => FluentRule::field()->confirmed()->message('x'), + fn () => FluentRule::field()->confirmed()->message('x'), ['confirmed' => 'x'], ], ]); @@ -142,7 +142,7 @@ it('EmailRule::max(message: ...) surfaces in validation', function (): void { $v = makeValidator( - ['email' => 'a@'.str_repeat('b', 300).'.com'], + ['email' => 'a@' . str_repeat('b', 300) . '.com'], ['email' => FluentRule::email()->max(50, message: 'Too long!')], ); diff --git a/tests/InlineMessageParamPhase3eTest.php b/tests/InlineMessageParamPhase3eTest.php index c0cd254..b02ba5b 100644 --- a/tests/InlineMessageParamPhase3eTest.php +++ b/tests/InlineMessageParamPhase3eTest.php @@ -147,9 +147,9 @@ ->max(255, message: 'Too long.'); expect($rule->getCustomMessages())->toBe([ - 'email' => 'Invalid email.', + 'email' => 'Invalid email.', 'required' => 'Email is required.', - 'max' => 'Too long.', + 'max' => 'Too long.', ]); }); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 780e102..cef62d7 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Translation\ArrayLoader; +use Illuminate\Validation\Validator; use Illuminate\Translation\Translator; +use Illuminate\Translation\ArrayLoader; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Validation\Rule as LaravelRule; -use Illuminate\Validation\Validator; use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; -use Simtabi\Laranail\Validation\FluentRule; // ========================================================================= // Mixed with other rules in an array @@ -102,8 +102,8 @@ it('uses custom error messages from the validator', function (): void { $v = new Validator( new Translator(new ArrayLoader, 'en'), - ['name' => ''], - ['name' => FluentRule::string()->required()], + ['name' => ''], + ['name' => FluentRule::string()->required()], ['name.required' => 'Please enter your name.'], ); diff --git a/tests/Internal/ItemRuleCompilerTest.php b/tests/Internal/ItemRuleCompilerTest.php index 81bd6f8..eced0b0 100644 --- a/tests/Internal/ItemRuleCompilerTest.php +++ b/tests/Internal/ItemRuleCompilerTest.php @@ -131,9 +131,9 @@ $compiler = new ItemRuleCompiler; [$checks, $slowRules] = $compiler->buildFastChecks([ - 'name' => 'required|string', + 'name' => 'required|string', 'email' => 'required|email', - 'meta' => ['required', new In(['a', 'b'])], + 'meta' => ['required', new In(['a', 'b'])], ]); expect($checks)->toHaveCount(2) diff --git a/tests/Internal/ItemValidatorTest.php b/tests/Internal/ItemValidatorTest.php index f9b94bd..934ac6e 100644 --- a/tests/Internal/ItemValidatorTest.php +++ b/tests/Internal/ItemValidatorTest.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation\Tests\Internal; -use Simtabi\Laranail\Validation\Internal\ItemErrorCollector; -use Simtabi\Laranail\Validation\Internal\ItemRuleCompiler; use Simtabi\Laranail\Validation\Internal\ItemValidator; +use Simtabi\Laranail\Validation\Internal\ItemRuleCompiler; +use Simtabi\Laranail\Validation\Internal\ItemErrorCollector; function makeItemValidator(bool $stopOnFirstFailure = false): ItemValidator { @@ -105,7 +105,7 @@ function makeItemValidator(bool $stopOnFirstFailure = false): ItemValidator ['type' => 'draft', 'price' => null], ], itemRules: [ - 'type' => 'required|string', + 'type' => 'required|string', 'price' => [['exclude_unless', 'type', 'product'], 'required', 'numeric'], ], itemMessages: [], diff --git a/tests/Internal/ValidatorStateCopierTest.php b/tests/Internal/ValidatorStateCopierTest.php index d7dacdf..0c1aaf9 100644 --- a/tests/Internal/ValidatorStateCopierTest.php +++ b/tests/Internal/ValidatorStateCopierTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; +use Illuminate\Translation\ArrayLoader; use Illuminate\Validation\Validator as BaseValidator; use Simtabi\Laranail\Validation\Internal\ValidatorStateCopier; /** - * @param array $data - * @param array $rules + * @param array $data + * @param array $rules */ function stateCopierValidator(array $data, array $rules): BaseValidator { diff --git a/tests/ItemExclusionLeakParityTest.php b/tests/ItemExclusionLeakParityTest.php index 9fc85e6..a578ea7 100644 --- a/tests/ItemExclusionLeakParityTest.php +++ b/tests/ItemExclusionLeakParityTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; /** * Exclusions must not carry from one array item to the next. @@ -35,7 +35,8 @@ function itemExclusionRules(): RuleSet } /** - * @param array $data + * @param array $data + * * @return array */ function vanillaExclusionErrors(array $data): array diff --git a/tests/ItemValidatorCustomResolverTest.php b/tests/ItemValidatorCustomResolverTest.php index 05d8562..fc2b3e8 100644 --- a/tests/ItemValidatorCustomResolverTest.php +++ b/tests/ItemValidatorCustomResolverTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Support\Facades\Validator; use Illuminate\Contracts\Translation\Translator; use Illuminate\Contracts\Validation\ValidationRule; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Validator as BaseValidator; -use Simtabi\Laranail\Validation\RuleSet; /** * Regression guard: the per-item slow-rule path must honor a custom @@ -35,10 +35,10 @@ public function validate(string $attribute, mixed $value, Closure $fail): void { } /** - * @param array $data - * @param array $rules - * @param array $messages - * @param array $attributes + * @param array $data + * @param array $rules + * @param array $messages + * @param array $attributes */ function makeCustomResolverValidator(Translator $translator, array $data, array $rules, array $messages = [], array $attributes = []): BaseValidator { @@ -53,7 +53,7 @@ function makeCustomResolverValidator(Translator $translator, array $data, array // The custom Rule object makes `rows.*.a` non-fast-checkable, forcing the // per-item slow path (makeItemValidator) rather than a fast-check closure. $rules = [ - 'rows' => 'required|array', + 'rows' => 'required|array', 'rows.*.a' => ['required', 'string', new CustomResolverAlwaysPassesRule], ]; diff --git a/tests/ItemValidatorFastCheckCacheTest.php b/tests/ItemValidatorFastCheckCacheTest.php index a402c4a..a1a14a0 100644 --- a/tests/ItemValidatorFastCheckCacheTest.php +++ b/tests/ItemValidatorFastCheckCacheTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; // Regression coverage for the per-call buildFastChecks memo ($fastChecksByReduced) // added to ItemValidator's sibling-conditional path. A value-conditional @@ -18,7 +18,8 @@ // correct at volume. /** - * @param list> $items + * @param list> $items + * * @return array> */ function runMemoConditionalItems(array $items): array diff --git a/tests/MemoizingValidatorParityTest.php b/tests/MemoizingValidatorParityTest.php index f153e9c..1b407e6 100644 --- a/tests/MemoizingValidatorParityTest.php +++ b/tests/MemoizingValidatorParityTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Translation\ArrayLoader; -use Illuminate\Translation\Translator; use Illuminate\Validation\Rule; -use Illuminate\Validation\Validator as BaseValidator; +use Illuminate\Translation\Translator; +use Illuminate\Translation\ArrayLoader; use Simtabi\Laranail\Validation\MemoizingValidator; use Simtabi\Laranail\Validation\OptimizedValidator; +use Illuminate\Validation\Validator as BaseValidator; /** * Parity suite for {@see MemoizingValidator}. The class memoizes string-rule @@ -29,8 +29,9 @@ }); /** - * @param array $data - * @param array $rules + * @param array $data + * @param array $rules + * * @return array{0: BaseValidator, 1: MemoizingValidator} */ function memoParityPair(array $data, array $rules): array @@ -187,7 +188,7 @@ function memoParityScenarios(): iterable */ it('caches up to the cap before resetting', function (): void { for ($i = 1; $i <= 1024; $i++) { - [, $validator] = memoParityPair(['f' => 'x'], ['f' => 'max:'.$i]); + [, $validator] = memoParityPair(['f' => 'x'], ['f' => 'max:' . $i]); $validator->passes(); } @@ -198,7 +199,7 @@ function memoParityScenarios(): iterable it('drops the cache after the cap is exceeded', function (): void { for ($i = 1; $i <= 1024; $i++) { - [, $validator] = memoParityPair(['f' => 'x'], ['f' => 'max:'.$i]); + [, $validator] = memoParityPair(['f' => 'x'], ['f' => 'max:' . $i]); $validator->passes(); } diff --git a/tests/NamingConventionTest.php b/tests/NamingConventionTest.php index ee42412..1e8245d 100644 --- a/tests/NamingConventionTest.php +++ b/tests/NamingConventionTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Illuminate\Validation\Factory as ValidationFactory; use Simtabi\Laranail\Validation\BatchDatabaseChecker; +use Illuminate\Validation\Factory as ValidationFactory; // ========================================================================= // Every public name this package registers must carry the vendor AND the @@ -44,7 +44,7 @@ )); expect($generic)->toBeEmpty( - 'bare validator extensions registered: '.implode(', ', $generic), + 'bare validator extensions registered: ' . implode(', ', $generic), ); }); diff --git a/tests/Pest.php b/tests/Pest.php index df73a89..920749c 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -2,23 +2,23 @@ declare(strict_types=1); -use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; -use Illuminate\Translation\ArrayLoader; -use Illuminate\Translation\Translator; use Illuminate\Validation\Validator; +use Illuminate\Translation\Translator; +use Illuminate\Translation\ArrayLoader; +use Illuminate\Foundation\Http\FormRequest; use Simtabi\Laranail\Validation\FluentSchema; use Simtabi\Laranail\Validation\HasFluentRules; use Simtabi\Laranail\Validation\Tests\TestCase; uses(TestCase::class)->in(__DIR__); -require_once __DIR__.'/../src/Testing/PestExpectations.php'; +require_once __DIR__ . '/../src/Testing/PestExpectations.php'; /** - * @param array $data - * @param array $rules + * @param array $data + * @param array $rules */ function makeValidator(array $data, array $rules): Validator { @@ -30,8 +30,8 @@ function makeValidator(array $data, array $rules): Validator } /** - * @param array $rules - * @param array $data + * @param array $rules + * @param array $data */ function createFormRequest(array $rules, array $data): FormRequest { @@ -63,8 +63,8 @@ public function authorize(): bool * Build a FormRequest that defines its rules through the FluentSchema * builder via a schema() method, mirroring createFormRequest(). * - * @param Closure(FluentSchema): array $schema - * @param array $data + * @param Closure(FluentSchema): array $schema + * @param array $data */ function createSchemaFormRequest(Closure $schema, array $data): FormRequest { @@ -98,8 +98,9 @@ public function authorize(): bool * * @template T of FormRequest * - * @param T $formRequest - * @param array $data + * @param T $formRequest + * @param array $data + * * @return T */ function bootFormRequest(FormRequest $formRequest, array $data): FormRequest diff --git a/tests/PhoneRuleTest.php b/tests/PhoneRuleTest.php index 9aa1236..4bbcea8 100644 --- a/tests/PhoneRuleTest.php +++ b/tests/PhoneRuleTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Simtabi\Laranail\Phone\Enums\PhoneNumberType; +use Illuminate\Database\Schema\Blueprint; use Simtabi\Laranail\Validation\FluentRule; +use Simtabi\Laranail\Phone\Enums\PhoneNumberType; use Simtabi\Laranail\Validation\Rules\Telecom\UniquePhone; // ========================================================================= @@ -21,10 +21,10 @@ it('accepts any country by default', function (string $number): void { expect(makeValidator(['phone' => $number], ['phone' => FluentRule::phone()])->passes())->toBeTrue(); })->with([ - 'Kenya' => ['+254712123456'], - 'Türkiye' => ['+905301111111'], + 'Kenya' => ['+254712123456'], + 'Türkiye' => ['+905301111111'], 'United Kingdom' => ['+447400123456'], - 'Brazil' => ['+5511961234567'], + 'Brazil' => ['+5511961234567'], ]); // ========================================================================= @@ -145,10 +145,10 @@ it('normalises what the user actually pasted', function (string $input): void { expect(makeValidator(['phone' => $input], ['phone' => FluentRule::phone()->country('KE')])->passes())->toBeTrue(); })->with([ - 'international' => ['+254712123456'], - 'IDD prefix' => ['00254712123456'], + 'international' => ['+254712123456'], + 'IDD prefix' => ['00254712123456'], 'spaced national' => ['0712 123 456'], - 'punctuated' => ['(0712) 123-456'], + 'punctuated' => ['(0712) 123-456'], // Typed on an Arabic keyboard. None of the surveyed packages handles this. 'Arabic-Indic digits' => ['٠٠٢٥٤٧١٢١٢٣٤٥٦'], ]); diff --git a/tests/PresenceConditionalParityTest.php b/tests/PresenceConditionalParityTest.php index d3bd188..cfb3b7c 100644 --- a/tests/PresenceConditionalParityTest.php +++ b/tests/PresenceConditionalParityTest.php @@ -3,9 +3,9 @@ declare(strict_types=1); use Illuminate\Support\Facades\Lang; -use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; +use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; // ========================================================================= // Phase 1: required_without (single-param) pre-evaluation parity grid. @@ -14,8 +14,9 @@ // ========================================================================= /** - * @param array> $items - * @param array $messages + * @param array> $items + * @param array $messages + * * @return array> */ function runPresenceItems(array $items, array $messages = []): array @@ -80,12 +81,12 @@ function runPresenceItems(array $items, array $messages = []): array it('required_without: verdicts match native Laravel for every shape', function (): void { $shapes = [ ['postcode' => '1234AB', 'birthdate' => '1990-01-01'], - ['postcode' => '1234AB'], + ['postcode' => '1234AB'], ['birthdate' => '1990-01-01'], [], - ['postcode' => null], - ['postcode' => ''], - ['postcode' => []], + ['postcode' => null], + ['postcode' => ''], + ['postcode' => []], ['birthdate' => null], ['birthdate' => ''], ]; @@ -96,7 +97,7 @@ function runPresenceItems(array $items, array $messages = []): array ->check(['addresses' => [$shape]]) ->fails(); - expect($fluent)->toBe($native, 'shape: '.json_encode($shape)); + expect($fluent)->toBe($native, 'shape: ' . json_encode($shape)); } }); @@ -159,17 +160,18 @@ function fieldWithPresenceRule(string $ruleName): FieldRule $field = FluentRule::field(); return match ($ruleName) { - 'required_with' => $field->requiredWith('birthdate'), - 'required_without' => $field->requiredWithout('birthdate'), - 'required_with_all' => $field->requiredWithAll('birthdate'), + 'required_with' => $field->requiredWith('birthdate'), + 'required_without' => $field->requiredWithout('birthdate'), + 'required_with_all' => $field->requiredWithAll('birthdate'), 'required_without_all' => $field->requiredWithoutAll('birthdate'), - default => throw new LogicException('Unknown rule '.$ruleName), + default => throw new LogicException('Unknown rule ' . $ruleName), }; } /** - * @param array> $items - * @param array $messages + * @param array> $items + * @param array $messages + * * @return array> */ function runPresenceItemsForRule(string $ruleName, array $items, array $messages = []): array @@ -220,12 +222,12 @@ function runPresenceItemsForRule(string $ruleName, array $items, array $messages it('phase 2: verdicts match native Laravel across all four rules × shape grid', function (): void { $shapes = [ ['postcode' => '1234AB', 'birthdate' => '1990-01-01'], - ['postcode' => '1234AB'], + ['postcode' => '1234AB'], ['birthdate' => '1990-01-01'], [], - ['postcode' => null], - ['postcode' => ''], - ['postcode' => []], + ['postcode' => null], + ['postcode' => ''], + ['postcode' => []], ['birthdate' => null], ['birthdate' => ' '], ]; @@ -234,14 +236,14 @@ function runPresenceItemsForRule(string $ruleName, array $items, array $messages foreach ($ruleNames as $rule) { foreach ($shapes as $shape) { - $native = validator($shape, ['postcode' => $rule.':birthdate|string'])->fails(); + $native = validator($shape, ['postcode' => $rule . ':birthdate|string'])->fails(); $field = fieldWithPresenceRule($rule)->rule('string'); $fluent = RuleSet::from(['addresses.*.postcode' => $field]) ->check(['addresses' => [$shape]]) ->fails(); - expect($fluent)->toBe($native, 'rule='.$rule.' shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'rule=' . $rule . ' shape=' . json_encode($shape)); } } }); @@ -251,12 +253,12 @@ function runPresenceItemsForRule(string $ruleName, array $items, array $messages foreach ($ruleNames as $ruleName) { Lang::addLines([ - 'validation.custom.addresses.*.postcode.'.$ruleName => 'Custom '.$ruleName, + 'validation.custom.addresses.*.postcode.' . $ruleName => 'Custom ' . $ruleName, ], 'en'); // Build a shape that definitely activates each rule → target missing → error must mention the preserved rule name. $shape = match ($ruleName) { - 'required_with', 'required_with_all' => ['birthdate' => '1990-01-01'], + 'required_with', 'required_with_all' => ['birthdate' => '1990-01-01'], 'required_without', 'required_without_all' => [], }; @@ -276,18 +278,18 @@ function runPresenceItemsForRule(string $ruleName, array $items, array $messages /** * Build a FieldRule with a multi-param presence conditional. * - * @param list $params + * @param list $params */ function fieldWithMultiParamPresenceRule(string $ruleName, array $params): FieldRule { $field = FluentRule::field(); return match ($ruleName) { - 'required_with' => $field->requiredWith(...$params), - 'required_without' => $field->requiredWithout(...$params), - 'required_with_all' => $field->requiredWithAll(...$params), + 'required_with' => $field->requiredWith(...$params), + 'required_without' => $field->requiredWithout(...$params), + 'required_with_all' => $field->requiredWithAll(...$params), 'required_without_all' => $field->requiredWithoutAll(...$params), - default => throw new LogicException('Unknown rule '.$ruleName), + default => throw new LogicException('Unknown rule ' . $ruleName), }; } @@ -301,23 +303,23 @@ function fieldWithMultiParamPresenceRule(string $ruleName, array $params): Field ['b' => 'B'], // one sibling present, target absent [], // none present ['postcode' => 'X'], // only target present - ['a' => '', 'b' => 'B'], // one sibling empty-string - ['a' => null, 'b' => 'B'], // one sibling null - ['a' => ' ', 'b' => 'B'], // one sibling whitespace-only + ['a' => '', 'b' => 'B'], // one sibling empty-string + ['a' => null, 'b' => 'B'], // one sibling null + ['a' => ' ', 'b' => 'B'], // one sibling whitespace-only ]; $ruleNames = ['required_with', 'required_without', 'required_with_all', 'required_without_all']; foreach ($ruleNames as $rule) { foreach ($shapes as $shape) { - $native = validator($shape, ['postcode' => $rule.':a,b|string'])->fails(); + $native = validator($shape, ['postcode' => $rule . ':a,b|string'])->fails(); $field = fieldWithMultiParamPresenceRule($rule, ['a', 'b'])->rule('string'); $fluent = RuleSet::from(['items.*.postcode' => $field]) ->check(['items' => [$shape]]) ->fails(); - expect($fluent)->toBe($native, 'rule='.$rule.' shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'rule=' . $rule . ' shape=' . json_encode($shape)); } } }); @@ -386,7 +388,8 @@ function fieldWithMultiParamPresenceRule(string $ruleName, array $params): Field * Uses the raw-rule form (not FluentRule) so exotic parameter shapes reach * the parser verbatim. * - * @param array> $items + * @param array> $items + * * @return array{native: array, fluent: array} */ function parityForRawPresenceRule(string $ruleString, array $items): array @@ -428,7 +431,7 @@ static function (array $item) use ($ruleString): bool { $items = [ ['postcode' => 'X', 'birthdate' => '1990-01-01'], // birthdate present → rule active ['birthdate' => '1990-01-01'], // target absent, rule active → fail - ['postcode' => 'X'], // birthdate absent, but null slot matches full item → present + ['postcode' => 'X'], // birthdate absent, but null slot matches full item → present [], // fully empty item ]; @@ -440,7 +443,7 @@ static function (array $item) use ($ruleString): bool { $items = [ ['postcode' => 'X', 'birthdate' => '1990-01-01'], ['birthdate' => '1990-01-01'], - ['postcode' => 'X'], + ['postcode' => 'X'], [], ]; @@ -455,7 +458,7 @@ static function (array $item) use ($ruleString): bool { $items = [ ['postcode' => 'X', 'a,b' => 1, 'c' => 1], ['a,b' => 1], - ['c' => 1], + ['c' => 1], [], ]; diff --git a/tests/ProhibitedConditionalParityTest.php b/tests/ProhibitedConditionalParityTest.php index f8b0278..a7fa3e0 100644 --- a/tests/ProhibitedConditionalParityTest.php +++ b/tests/ProhibitedConditionalParityTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; use Symfony\Component\HttpFoundation\File\File; // ========================================================================= @@ -50,7 +50,7 @@ $fluent = RuleSet::from(['items.*.forbidden' => FluentRule::field()->prohibited()]) ->check(['items' => [$shape]]) ->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -71,7 +71,7 @@ foreach ([['field' => 'X'], ['field' => null], ['field' => ''], ['field' => []], []] as $shape) { $native = validator($shape, ['field' => 'prohibited|required'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -81,7 +81,7 @@ foreach ([['field' => 'yes'], ['field' => null], ['field' => ''], ['field' => 'random']] as $shape) { $native = validator($shape, ['field' => 'prohibited|accepted'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -91,7 +91,7 @@ foreach ([['field' => 'no'], ['field' => null], ['field' => ''], ['field' => 'random']] as $shape) { $native = validator($shape, ['field' => 'prohibited|declined'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -104,7 +104,7 @@ foreach ([['field' => null], [], ['field' => ''], ['field' => ' ']] as $shape) { $native = validator($shape, ['field' => 'prohibited|string|max:10'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } // Non-empty value: prohibited fails regardless of string/max. @@ -131,7 +131,7 @@ foreach ([[], ['other' => 'X'], ['field' => null, 'other' => 'X'], ['field' => 'X', 'other' => 'X']] as $shape) { $native = validator($shape, ['field' => 'prohibited|same:other'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -141,7 +141,7 @@ foreach ([[], ['other' => 'X'], ['field' => null, 'other' => 'X'], ['field' => 'Y', 'other' => 'X']] as $shape) { $native = validator($shape, ['field' => 'prohibited|different:other'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); @@ -156,7 +156,7 @@ ] as $shape) { $native = validator($shape, ['field' => 'prohibited|after:start'])->fails(); $fluent = $ruleSet->check(['items' => [$shape]])->fails(); - expect($fluent)->toBe($native, 'shape='.json_encode($shape)); + expect($fluent)->toBe($native, 'shape=' . json_encode($shape)); } }); diff --git a/tests/ProviderLayoutTest.php b/tests/ProviderLayoutTest.php index aae9fb0..79ab00e 100644 --- a/tests/ProviderLayoutTest.php +++ b/tests/ProviderLayoutTest.php @@ -36,7 +36,7 @@ function providerFiles(string $src): array it('keeps every service provider in a Providers directory', function (): void { $offenders = []; - foreach (providerFiles(__DIR__.'/../src') as $file) { + foreach (providerFiles(__DIR__ . '/../src') as $file) { if (basename($file->getPath()) !== 'Providers') { $offenders[] = $file->getFilename(); } @@ -48,7 +48,7 @@ function providerFiles(string $src): array it('ends every provider namespace in Providers', function (): void { $checked = 0; - foreach (providerFiles(__DIR__.'/../src') as $file) { + foreach (providerFiles(__DIR__ . '/../src') as $file) { // The directory and the namespace are separate facts -- PSR-4 makes them agree only if the // file declares the namespace its path implies, and a bad move can leave them disagreeing. $source = file_get_contents($file->getPathname()); diff --git a/tests/PublicApiBoundaryTest.php b/tests/PublicApiBoundaryTest.php index 20fdc92..ee24ff8 100644 --- a/tests/PublicApiBoundaryTest.php +++ b/tests/PublicApiBoundaryTest.php @@ -47,9 +47,9 @@ it('marks every Internal\\ and FastCheck\\ class @internal', function (): void { $globs = [ - glob(dirname(__DIR__).'/src/Internal/*.php'), - glob(dirname(__DIR__).'/src/FastCheck/*.php'), - glob(dirname(__DIR__).'/src/FastCheck/Shared/*.php'), + glob(dirname(__DIR__) . '/src/Internal/*.php'), + glob(dirname(__DIR__) . '/src/FastCheck/*.php'), + glob(dirname(__DIR__) . '/src/FastCheck/Shared/*.php'), ]; $files = array_merge(...array_map( static fn (array|false $found): array => $found === false ? [] : $found, @@ -60,22 +60,22 @@ foreach ($files as $file) { expect(str_contains((string) file_get_contents($file), '@internal'))->toBeTrue( - basename($file).' lives in an internal namespace but is not marked @internal.', + basename($file) . ' lives in an internal namespace but is not marked @internal.', ); } }); it('marks every top-level optimizer class @internal', function (): void { foreach (INTERNAL_TOP_LEVEL as $path) { - expect(str_contains((string) file_get_contents(dirname(__DIR__).'/'.$path), '@internal'))->toBeTrue( - $path.' is optimizer machinery and must carry @internal.', + expect(str_contains((string) file_get_contents(dirname(__DIR__) . '/' . $path), '@internal'))->toBeTrue( + $path . ' is optimizer machinery and must carry @internal.', ); } }); it('never marks the stable surface @internal at class level', function (): void { foreach (STABLE_SURFACE as $path) { - $source = (string) file_get_contents(dirname(__DIR__).'/'.$path); + $source = (string) file_get_contents(dirname(__DIR__) . '/' . $path); // Only the CLASS-level docblock counts: a stable class may still // mark an individual method @internal. Inspect the source up to the @@ -85,7 +85,7 @@ $head = $offset === null ? $source : substr($source, 0, $offset); expect(str_contains($head, '@internal'))->toBeFalse( - $path.' is on the §12.1 stable list — a class-level @internal there breaks the SemVer promise.', + $path . ' is on the §12.1 stable list — a class-level @internal there breaks the SemVer promise.', ); } }); diff --git a/tests/RegexBuilderTest.php b/tests/RegexBuilderTest.php index f08c5ea..51e922c 100644 --- a/tests/RegexBuilderTest.php +++ b/tests/RegexBuilderTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\Regex; use Illuminate\Support\Facades\Validator; use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\Regex; /** * The fluent regex builder (§6.9) — safe-by-construction patterns for the diff --git a/tests/RegexWithPipeTest.php b/tests/RegexWithPipeTest.php index 8f77906..32b3b49 100644 --- a/tests/RegexWithPipeTest.php +++ b/tests/RegexWithPipeTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; // ========================================================================= // A regex rule whose pattern contains a literal `|` (e.g. `regex:/^(a|b)$/`) @@ -75,18 +75,18 @@ it('every typed builder falls back to array form when a rule contains a pipe', function (string $factory): void { $rule = match ($factory) { - 'string' => FluentRule::string(), - 'numeric' => FluentRule::numeric(), - 'integer' => FluentRule::integer(), - 'date' => FluentRule::date(), - 'boolean' => FluentRule::boolean(), - 'array' => FluentRule::array(), - 'file' => FluentRule::file(), - 'image' => FluentRule::image(), + 'string' => FluentRule::string(), + 'numeric' => FluentRule::numeric(), + 'integer' => FluentRule::integer(), + 'date' => FluentRule::date(), + 'boolean' => FluentRule::boolean(), + 'array' => FluentRule::array(), + 'file' => FluentRule::file(), + 'image' => FluentRule::image(), 'password' => FluentRule::password(), - 'email' => FluentRule::email(), + 'email' => FluentRule::email(), 'accepted' => FluentRule::accepted(), - default => FluentRule::field(), + default => FluentRule::field(), }; expect($rule->rule('regex:/^(foo|bar)$/')->compiledRules())->toBeArray(); diff --git a/tests/RequiredConditionalNullableTest.php b/tests/RequiredConditionalNullableTest.php index 3644411..27e8130 100644 --- a/tests/RequiredConditionalNullableTest.php +++ b/tests/RequiredConditionalNullableTest.php @@ -3,9 +3,9 @@ declare(strict_types=1); use Illuminate\Validation\Rule; -use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; +use Simtabi\Laranail\Validation\Contracts\FluentRuleContract; // ========================================================================= // Regression matrix: a conditional-required modifier combined with nullable() @@ -73,21 +73,21 @@ function conditionalRequiredFamilies(): array // ---- field-value string forms ----------------------------------- ['requiredIf(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredIf('role', 'admin'), - 'required_if:role,admin', [['active', ['role' => 'admin']], ['inactive', ['role' => 'guest']]]], + 'required_if:role,admin', [['active', ['role' => 'admin']], ['inactive', ['role' => 'guest']]]], ['requiredUnless(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredUnless('role', 'admin'), - 'required_unless:role,admin', [['active', ['role' => 'guest']], ['inactive', ['role' => 'admin']]]], + 'required_unless:role,admin', [['active', ['role' => 'guest']], ['inactive', ['role' => 'admin']]]], ['requiredWith(other)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredWith('other'), - 'required_with:other', [['active', ['other' => 'y']], ['inactive', []]]], + 'required_with:other', [['active', ['other' => 'y']], ['inactive', []]]], ['requiredWithAll(a,b)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredWithAll('a', 'b'), - 'required_with_all:a,b', [['active', ['a' => 1, 'b' => 2]], ['inactive', ['a' => 1]]]], + 'required_with_all:a,b', [['active', ['a' => 1, 'b' => 2]], ['inactive', ['a' => 1]]]], ['requiredWithout(other)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredWithout('other'), - 'required_without:other', [['active', []], ['inactive', ['other' => 'y']]]], + 'required_without:other', [['active', []], ['inactive', ['other' => 'y']]]], ['requiredWithoutAll(a,b)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredWithoutAll('a', 'b'), - 'required_without_all:a,b', [['active', []], ['inactive', ['a' => 1]]]], + 'required_without_all:a,b', [['active', []], ['inactive', ['a' => 1]]]], ['requiredIfAccepted(terms)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredIfAccepted('terms'), - 'required_if_accepted:terms', [['active', ['terms' => 'yes']], ['inactive', ['terms' => 'no']]]], + 'required_if_accepted:terms', [['active', ['terms' => 'yes']], ['inactive', ['terms' => 'no']]]], ['requiredIfDeclined(terms)', static fn (FluentRuleContract $r): FluentRuleContract => $r->requiredIfDeclined('terms'), - 'required_if_declined:terms', [['active', ['terms' => 'no']], ['inactive', ['terms' => 'yes']]]], + 'required_if_declined:terms', [['active', ['terms' => 'no']], ['inactive', ['terms' => 'yes']]]], ]; } @@ -99,10 +99,10 @@ function conditionalRequiredFamilies(): array function conditionalFieldValueStates(): array { return [ - 'absent' => FIELD_ABSENT, - 'null' => null, + 'absent' => FIELD_ABSENT, + 'null' => null, 'empty-string' => '', - 'valid' => 'x', + 'valid' => 'x', ]; } @@ -123,23 +123,23 @@ function presenceForcingFamilies(): array ['present', static fn (FluentRuleContract $r): FluentRuleContract => $r->present(), 'present', [['always', []]]], ['presentIf(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->presentIf('role', 'admin'), - 'present_if:role,admin', [['trigger', ['role' => 'admin']], ['no-trigger', ['role' => 'guest']]]], + 'present_if:role,admin', [['trigger', ['role' => 'admin']], ['no-trigger', ['role' => 'guest']]]], ['presentUnless(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->presentUnless('role', 'admin'), - 'present_unless:role,admin', [['trigger', ['role' => 'guest']], ['no-trigger', ['role' => 'admin']]]], + 'present_unless:role,admin', [['trigger', ['role' => 'guest']], ['no-trigger', ['role' => 'admin']]]], ['presentWith(other)', static fn (FluentRuleContract $r): FluentRuleContract => $r->presentWith('other'), - 'present_with:other', [['trigger', ['other' => 'y']], ['no-trigger', []]]], + 'present_with:other', [['trigger', ['other' => 'y']], ['no-trigger', []]]], ['presentWithAll(a,b)', static fn (FluentRuleContract $r): FluentRuleContract => $r->presentWithAll('a', 'b'), - 'present_with_all:a,b', [['trigger', ['a' => 1, 'b' => 2]], ['no-trigger', ['a' => 1]]]], + 'present_with_all:a,b', [['trigger', ['a' => 1, 'b' => 2]], ['no-trigger', ['a' => 1]]]], ['missing', static fn (FluentRuleContract $r): FluentRuleContract => $r->missing(), 'missing', [['always', []]]], ['missingIf(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->missingIf('role', 'admin'), - 'missing_if:role,admin', [['trigger', ['role' => 'admin']], ['no-trigger', ['role' => 'guest']]]], + 'missing_if:role,admin', [['trigger', ['role' => 'admin']], ['no-trigger', ['role' => 'guest']]]], ['missingUnless(role,admin)', static fn (FluentRuleContract $r): FluentRuleContract => $r->missingUnless('role', 'admin'), - 'missing_unless:role,admin', [['trigger', ['role' => 'guest']], ['no-trigger', ['role' => 'admin']]]], + 'missing_unless:role,admin', [['trigger', ['role' => 'guest']], ['no-trigger', ['role' => 'admin']]]], ['missingWith(other)', static fn (FluentRuleContract $r): FluentRuleContract => $r->missingWith('other'), - 'missing_with:other', [['trigger', ['other' => 'y']], ['no-trigger', []]]], + 'missing_with:other', [['trigger', ['other' => 'y']], ['no-trigger', []]]], ['missingWithAll(a,b)', static fn (FluentRuleContract $r): FluentRuleContract => $r->missingWithAll('a', 'b'), - 'missing_with_all:a,b', [['trigger', ['a' => 1, 'b' => 2]], ['no-trigger', ['a' => 1]]]], + 'missing_with_all:a,b', [['trigger', ['a' => 1, 'b' => 2]], ['no-trigger', ['a' => 1]]]], // Prohibited family — excluded from the presence-forcing set on purpose: // it is satisfied by an empty/null value, so the nullable short-circuit @@ -156,7 +156,8 @@ function presenceForcingFamilies(): array /** * Expand a family list into a flat, labelled case map. * - * @param list}>}> $families + * @param list}>}> $families + * * @return array, modifier: Closure}> */ function expandNullableCases(array $families): array @@ -178,9 +179,9 @@ function expandNullableCases(array $families): array $label = "{$familyLabel} | {$nullableLabel} | {$contextLabel} | {$valueLabel}"; $cases[$label] = [ - 'native' => $native, + 'native' => $native, 'nullable' => $withNullable, - 'data' => $data, + 'data' => $data, 'modifier' => $modifier, ]; } @@ -238,7 +239,7 @@ function buildConditionalRule(Closure $modifier, bool $nullable): FluentRuleCont * self-validation (the bug site) and the compiled HasFluentRules pipeline — * and that both match native Laravel. Each path gets a fresh rule instance. * - * @param array{native: string|object, nullable: bool, data: array, modifier: Closure} $case + * @param array{native: string|object, nullable: bool, data: array, modifier: Closure} $case */ function assertNullableParityAcrossPaths(array $case): void { @@ -276,11 +277,11 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'missing' => [null, false, true], - 'null' => [null, true, true], - 'empty string' => ['', true, true], + 'missing' => [null, false, true], + 'null' => [null, true, true], + 'empty string' => ['', true, true], 'invalid email' => ['nope', true, true], - 'valid email' => ['a@b.com', true, false], + 'valid email' => ['a@b.com', true, false], ]); it('requiredIf(false)->nullable() stays optional', function (mixed $value, bool $present, bool $shouldFail): void { @@ -290,11 +291,11 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'missing' => [null, false, false], - 'null' => [null, true, false], - 'empty string' => ['', true, false], + 'missing' => [null, false, false], + 'null' => [null, true, false], + 'empty string' => ['', true, false], 'invalid email still fails type' => ['nope', true, true], - 'valid email' => ['a@b.com', true, false], + 'valid email' => ['a@b.com', true, false], ]); it('string-form requiredWith->nullable() enforces when the trigger is present', function (array $data, bool $shouldFail): void { @@ -304,11 +305,11 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ 'trigger present, field missing' => [['other' => 'y'], true], - 'trigger present, field null' => [['other' => 'y', 'field' => null], true], - 'trigger present, field empty' => [['other' => 'y', 'field' => ''], true], - 'trigger present, field set' => [['other' => 'y', 'field' => 'x'], false], - 'trigger absent, field missing' => [[], false], - 'trigger absent, field null' => [['field' => null], false], + 'trigger present, field null' => [['other' => 'y', 'field' => null], true], + 'trigger present, field empty' => [['other' => 'y', 'field' => ''], true], + 'trigger present, field set' => [['other' => 'y', 'field' => 'x'], false], + 'trigger absent, field missing' => [[], false], + 'trigger absent, field null' => [['field' => null], false], ]); it('string-form requiredIfAccepted->nullable() enforces when accepted', function (array $data, bool $shouldFail): void { @@ -317,11 +318,11 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'accepted, sig missing' => [['terms' => 'yes'], true], - 'accepted, sig null' => [['terms' => 'yes', 'sig' => null], true], - 'accepted, sig set' => [['terms' => 'yes', 'sig' => 'signed'], false], + 'accepted, sig missing' => [['terms' => 'yes'], true], + 'accepted, sig null' => [['terms' => 'yes', 'sig' => null], true], + 'accepted, sig set' => [['terms' => 'yes', 'sig' => 'signed'], false], 'not accepted, sig missing' => [['terms' => 'no'], false], - 'not accepted, sig null' => [['terms' => 'no', 'sig' => null], false], + 'not accepted, sig null' => [['terms' => 'no', 'sig' => null], false], ]); // ------------------------------------------------------------------------- @@ -357,8 +358,8 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'key absent' => [[], true], - 'key present, null' => [['field' => null], false], + 'key absent' => [[], true], + 'key present, null' => [['field' => null], false], 'key present, value' => [['field' => 'x'], false], ]); @@ -368,8 +369,8 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'key absent' => [[], false], - 'key present, null' => [['field' => null], true], + 'key absent' => [[], false], + 'key present, null' => [['field' => null], true], 'key present, empty' => [['field' => ''], true], 'key present, value' => [['field' => 'x'], false], ]); @@ -380,8 +381,8 @@ function assertNullableParityAcrossPaths(array $case): void expect($validator->fails())->toBe($shouldFail); })->with([ - 'key absent' => [[], false], - 'key present, null' => [['field' => null], true], + 'key absent' => [[], false], + 'key present, null' => [['field' => null], true], 'key present, value' => [['field' => 'x'], true], ]); @@ -397,9 +398,9 @@ function assertNullableParityAcrossPaths(array $case): void ->and(makeValidator($data, ['field' => $rule])->fails())->toBe($shouldFail) ->and(RuleSet::from(['field' => $rule])->check($data)->fails())->toBe($shouldFail); })->with([ - 'null value' => [['field' => null], false], - 'missing field' => [[], false], - 'valid array' => [['field' => ['name' => 'x']], false], + 'null value' => [['field' => null], false], + 'missing field' => [[], false], + 'valid array' => [['field' => ['name' => 'x']], false], 'array missing required key' => [['field' => ['other' => 'x']], true], ]); @@ -423,11 +424,11 @@ function nestedNullableCases(): array $eachRequiredNative = ['obj' => ['nullable', 'array'], 'obj.*.id' => ['integer', 'required']]; return [ - 'children required, parent null' => [$childrenRequired, $childrenRequiredNative, ['obj' => null]], - 'children required, parent absent' => [$childrenRequired, $childrenRequiredNative, []], - 'children required, parent valid' => [$childrenRequired, $childrenRequiredNative, ['obj' => ['id' => 1]]], + 'children required, parent null' => [$childrenRequired, $childrenRequiredNative, ['obj' => null]], + 'children required, parent absent' => [$childrenRequired, $childrenRequiredNative, []], + 'children required, parent valid' => [$childrenRequired, $childrenRequiredNative, ['obj' => ['id' => 1]]], 'children required, parent missing key' => [$childrenRequired, $childrenRequiredNative, ['obj' => ['other' => 1]]], - 'children nullable child, parent null' => [ + 'children nullable child, parent null' => [ static fn (): FluentRuleContract => FluentRule::array()->nullable()->children(['id' => FluentRule::integer()->nullable()]), ['obj' => ['nullable', 'array'], 'obj.id' => ['integer', 'nullable']], ['obj' => null], @@ -437,9 +438,9 @@ function nestedNullableCases(): array ['obj' => ['present', 'nullable', 'array'], 'obj.id' => ['integer', 'required']], ['obj' => null], ], - 'each required, parent null' => [$eachRequired, $eachRequiredNative, ['obj' => null]], - 'each required, parent empty array' => [$eachRequired, $eachRequiredNative, ['obj' => []]], - 'each required, parent valid' => [$eachRequired, $eachRequiredNative, ['obj' => [['id' => 1]]]], + 'each required, parent null' => [$eachRequired, $eachRequiredNative, ['obj' => null]], + 'each required, parent empty array' => [$eachRequired, $eachRequiredNative, ['obj' => []]], + 'each required, parent valid' => [$eachRequired, $eachRequiredNative, ['obj' => [['id' => 1]]]], 'each required, element missing key' => [$eachRequired, $eachRequiredNative, ['obj' => [['other' => 1]]]], ]; } diff --git a/tests/RuleAliasesTest.php b/tests/RuleAliasesTest.php index b7cb8c6..57e4d66 100644 --- a/tests/RuleAliasesTest.php +++ b/tests/RuleAliasesTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Auth\User; use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Support\RuleAliases; +use Illuminate\Contracts\Validation\Factory as ValidationFactory; +use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; /** * Rule families whose aliases ship with their own package rather than here. @@ -31,19 +31,19 @@ function sampleParameters(): array { return [ - 'models_exist' => [User::class], - 'authorized' => ['do-something', User::class], - 'parity' => ['even'], - 'vendor_identifier' => ['aws_region'], + 'models_exist' => [User::class], + 'authorized' => ['do-something', User::class], + 'parity' => ['even'], + 'vendor_identifier' => ['aws_region'], 'national_identifier' => ['nl'], - 'minute_in' => ['0', '15', '30', '45'], + 'minute_in' => ['0', '15', '30', '45'], 'max_date_difference' => ['48', '2026-08-24 00:00:00'], - 'minimum_age' => ['18'], - 'compare_to_column' => ['posts', 'owner_id', 'gte', 'id', '1'], + 'minimum_age' => ['18'], + 'compare_to_column' => ['posts', 'owner_id', 'gte', 'id', '1'], 'file_exists_on_disk' => ['local'], - 'hash_digest' => ['sha256'], - 'max_words' => ['200'], - 'min_words' => ['2'], + 'hash_digest' => ['sha256'], + 'max_words' => ['200'], + 'min_words' => ['2'], ]; } @@ -191,7 +191,7 @@ function ruleClasses(): array } } - expect($drifted)->toBeEmpty('aliases whose defaults differ from the rule: '.implode(', ', $drifted)); + expect($drifted)->toBeEmpty('aliases whose defaults differ from the rule: ' . implode(', ', $drifted)); }); it('refuses a database alias whose model parameter is not a model', function (): void { @@ -227,5 +227,5 @@ function ruleClasses(): array } } - expect($missing)->toBeEmpty('rules with no alias and no stated reason: '.implode(', ', $missing)); + expect($missing)->toBeEmpty('rules with no alias and no stated reason: ' . implode(', ', $missing)); }); diff --git a/tests/RuleConfigBuilderTest.php b/tests/RuleConfigBuilderTest.php index 3f35602..39c5359 100644 --- a/tests/RuleConfigBuilderTest.php +++ b/tests/RuleConfigBuilderTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Simtabi\Laranail\Validation\FastCheck\CoreValueCompiler; -use Simtabi\Laranail\Validation\FastCheck\ItemContextCompiler; use Simtabi\Laranail\Validation\FastCheck\RuleConfigBuilder; +use Simtabi\Laranail\Validation\FastCheck\ItemContextCompiler; /** * Direct unit coverage for {@see RuleConfigBuilder} and the bailout diff --git a/tests/RuleMessagesResolveTest.php b/tests/RuleMessagesResolveTest.php index 4d86273..9aa8662 100644 --- a/tests/RuleMessagesResolveTest.php +++ b/tests/RuleMessagesResolveTest.php @@ -4,9 +4,9 @@ namespace Simtabi\Laranail\Validation\Tests; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; use SplFileInfo; +use RecursiveIteratorIterator; +use RecursiveDirectoryIterator; /** * Every message key a rule can emit must resolve to a real sentence. @@ -82,16 +82,16 @@ public function test_the_namespace_is_the_composer_package_name(): void */ public function test_every_shipped_locale_is_complete(): void { - $root = dirname(__DIR__).'/resources/lang'; - $reference = require $root.'/en/validation.php'; + $root = dirname(__DIR__) . '/resources/lang'; + $reference = require $root . '/en/validation.php'; $this->assertIsArray($reference); - $locales = glob($root.'/*', GLOB_ONLYDIR); + $locales = glob($root . '/*', GLOB_ONLYDIR); $this->assertNotFalse($locales); foreach ($locales as $localeDir) { $locale = basename($localeDir); - $messages = require $localeDir.'/validation.php'; + $messages = require $localeDir . '/validation.php'; $this->assertIsArray($messages, "{$locale}/validation.php did not return an array."); $missing = array_diff(array_keys($reference), array_keys($messages)); @@ -121,7 +121,7 @@ public function test_no_message_is_left_as_a_placeholder(): void private function keysReferencedInSource(): array { $keys = []; - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__DIR__).'/src')); + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__DIR__) . '/src')); foreach ($files as $file) { // RecursiveIteratorIterator is typed as yielding mixed, and the diff --git a/tests/RuleRegistrarTest.php b/tests/RuleRegistrarTest.php index e807a40..4bdf50b 100644 --- a/tests/RuleRegistrarTest.php +++ b/tests/RuleRegistrarTest.php @@ -2,16 +2,16 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\ValidationRule; +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Contracts\ClientCheckable; -use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; +use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Rules\Banking\Iban; use Simtabi\Laranail\Validation\Rules\Telecom\Phone; -use Simtabi\Laranail\Validation\Rules\Text\Slug; -use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\Support\RuleRegistrar; +use Simtabi\Laranail\Validation\Contracts\ClientCheckable; use Simtabi\Laranail\Validation\Tests\Fixtures\Registry\EvenNumber; +use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; /** * The §5.2.2 registry — one place the alias map, the console command, the diff --git a/tests/RuleSetEventsTest.php b/tests/RuleSetEventsTest.php index 09dfd8e..16d3da9 100644 --- a/tests/RuleSetEventsTest.php +++ b/tests/RuleSetEventsTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); +use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Event; +use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Validation\ValidationException; -use Illuminate\Validation\Validator; use Simtabi\Laranail\Validation\Events\RuleSetCompiling; -use Simtabi\Laranail\Validation\Events\ValidationCompleted; use Simtabi\Laranail\Validation\Events\ValidationFailed; use Simtabi\Laranail\Validation\Events\ValidationStarting; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Events\ValidationCompleted; /** * The event vocabulary and closure hooks of §5.6 — the seams a consumer diff --git a/tests/RuleSetModifyEachTest.php b/tests/RuleSetModifyEachTest.php index e808b88..45c0b8d 100644 --- a/tests/RuleSetModifyEachTest.php +++ b/tests/RuleSetModifyEachTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; use Simtabi\Laranail\Validation\Builder\Nodes\FieldRule; use Simtabi\Laranail\Validation\Exceptions\CannotExtendListShapedEach; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\RuleSet; // ========================================================================= // 1.24.0 — RuleSet::modifyEach / modifyChildren sugar. diff --git a/tests/RuleSetParityHarnessTest.php b/tests/RuleSetParityHarnessTest.php index ad2af34..f218ea2 100644 --- a/tests/RuleSetParityHarnessTest.php +++ b/tests/RuleSetParityHarnessTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\RuleSet; /** * End-to-end fast-check parity harness: every cell runs the same rules and @@ -52,7 +52,7 @@ function assertVerdictParity(string $rule, mixed $value): void { $shapes = [ 'top-level' => [['f' => $rule], ['f' => $value]], - 'wildcard' => [['items.*.f' => $rule], ['items' => [['f' => $value], ['f' => $value]]]], + 'wildcard' => [['items.*.f' => $rule], ['items' => [['f' => $value], ['f' => $value]]]], ]; foreach ($shapes as $shape => [$rules, $data]) { @@ -163,7 +163,7 @@ function ruleSetParityGrid(): iterable { foreach (harnessRules() as $rule) { foreach (harnessValues() as $i => $value) { - yield "{$rule} :: value #{$i} ".var_export($value, true) => [$rule, $value]; + yield "{$rule} :: value #{$i} " . var_export($value, true) => [$rule, $value]; } } } @@ -184,7 +184,7 @@ function ruleSetParityGrid(): iterable */ it('P1: regex fast path fails closed when PCRE aborts, matching Laravel', function (): void { $pattern = '/^(a+)+$/'; - $value = str_repeat('a', 200).'!'; + $value = str_repeat('a', 200) . '!'; $original = (string) ini_get('pcre.backtrack_limit'); ini_set('pcre.backtrack_limit', '100'); @@ -196,10 +196,10 @@ function ruleSetParityGrid(): iterable ->and(preg_last_error())->not->toBe(PREG_NO_ERROR); // Laravel rejects on PCRE error… - expect(harnessVanillaVerdict(['f' => 'required|string|regex:'.$pattern], ['f' => $value]))->toBeFalse(); + expect(harnessVanillaVerdict(['f' => 'required|string|regex:' . $pattern], ['f' => $value]))->toBeFalse(); // …and the optimized pipeline must agree, in both shapes. - assertVerdictParity('required|string|regex:'.$pattern, $value); + assertVerdictParity('required|string|regex:' . $pattern, $value); } finally { ini_set('pcre.backtrack_limit', $original); } @@ -213,7 +213,7 @@ function ruleSetParityGrid(): iterable */ it('P1: not_regex fast path matches Laravel when PCRE aborts', function (): void { $pattern = '/^(a+)+$/'; - $value = str_repeat('a', 200).'!'; + $value = str_repeat('a', 200) . '!'; $original = (string) ini_get('pcre.backtrack_limit'); ini_set('pcre.backtrack_limit', '100'); @@ -222,7 +222,7 @@ function ruleSetParityGrid(): iterable expect(preg_match($pattern, $value)) ->toBeFalse(); - assertVerdictParity('required|string|not_regex:'.$pattern, $value); + assertVerdictParity('required|string|not_regex:' . $pattern, $value); } finally { ini_set('pcre.backtrack_limit', $original); } diff --git a/tests/RuleSetTest.php b/tests/RuleSetTest.php index 5728a73..526b905 100644 --- a/tests/RuleSetTest.php +++ b/tests/RuleSetTest.php @@ -2,21 +2,21 @@ declare(strict_types=1); -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +use Simtabi\Laranail\Validation\RuleSet; +use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationData; +use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Contracts\Validation\Factory; use Illuminate\Validation\ValidationException; +use Simtabi\Laranail\Validation\FluentValidator; +use Simtabi\Laranail\Validation\WildcardExpander; +use Illuminate\Contracts\Validation\ValidationRule; use Simtabi\Laranail\Validation\Builder\Nodes\ArrayRule; -use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; use Simtabi\Laranail\Validation\Builder\Nodes\StringRule; -use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\FluentValidator; -use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Builder\Nodes\NumericRule; use Simtabi\Laranail\Validation\Tests\Fixtures\TestStringEnum; -use Simtabi\Laranail\Validation\WildcardExpander; // ========================================================================= // Basic RuleSet building @@ -36,7 +36,7 @@ it('builds a rule set from an array via from()', function (): void { $rules = RuleSet::from([ 'name' => FluentRule::string()->required(), - 'age' => FluentRule::numeric()->nullable(), + 'age' => FluentRule::numeric()->nullable(), ])->toArray(); expect($rules)->toHaveKeys(['name', 'age']); @@ -44,8 +44,8 @@ it('handles mixed rule types via from()', function (): void { $rules = RuleSet::from([ - 'name' => 'required|string|max:255', - 'age' => ['required', 'integer'], + 'name' => 'required|string|max:255', + 'age' => ['required', 'integer'], 'email' => FluentRule::string()->required()->rule('email'), ])->toArray(); expect($rules)->toMatchArray(['name' => 'required|string|max:255', 'age' => ['required', 'integer']]) @@ -70,7 +70,7 @@ $rules = RuleSet::from([ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required(), - 'qty' => FluentRule::numeric()->required()->integer(), + 'qty' => FluentRule::numeric()->required()->integer(), ]), ])->toArray(); @@ -109,7 +109,7 @@ it('expands wildcard fields against data', function (): void { $rules = RuleSet::from([ - 'items' => FluentRule::array()->required(), + 'items' => FluentRule::array()->required(), 'items.*.name' => FluentRule::string()->required(), ])->expandWildcards([ 'items' => [ @@ -179,7 +179,7 @@ it('validates simple rules without wildcards', function (): void { $validated = RuleSet::from([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->nullable()->integer()->min(0), + 'age' => FluentRule::numeric()->nullable()->integer()->min(0), ])->validate(['name' => 'John', 'age' => 25]); expect($validated)->toBe(['name' => 'John', 'age' => 25]); @@ -278,12 +278,12 @@ it('throws when top-level field fails alongside wildcard rules', function (): void { RuleSet::from([ - 'name' => FluentRule::string()->required()->min(5), + 'name' => FluentRule::string()->required()->min(5), 'items' => FluentRule::array()->required()->each([ 'title' => FluentRule::string()->required(), ]), ])->validate([ - 'name' => 'Jo', + 'name' => 'Jo', 'items' => [['title' => 'OK']], ]); })->throws(ValidationException::class); @@ -959,7 +959,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('supports array spread via IteratorAggregate', function (): void { $ruleSet = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ]); @@ -974,7 +974,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('IteratorAggregate yields the same keys as toArray()', function (): void { $ruleSet = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'items' => FluentRule::array()->each([ 'qty' => FluentRule::numeric()->required(), ]), @@ -993,7 +993,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('all() returns the same value as toArray()', function (): void { $ruleSet = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ]); @@ -1048,9 +1048,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('keeps only the named fields via only()', function (): void { $rules = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), - 'age' => FluentRule::numeric()->nullable(), + 'age' => FluentRule::numeric()->nullable(), ])->only('name', 'email')->toArray(); expect($rules)->toHaveKeys(['name', 'email']) @@ -1073,7 +1073,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('only() with no arguments empties the RuleSet', function (): void { $rules = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->only()->toArray(); @@ -1082,9 +1082,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('only() accepts an array argument (Collection-style)', function (): void { $rules = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), - 'age' => FluentRule::numeric()->nullable(), + 'age' => FluentRule::numeric()->nullable(), ])->only(['name', 'email'])->toArray(); expect($rules)->toHaveKeys(['name', 'email']) @@ -1093,9 +1093,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('except() accepts an array argument (Collection-style)', function (): void { $rules = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), - 'age' => FluentRule::numeric()->nullable(), + 'age' => FluentRule::numeric()->nullable(), ])->except(['email', 'age'])->toArray(); expect($rules)->toHaveKey('name') @@ -1117,9 +1117,9 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('drops the named fields via except()', function (): void { $rules = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), - 'age' => FluentRule::numeric()->nullable(), + 'age' => FluentRule::numeric()->nullable(), ])->except('age')->toArray(); expect($rules)->toHaveKeys(['name', 'email']) @@ -1398,7 +1398,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('prepare returns compiled rules with metadata', function (): void { $preparedRules = RuleSet::from([ - 'name' => FluentRule::string('Full Name')->required()->message('Name is required.')->min(2), + 'name' => FluentRule::string('Full Name')->required()->message('Name is required.')->min(2), 'items' => FluentRule::array()->required()->each([ 'qty' => FluentRule::numeric('Quantity')->required()->integer()->min(1), ]), @@ -1476,7 +1476,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('prepare returns empty implicitAttributes for non-wildcard rules', function (): void { $prepared = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->prepare([]); @@ -1544,7 +1544,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $formRequest = createFormRequest( rules: [ 'items' => FluentRule::array()->required()->each([ - 'type' => FluentRule::string()->required()->in(['chapter', 'menu']), + 'type' => FluentRule::string()->required()->in(['chapter', 'menu']), 'end_time' => FluentRule::numeric() ->requiredUnless('items.*.type', 'menu'), ]), @@ -1570,20 +1570,20 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // Simulates a real-world import validator with deeply nested objects // and many wildcard patterns (similar to JsonInteractionImportValidator). $items = array_map(fn (int $i): array => [ - 'type' => 'button', - 'title' => "Item {$i}", + 'type' => 'button', + 'title' => "Item {$i}", 'start_time' => $i * 10, - 'end_time' => $i * 10 + 5, - 'style' => [ - 'top' => '10%', - 'left' => '20%', - 'height' => '30%', - 'width' => '40%', + 'end_time' => $i * 10 + 5, + 'style' => [ + 'top' => '10%', + 'left' => '20%', + 'height' => '30%', + 'width' => '40%', 'background_color' => '#ff0000', - 'border_radius' => 5, - 'padding_top' => 10, - 'padding_bottom' => 10, - 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], + 'border_radius' => 5, + 'padding_top' => 10, + 'padding_bottom' => 10, + 'border' => ['width' => 1, 'style' => 'solid', 'color' => '#000000'], ], 'action' => [ 'type' => 'link', @@ -1591,14 +1591,14 @@ public function validate(string $attribute, mixed $value, Closure $fail): void 'time' => 0, ], 'attributes' => [ - 'show_indicator' => true, + 'show_indicator' => true, 'indicator_color' => '#00ff00', - 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], + 'options' => ['menu_button_location' => 'top', 'menu_button_name' => 'Menu'], ], 'chapters' => array_map(fn (int $j): array => [ - 'title' => "Chapter {$j}", + 'title' => "Chapter {$j}", 'start_time' => $j * 5, - 'end_time' => $j * 5 + 4, + 'end_time' => $j * 5 + 4, 'sort_order' => $j, ], range(1, 4)), ], range(1, 200)); @@ -1645,21 +1645,21 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('RuleSet::validate() with compiled rules matches native Laravel performance', function (): void { $items = array_map(fn (int $i): array => [ - 'name' => "Item {$i}", + 'name' => "Item {$i}", 'email' => "user{$i}@example.com", - 'age' => $i % 80 + 18, - 'role' => ['admin', 'editor', 'viewer'][$i % 3], + 'age' => $i % 80 + 18, + 'role' => ['admin', 'editor', 'viewer'][$i % 3], ], range(1, 500)); $data = ['items' => $items]; // Native Laravel with string rules $nativeStart = microtime(true); Validator::make($data, [ - 'items' => ['required', 'array'], - 'items.*.name' => ['required', 'string', 'min:2', 'max:255'], + 'items' => ['required', 'array'], + 'items.*.name' => ['required', 'string', 'min:2', 'max:255'], 'items.*.email' => ['required', 'string', 'max:255'], - 'items.*.age' => ['required', 'numeric', 'integer', 'min:0', 'max:150'], - 'items.*.role' => ['required', 'string', Rule::in(['admin', 'editor', 'viewer'])], + 'items.*.age' => ['required', 'numeric', 'integer', 'min:0', 'max:150'], + 'items.*.role' => ['required', 'string', Rule::in(['admin', 'editor', 'viewer'])], ])->validate(); $nativeElapsed = microtime(true) - $nativeStart; @@ -1667,10 +1667,10 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $ruleSetStart = microtime(true); RuleSet::from([ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2)->max(255), + 'name' => FluentRule::string()->required()->min(2)->max(255), 'email' => FluentRule::string()->required()->max(255), - 'age' => FluentRule::numeric()->required()->integer()->min(0)->max(150), - 'role' => FluentRule::string()->required()->in(['admin', 'editor', 'viewer']), + 'age' => FluentRule::numeric()->required()->integer()->min(0)->max(150), + 'role' => FluentRule::string()->required()->in(['admin', 'editor', 'viewer']), ]), ])->validate($data); $ruleSetElapsed = microtime(true) - $ruleSetStart; @@ -1806,7 +1806,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('validates mixed fast+slow rules via partial fast-check', function (): void { $validated = RuleSet::from([ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2)->max(255), + 'name' => FluentRule::string()->required()->min(2)->max(255), 'starts_at' => FluentRule::date()->required()->after('2025-01-01'), ]), ])->validate(['items' => [ @@ -1821,7 +1821,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void try { RuleSet::from([ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2)->max(255), + 'name' => FluentRule::string()->required()->min(2)->max(255), 'starts_at' => FluentRule::date()->required()->after('2025-01-01'), ]), ])->validate(['items' => [ @@ -1837,7 +1837,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void try { RuleSet::from([ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2)->max(255), + 'name' => FluentRule::string()->required()->min(2)->max(255), 'starts_at' => FluentRule::date()->required()->after('2025-01-01'), ]), ])->validate(['items' => [ @@ -1853,7 +1853,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $validated = RuleSet::from([ 'items' => FluentRule::array()->required()->each([ 'name' => FluentRule::string()->required()->min(2), - 'age' => FluentRule::numeric()->required()->integer()->min(0), + 'age' => FluentRule::numeric()->required()->integer()->min(0), ]), ])->validate(['items' => [['name' => 'Alice', 'age' => 30]]]); @@ -1862,8 +1862,8 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('partial fast-check catches single invalid item among many valid', function (): void { $items = array_map(fn (int $i): array => [ - 'name' => 'User '.$i, - 'starts_at' => '2025-06-'.str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), + 'name' => 'User ' . $i, + 'starts_at' => '2025-06-' . str_pad((string) ($i % 28 + 1), 2, '0', STR_PAD_LEFT), ], range(1, 50)); $items[24]['starts_at'] = '2020-01-01'; @@ -1871,7 +1871,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void try { RuleSet::from([ 'items' => FluentRule::array()->required()->each([ - 'name' => FluentRule::string()->required()->min(2), + 'name' => FluentRule::string()->required()->min(2), 'starts_at' => FluentRule::date()->required()->after('2025-01-01'), ]), ])->validate(['items' => $items]); @@ -1948,10 +1948,10 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('failOnUnknownFields passes when all input keys are known', function (): void { $validated = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->failOnUnknownFields()->validate([ - 'name' => 'John', + 'name' => 'John', 'email' => 'john@example.com', ]); @@ -1976,7 +1976,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void RuleSet::from([ 'name' => FluentRule::string()->required(), ])->failOnUnknownFields()->validate([ - 'name' => 'John', + 'name' => 'John', 'unknown_field' => 'value', ]); } catch (ValidationException $validationException) { @@ -2020,7 +2020,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $validated = RuleSet::from([ 'name' => FluentRule::string()->required(), ])->validate([ - 'name' => 'John', + 'name' => 'John', 'extra' => 'ignored', ]); @@ -2036,7 +2036,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void try { RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->stopOnFirstFailure()->validate([]); } catch (ValidationException $validationException) { @@ -2049,10 +2049,10 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('stopOnFirstFailure still passes when all fields are valid', function (): void { $validated = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->stopOnFirstFailure()->validate([ - 'name' => 'John', + 'name' => 'John', 'email' => 'john@example.com', ]); @@ -2174,7 +2174,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $request = Request::create('/test', 'POST', ['name' => 'John', 'email' => 'john@example.com']); $validated = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->validate($request); @@ -2237,7 +2237,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('dropUnknownFields strips unknown nested keys declared via dotted rule keys', function (): void { $validated = RuleSet::from([ - 'meta' => FluentRule::array()->required(), + 'meta' => FluentRule::array()->required(), 'meta.type' => FluentRule::string()->required(), ])->dropUnknownFields()->validate([ 'meta' => ['type' => 'admin', 'secret' => 'leak'], @@ -2307,7 +2307,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $validated = RuleSet::from([ 'name' => FluentRule::string()->required(), ])->dropUnknownFields()->validate([ - 'name' => 'John', + 'name' => 'John', 'extra' => 'noise', ]); @@ -2372,7 +2372,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('compileWithMetadata returns compiled rules with extracted messages and labels', function (): void { $rules = [ - 'name' => FluentRule::string('Full Name')->required()->message('Name is required!'), + 'name' => FluentRule::string('Full Name')->required()->message('Name is required!'), 'items' => FluentRule::array()->required()->each([ 'qty' => FluentRule::numeric('Quantity')->required()->integer()->min(1), ]), @@ -2477,7 +2477,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('check() safe() returns ValidatedInput on success', function (): void { $result = RuleSet::from([ - 'name' => FluentRule::string()->required(), + 'name' => FluentRule::string()->required(), 'email' => FluentRule::email()->required(), ])->check(['name' => 'John', 'email' => 'john@example.com']); @@ -2520,7 +2520,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $rules = [ 'events' => FluentRule::array()->required()->each([ 'start_date' => FluentRule::date()->required(), - 'end_date' => FluentRule::date()->required()->after('start_date'), + 'end_date' => FluentRule::date()->required()->after('start_date'), ]), ]; @@ -2537,7 +2537,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $rules = [ 'events' => FluentRule::array()->required()->each([ 'start_date' => FluentRule::date()->required(), - 'end_date' => FluentRule::date()->required()->after('start_date'), + 'end_date' => FluentRule::date()->required()->after('start_date'), ]), ]; @@ -2552,7 +2552,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('wildcard fast path rejects field-ref date violation (before:sibling)', function (): void { $rules = [ 'events' => FluentRule::array()->required()->each([ - 'start_date' => FluentRule::date()->required(), + 'start_date' => FluentRule::date()->required(), 'registration_deadline' => FluentRule::date()->required()->before('start_date'), ]), ]; @@ -2569,7 +2569,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('wildcard fast path enforces same:FIELD equality against sibling', function (): void { $rules = [ 'users' => FluentRule::array()->required()->each([ - 'password' => FluentRule::string()->required(), + 'password' => FluentRule::string()->required(), 'password_confirmation' => FluentRule::string()->required()->rule('same:password'), ]), ]; @@ -2590,7 +2590,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $rules = [ 'slots' => FluentRule::array()->required()->each([ 'stock' => FluentRule::integer()->required(), - 'sold' => FluentRule::integer()->required()->rule('lte:stock'), + 'sold' => FluentRule::integer()->required()->rule('lte:stock'), ]), ]; @@ -2612,7 +2612,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void 'offers' => FluentRule::array()->required()->each([ 'min_price' => FluentRule::numeric()->required(), 'max_price' => FluentRule::numeric()->required(), - 'price' => FluentRule::numeric()->required()->rule('gt:min_price')->rule('lt:max_price'), + 'price' => FluentRule::numeric()->required()->rule('gt:min_price')->rule('lt:max_price'), ]), ]; @@ -2642,7 +2642,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $rules = [ 'entries' => FluentRule::array()->required()->each([ 'short' => FluentRule::string()->required(), - 'long' => FluentRule::string()->required()->rule('gt:short'), + 'long' => FluentRule::string()->required()->rule('gt:short'), ]), ]; @@ -2726,7 +2726,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void $rules = [ 'events' => FluentRule::array()->required()->each([ 'start' => FluentRule::date()->required(), - 'end' => FluentRule::date()->required(), + 'end' => FluentRule::date()->required(), // checkpoint must sit strictly between start and end. 'checkpoint' => FluentRule::date()->required()->after('start')->before('end'), ]), @@ -2751,7 +2751,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('wildcard fast path enforces required_with (sibling present → required)', function (): void { $rules = [ 'contacts' => FluentRule::array()->required()->each([ - 'phone' => FluentRule::string()->nullable(), + 'phone' => FluentRule::string()->nullable(), 'phone_type' => FluentRule::string()->requiredWith('phone'), ]), ]; @@ -2775,8 +2775,8 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('wildcard fast path enforces required_with with multi-param (any sibling present)', function (): void { $rules = [ 'rows' => FluentRule::array()->required()->each([ - 'a' => FluentRule::string()->nullable(), - 'b' => FluentRule::string()->nullable(), + 'a' => FluentRule::string()->nullable(), + 'b' => FluentRule::string()->nullable(), 'label' => FluentRule::string()->rule('required_with:a,b')->max(50), ]), ]; @@ -2805,7 +2805,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void // withoutRequired is the always-pass closure. $rules = [ 'items' => FluentRule::array()->required()->each([ - 'trigger' => FluentRule::string()->nullable(), + 'trigger' => FluentRule::string()->nullable(), 'conditional' => FluentRule::field()->rule('required_with:trigger'), ]), ]; @@ -2847,7 +2847,7 @@ public function validate(string $attribute, mixed $value, Closure $fail): void 'cards' => FluentRule::array()->required()->each([ 'number' => FluentRule::string()->nullable(), 'expiry' => FluentRule::string()->nullable(), - 'cvc' => FluentRule::string()->rule('required_with_all:number,expiry'), + 'cvc' => FluentRule::string()->rule('required_with_all:number,expiry'), ]), ]; @@ -2868,8 +2868,8 @@ public function validate(string $attribute, mixed $value, Closure $fail): void it('wildcard fast path enforces required_without_all (all siblings absent → required)', function (): void { $rules = [ 'contacts' => FluentRule::array()->required()->each([ - 'email' => FluentRule::string()->nullable(), - 'phone' => FluentRule::string()->nullable(), + 'email' => FluentRule::string()->nullable(), + 'phone' => FluentRule::string()->nullable(), 'fallback' => FluentRule::string()->rule('required_without_all:email,phone'), ]), ]; diff --git a/tests/RuleSetWithBagTest.php b/tests/RuleSetWithBagTest.php index 4355001..2311df2 100644 --- a/tests/RuleSetWithBagTest.php +++ b/tests/RuleSetWithBagTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Illuminate\Validation\ValidationException; -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; +use Illuminate\Validation\ValidationException; // ========================================================================= // RuleSet::withBag() — routes ValidationException into a named error bag. @@ -86,7 +86,7 @@ // with the other forms' error display. $ruleSet = RuleSet::from([ 'current_password' => FluentRule::string()->required()->min(8), - 'new_password' => FluentRule::string()->required()->min(12), + 'new_password' => FluentRule::string()->required()->min(12), ])->withBag('updatePassword'); try { diff --git a/tests/Rules/AntiSpam/AntiSpamRulesTest.php b/tests/Rules/AntiSpam/AntiSpamRulesTest.php index e0034b0..f928de6 100644 --- a/tests/Rules/AntiSpam/AntiSpamRulesTest.php +++ b/tests/Rules/AntiSpam/AntiSpamRulesTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Crypt; use Simtabi\Laranail\Validation\Rules\AntiSpam\Honeypot; use Simtabi\Laranail\Validation\Rules\AntiSpam\SubmissionTiming; @@ -60,8 +60,8 @@ expect(ruleAccepts(new SubmissionTiming, $value))->toBeFalse(); })->with([ 'plain timestamp' => '1700000000', - 'garbage' => 'not-a-token', - 'non-string' => 12345, + 'garbage' => 'not-a-token', + 'non-string' => 12345, ]); it('rejects a token from the future rather than treating it as instant', function (): void { diff --git a/tests/Rules/Banking/BankingRulesTest.php b/tests/Rules/Banking/BankingRulesTest.php index fbcbbec..83da547 100644 --- a/tests/Rules/Banking/BankingRulesTest.php +++ b/tests/Rules/Banking/BankingRulesTest.php @@ -39,11 +39,11 @@ expect(ruleAccepts(new $ruleClass, $value))->toBeFalse(); })->with([ 'luhn array' => [Luhn::class, ['x']], - 'luhn bool' => [Luhn::class, true], + 'luhn bool' => [Luhn::class, true], 'iban array' => [Iban::class, ['x']], - 'iban int' => [Iban::class, 12345], + 'iban int' => [Iban::class, 12345], 'isin array' => [Isin::class, ['x']], - 'bic array' => [Bic::class, ['x']], + 'bic array' => [Bic::class, ['x']], ]); it('leaves an empty value to required, as Laravel does', function (): void { diff --git a/tests/Rules/Chrono/ChronoRulesTest.php b/tests/Rules/Chrono/ChronoRulesTest.php index a7a2f19..68ad49d 100644 --- a/tests/Rules/Chrono/ChronoRulesTest.php +++ b/tests/Rules/Chrono/ChronoRulesTest.php @@ -4,14 +4,14 @@ use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Rules\Chrono\DateInterval; -use Simtabi\Laranail\Validation\Rules\Chrono\MaxDateDifference; -use Simtabi\Laranail\Validation\Rules\Chrono\MinimumAge; -use Simtabi\Laranail\Validation\Rules\Chrono\MinuteIn; use Simtabi\Laranail\Validation\Rules\Chrono\Rfc3339; +use Simtabi\Laranail\Validation\Rules\Chrono\MinuteIn; use Simtabi\Laranail\Validation\Rules\Chrono\TimeOfDay; -use Simtabi\Laranail\Validation\Rules\Chrono\TimezoneAbbreviation; +use Simtabi\Laranail\Validation\Rules\Chrono\MinimumAge; +use Simtabi\Laranail\Validation\Rules\Chrono\DateInterval; use Simtabi\Laranail\Validation\Rules\Chrono\UnixTimestamp; +use Simtabi\Laranail\Validation\Rules\Chrono\MaxDateDifference; +use Simtabi\Laranail\Validation\Rules\Chrono\TimezoneAbbreviation; afterEach(function (): void { Date::setTestNow(); @@ -154,7 +154,7 @@ it('reads an @-prefixed reference from a sibling field', function (): void { $rules = [ 'start_at' => 'required', - 'end_at' => [new MaxDateDifference(48, '@start_at')], + 'end_at' => [new MaxDateDifference(48, '@start_at')], ]; expect(Validator::make( diff --git a/tests/Rules/ClientCheckableTest.php b/tests/Rules/ClientCheckableTest.php index 1c4d579..17cb02c 100644 --- a/tests/Rules/ClientCheckableTest.php +++ b/tests/Rules/ClientCheckableTest.php @@ -3,13 +3,13 @@ declare(strict_types=1); use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Contracts\ClientCheckable; -use Simtabi\Laranail\Validation\Rules\Colour\CssColor; use Simtabi\Laranail\Validation\Rules\Geo\Latitude; use Simtabi\Laranail\Validation\Rules\Geo\Longitude; -use Simtabi\Laranail\Validation\Rules\Identifiers\HashDigest; -use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; use Simtabi\Laranail\Validation\Rules\Text\CaseStyle; +use Simtabi\Laranail\Validation\Rules\Colour\CssColor; +use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; +use Simtabi\Laranail\Validation\Contracts\ClientCheckable; +use Simtabi\Laranail\Validation\Rules\Identifiers\HashDigest; use Simtabi\Laranail\Validation\Rules\Vendor\VendorIdentifier; /** @@ -30,9 +30,9 @@ function clientCheckableArguments(): array { return [ - CaseStyle::class => [CaseStyle::KEBAB], + CaseStyle::class => [CaseStyle::KEBAB], VendorIdentifier::class => [VendorIdentifier::AWS_REGION], - HashDigest::class => ['sha256'], + HashDigest::class => ['sha256'], ]; } @@ -74,7 +74,8 @@ function clientCheckableRules(): array /** * The advertised rules as a native Laravel rule string. * - * @param list}> $advertised + * @param list}> $advertised + * * @return list */ function asLaravelRules(array $advertised): array @@ -89,7 +90,7 @@ function asLaravelRules(array $advertised): array // Laravel's parser special-cases regex and does NOT split its // parameter on commas, which matters because a pattern routinely // contains one. - return $rule['rule'].':'.implode(',', $params); + return $rule['rule'] . ':' . implode(',', $params); }, $advertised); } @@ -101,7 +102,7 @@ function asLaravelRules(array $advertised): array // actually run rather than an approximation of it. $grid = [ '', ' ', 'abc', 'ABC', 'abc-def', 'abc--def', '-abc', 'abc-', 'a b', "a\tb", "a\u{200B}b", - '1.0.0', '1.0.0-alpha.1', '1.0', 'v1.0.0', '0x'.str_repeat('a', 40), '0x'.str_repeat('g', 40), + '1.0.0', '1.0.0-alpha.1', '1.0', 'v1.0.0', '0x' . str_repeat('a', 40), '0x' . str_repeat('g', 40), 'sub.domain', 'subdomain', str_repeat('a', 64), 'a_b', 'café', '123', 'kebab-case', 'camelCase', 'PascalCase', 'snake_case', '12.34', '12.345', '-12.00', '+3', '1e3', '$12', '1,234.50', @@ -171,7 +172,7 @@ class_basename($class), $offenders = []; foreach ($mustNotAdvertise as $suffix) { - $class = 'Simtabi\\Laranail\\Validation\\Rules\\'.$suffix; + $class = 'Simtabi\\Laranail\\Validation\\Rules\\' . $suffix; expect(class_exists($class))->toBeTrue("{$class} no longer exists — update this list"); @@ -181,7 +182,7 @@ class_basename($class), } expect($offenders)->toBeEmpty( - 'performs a checksum, a query or IO but advertises a browser form: '.implode(', ', $offenders), + 'performs a checksum, a query or IO but advertises a browser form: ' . implode(', ', $offenders), ); }); @@ -262,5 +263,5 @@ class_basename($class), } } - expect($undeclared)->toBeEmpty('has clientRules() but does not implement ClientCheckable: '.implode(', ', $undeclared)); + expect($undeclared)->toBeEmpty('has clientRules() but does not implement ClientCheckable: ' . implode(', ', $undeclared)); }); diff --git a/tests/Rules/Crypto/CryptoRulesTest.php b/tests/Rules/Crypto/CryptoRulesTest.php index c41f098..bb450fb 100644 --- a/tests/Rules/Crypto/CryptoRulesTest.php +++ b/tests/Rules/Crypto/CryptoRulesTest.php @@ -12,13 +12,13 @@ it('accepts real Bitcoin addresses of each format', function (string $address): void { expect(ruleAccepts(new BitcoinAddress, $address))->toBeTrue(); })->with([ - 'P2PKH (genesis)' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', - 'P2PKH' => '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', - 'P2SH' => '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy', + 'P2PKH (genesis)' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', + 'P2PKH' => '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', + 'P2SH' => '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy', 'P2WPKH (bech32 v0)' => 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', - 'P2WSH (bech32 v0)' => 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3', - 'P2TR (bech32m v1)' => 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr', - 'uppercase bech32' => 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', + 'P2WSH (bech32 v0)' => 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3', + 'P2TR (bech32m v1)' => 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr', + 'uppercase bech32' => 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', ]); it('rejects a single mistyped character, which a regex would accept', function (string $address): void { @@ -27,21 +27,21 @@ // right length and alphabet throughout. expect(ruleAccepts(new BitcoinAddress, $address))->toBeFalse(); })->with([ - 'P2PKH last char' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7Divfna', - 'P2SH last char' => '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLx', - 'bech32 last char' => 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', + 'P2PKH last char' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7Divfna', + 'P2SH last char' => '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLx', + 'bech32 last char' => 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5', 'bech32m last char' => 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcq', ]); it('rejects malformed Bitcoin addresses', function (string $address): void { expect(ruleAccepts(new BitcoinAddress, $address))->toBeFalse(); })->with([ - 'truncated' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf', + 'truncated' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf', 'chars outside base58' => '0OIl1A1zP1eP5QGefi2DMPTfTL5SLmv7', - 'mixed-case bech32' => 'bc1Qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', - 'wrong hrp' => 'ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', - 'not an address' => 'not-an-address', - 'ethereum address' => '0x52908400098527886E0F7030069857D2E4169EE7', + 'mixed-case bech32' => 'bc1Qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + 'wrong hrp' => 'ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + 'not an address' => 'not-an-address', + 'ethereum address' => '0x52908400098527886E0F7030069857D2E4169EE7', ]); it('rejects testnet addresses on mainnet and the reverse', function (): void { @@ -71,21 +71,21 @@ it('accepts well-formed Ethereum addresses', function (string $address): void { expect(ruleAccepts(new EthereumAddress, $address))->toBeTrue(); })->with([ - 'all lowercase' => '0x52908400098527886e0f7030069857d2e4169ee7', - 'all uppercase' => '0x52908400098527886E0F7030069857D2E4169EE7', + 'all lowercase' => '0x52908400098527886e0f7030069857d2e4169ee7', + 'all uppercase' => '0x52908400098527886E0F7030069857D2E4169EE7', 'EIP-55 mixed case' => '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed', - 'zero address' => '0x0000000000000000000000000000000000000000', + 'zero address' => '0x0000000000000000000000000000000000000000', ]); it('rejects malformed Ethereum addresses', function (string $address): void { expect(ruleAccepts(new EthereumAddress, $address))->toBeFalse(); })->with([ - 'no prefix' => '52908400098527886E0F7030069857D2E4169EE7', - 'too short' => '0x52908400098527886E0F7030069857D2E4169EE', - 'too long' => '0x52908400098527886E0F7030069857D2E4169EE77', - 'non-hex' => '0x52908400098527886E0F7030069857D2E4169EEZ', + 'no prefix' => '52908400098527886E0F7030069857D2E4169EE7', + 'too short' => '0x52908400098527886E0F7030069857D2E4169EE', + 'too long' => '0x52908400098527886E0F7030069857D2E4169EE77', + 'non-hex' => '0x52908400098527886E0F7030069857D2E4169EEZ', 'uppercase prefix' => '0X52908400098527886E0F7030069857D2E4169EE7', - 'bitcoin address' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', + 'bitcoin address' => '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', ]); it('does not verify the EIP-55 checksum, and a typo therefore passes', function (): void { diff --git a/tests/Rules/Database/CompareToColumnTest.php b/tests/Rules/Database/CompareToColumnTest.php index a238da8..01ee9ac 100644 --- a/tests/Rules/Database/CompareToColumnTest.php +++ b/tests/Rules/Database/CompareToColumnTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Rules\Database\CompareToColumn; use Simtabi\Laranail\Validation\Rules\Database\Comparison; +use Simtabi\Laranail\Validation\Rules\Database\CompareToColumn; beforeEach(function (): void { config(['database.default' => 'testing']); @@ -56,7 +56,7 @@ it('reads an @-prefixed key from a sibling field', function (): void { $rules = [ 'product_id' => 'required', - 'quantity' => [new CompareToColumn('products', 'max_quantity', Comparison::LessThanOrEqual, 'id', '@product_id')], + 'quantity' => [new CompareToColumn('products', 'max_quantity', Comparison::LessThanOrEqual, 'id', '@product_id')], ]; expect(Validator::make(['product_id' => 1, 'quantity' => '10'], $rules)->passes())->toBeTrue() diff --git a/tests/Rules/Database/DatabaseRulesTest.php b/tests/Rules/Database/DatabaseRulesTest.php index b987542..2732d48 100644 --- a/tests/Rules/Database/DatabaseRulesTest.php +++ b/tests/Rules/Database/DatabaseRulesTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Validator; use Simtabi\Laranail\Validation\Rules\Database\Authorized; use Simtabi\Laranail\Validation\Rules\Database\ModelsExist; diff --git a/tests/Rules/Email/EmailRulesTest.php b/tests/Rules/Email/EmailRulesTest.php index e2631cc..7d72278 100644 --- a/tests/Rules/Email/EmailRulesTest.php +++ b/tests/Rules/Email/EmailRulesTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; -use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; -use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; +use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIs; use Simtabi\Laranail\Validation\Rules\Email\EmailDomainIsNot; use Simtabi\Laranail\Validation\Rules\Email\NotDisposableEmail; -use Simtabi\Laranail\Validation\Rules\Email\NotRoleEmail; -use Simtabi\Laranail\Validation\Support\Email\BundledDisposableDomainList; +use Simtabi\Laranail\Validation\Contracts\Email\RoleAccountList; +use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; +use Simtabi\Laranail\Validation\Contracts\Email\DisposableDomainList; use Simtabi\Laranail\Validation\Support\Email\BundledRoleAccountList; +use Simtabi\Laranail\Validation\Support\Email\BundledDisposableDomainList; /** * A list that answers yes to exactly the entries given. @@ -19,7 +19,7 @@ * disposable and role rules — and so the tests exercise the same seam * laranail/email will use to swap in the real implementations. * - * @param list $entries + * @param list $entries */ function fakeList(array $entries): DisposableDomainList&RoleAccountList { diff --git a/tests/Rules/Encoding/EncodingRulesTest.php b/tests/Rules/Encoding/EncodingRulesTest.php index 79120a1..0abf330 100644 --- a/tests/Rules/Encoding/EncodingRulesTest.php +++ b/tests/Rules/Encoding/EncodingRulesTest.php @@ -4,8 +4,8 @@ use Illuminate\Support\Facades\Validator; use Simtabi\Laranail\Validation\Rules\Encoding\Base64; -use Simtabi\Laranail\Validation\Rules\Encoding\Base64Image; use Simtabi\Laranail\Validation\Rules\Encoding\DataUri; +use Simtabi\Laranail\Validation\Rules\Encoding\Base64Image; use Simtabi\Laranail\Validation\Support\Encoding\Base64File; /** A real 1x1 PNG, 70 bytes decoded. */ @@ -32,7 +32,7 @@ it('accepts a real image, bare and as a data URI', function (): void { expect(ruleAccepts(new Base64Image, PNG_B64))->toBeTrue() - ->and(ruleAccepts(new Base64Image, 'data:image/png;base64,'.PNG_B64))->toBeTrue(); + ->and(ruleAccepts(new Base64Image, 'data:image/png;base64,' . PNG_B64))->toBeTrue(); }); it('rejects non-images and images outside the allowed types', function (): void { @@ -57,7 +57,7 @@ }); it('bridges a validated image to an UploadedFile for Laravel file rules', function (): void { - $file = Base64File::toUploadedFile('data:image/png;base64,'.PNG_B64, 'avatar.png'); + $file = Base64File::toUploadedFile('data:image/png;base64,' . PNG_B64, 'avatar.png'); expect($file)->not->toBeNull() ->and($file->getSize())->toBe(70) @@ -76,7 +76,7 @@ it('accepts well-formed data URIs', function (string $value): void { expect(ruleAccepts(new DataUri, $value))->toBeTrue(); })->with([ - 'data:image/png;base64,'.PNG_B64, + 'data:image/png;base64,' . PNG_B64, 'data:text/plain,hello%20world', 'data:,bare%20form', // both parts are optional 'data:text/plain;charset=utf-8,hi', @@ -87,7 +87,7 @@ })->with([ 'data:image/png;base64,!!!!', // base64 flag with non-base64 payload 'data:image/png;base64', // no comma, no data - 'dat:image/png;base64,'.PNG_B64, // scheme typo + 'dat:image/png;base64,' . PNG_B64, // scheme typo 'data:image;base64,abcd', // media type missing its subtype 'data:text/plain,hello world', // raw space is not URL-encoded 12345, @@ -97,7 +97,7 @@ it('can restrict the media types it accepts', function (): void { $imagesOnly = new DataUri(mediaTypes: ['image/*']); - expect(ruleAccepts($imagesOnly, 'data:image/png;base64,'.PNG_B64))->toBeTrue() + expect(ruleAccepts($imagesOnly, 'data:image/png;base64,' . PNG_B64))->toBeTrue() ->and(ruleAccepts($imagesOnly, 'data:text/plain,hi'))->toBeFalse() // Restricting to a type means an untyped URI no longer qualifies. ->and(ruleAccepts($imagesOnly, 'data:,hi'))->toBeFalse() diff --git a/tests/Rules/Fiscal/NationalIdentifierTest.php b/tests/Rules/Fiscal/NationalIdentifierTest.php index e8becfb..3bad328 100644 --- a/tests/Rules/Fiscal/NationalIdentifierTest.php +++ b/tests/Rules/Fiscal/NationalIdentifierTest.php @@ -30,10 +30,10 @@ expect(Id::passes($value, Id::NL))->toBeFalse(); })->with([ 'wrong check' => '111222334', - 'all zeroes' => '000000000', - 'too short' => '1234567', - 'too long' => '1112223334', - 'not digits' => 'abcdefghi', + 'all zeroes' => '000000000', + 'too short' => '1234567', + 'too long' => '1112223334', + 'not digits' => 'abcdefghi', ]); it('weights the final BSN digit negatively', function (): void { @@ -55,10 +55,10 @@ expect(Id::passes($value, Id::BR))->toBeFalse(); })->with([ 'bad second digit' => '11144477736', - 'bad first digit' => '11144477745', - 'all ones' => '11111111111', - 'all zeroes' => '00000000000', - 'too short' => '1114447773', + 'bad first digit' => '11144477745', + 'all ones' => '11111111111', + 'all zeroes' => '00000000000', + 'too short' => '1114447773', ]); // ========================================================================= @@ -72,13 +72,13 @@ it('rejects the ranges the SSA has never issued', function (string $value): void { expect(Id::passes($value, Id::US))->toBeFalse(); })->with([ - 'area 000' => '000-12-3456', - 'area 666' => '666-12-3456', - 'area 900+' => '900-12-3456', - 'area 999' => '999-12-3456', - 'group 00' => '078-00-1120', + 'area 000' => '000-12-3456', + 'area 666' => '666-12-3456', + 'area 900+' => '900-12-3456', + 'area 999' => '999-12-3456', + 'group 00' => '078-00-1120', 'serial 0000' => '078-05-0000', - 'too short' => '078-05-112', + 'too short' => '078-05-112', ]); // ========================================================================= @@ -92,18 +92,18 @@ it('rejects reserved prefixes and letters the scheme never uses', function (string $value): void { expect(Id::passes($value, Id::GB))->toBeFalse(); })->with([ - 'D first' => 'DA123456C', - 'F first' => 'FA123456C', - 'Q first' => 'QQ123456C', - 'U first' => 'UA123456C', - 'V first' => 'VA123456C', - 'O second' => 'AO123456C', + 'D first' => 'DA123456C', + 'F first' => 'FA123456C', + 'Q first' => 'QQ123456C', + 'U first' => 'UA123456C', + 'V first' => 'VA123456C', + 'O second' => 'AO123456C', 'reserved BG' => 'BG123456C', 'reserved GB' => 'GB123456C', 'reserved NK' => 'NK123456C', 'reserved TN' => 'TN123456C', 'reserved ZZ' => 'ZZ123456C', - 'suffix E' => 'AB123456E', + 'suffix E' => 'AB123456E', ]); // ========================================================================= @@ -115,9 +115,9 @@ // substitutes 19 and 18 before the modulo. expect(Id::passes($value, Id::FR))->toBeTrue(); })->with([ - 'mainland' => '269054958815780', - 'key of 97' => '180126745108997', - 'another' => '184017512345658', + 'mainland' => '269054958815780', + 'key of 97' => '180126745108997', + 'another' => '184017512345658', 'Corsica 2A' => '199122A12345641', 'Corsica 2B' => '199122B12345668', ]); @@ -125,11 +125,11 @@ it('rejects a NIR whose key does not match', function (string $value): void { expect(Id::passes($value, Id::FR))->toBeFalse(); })->with([ - 'wrong key' => '269054958815781', + 'wrong key' => '269054958815781', 'Corsica wrong key' => '199122A12345642', - 'bad sex digit' => '369054958815780', - 'bad month' => '269134958815780', - 'too short' => '26905495881578', + 'bad sex digit' => '369054958815780', + 'bad month' => '269134958815780', + 'too short' => '26905495881578', ]); // ========================================================================= diff --git a/tests/Rules/Fiscal/VatNumberTest.php b/tests/Rules/Fiscal/VatNumberTest.php index 0ee5f0e..ace93fb 100644 --- a/tests/Rules/Fiscal/VatNumberTest.php +++ b/tests/Rules/Fiscal/VatNumberTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; use Simtabi\Laranail\Validation\Rules\Fiscal\VatNumber; +use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; // ========================================================================= // VatNumber — checksum countries diff --git a/tests/Rules/Geo/GeoRulesTest.php b/tests/Rules/Geo/GeoRulesTest.php index a57ba87..7f1e0a8 100644 --- a/tests/Rules/Geo/GeoRulesTest.php +++ b/tests/Rules/Geo/GeoRulesTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Geo\CaProvince; -use Simtabi\Laranail\Validation\Rules\Geo\Latitude; use Simtabi\Laranail\Validation\Rules\Geo\LatLng; -use Simtabi\Laranail\Validation\Rules\Geo\Longitude; use Simtabi\Laranail\Validation\Rules\Geo\UsState; +use Simtabi\Laranail\Validation\Rules\Geo\Latitude; +use Simtabi\Laranail\Validation\Rules\Geo\Longitude; +use Simtabi\Laranail\Validation\Rules\Geo\CaProvince; // ========================================================================= // Latitude / Longitude diff --git a/tests/Rules/I18n/I18nRulesTest.php b/tests/Rules/I18n/I18nRulesTest.php index 48fc6fe..95a0e38 100644 --- a/tests/Rules/I18n/I18nRulesTest.php +++ b/tests/Rules/I18n/I18nRulesTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Contracts\I18n\CountryDataset; -use Simtabi\Laranail\Validation\Contracts\I18n\CurrencyDataset; -use Simtabi\Laranail\Validation\Contracts\I18n\LanguageDataset; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\PhpExecutableFinder; use Simtabi\Laranail\Validation\Rules\I18n\CountryCode; use Simtabi\Laranail\Validation\Rules\I18n\CurrencyCode; use Simtabi\Laranail\Validation\Rules\I18n\LanguageCode; -use Symfony\Component\Process\PhpExecutableFinder; -use Symfony\Component\Process\Process; +use Simtabi\Laranail\Validation\Contracts\I18n\CountryDataset; +use Simtabi\Laranail\Validation\Contracts\I18n\CurrencyDataset; +use Simtabi\Laranail\Validation\Contracts\I18n\LanguageDataset; // ========================================================================= // CountryCode — ISO 3166-1, alpha-2 by default, alpha-3 by flag @@ -157,7 +157,7 @@ public function isSymbol(string $symbol): bool expect($php)->not->toBeEmpty(); $process = new Process( - [$php, dirname(__DIR__, 3).'/tools/build-datasets.php', '--check'], + [$php, dirname(__DIR__, 3) . '/tools/build-datasets.php', '--check'], ); $process->run(); diff --git a/tests/Rules/Identifiers/IdentifiersRulesTest.php b/tests/Rules/Identifiers/IdentifiersRulesTest.php index e5cd6c4..17f1ef1 100644 --- a/tests/Rules/Identifiers/IdentifiersRulesTest.php +++ b/tests/Rules/Identifiers/IdentifiersRulesTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; -use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; +use Simtabi\Laranail\Validation\Rules\Identifiers\Imei; +use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; // ========================================================================= // IMEI @@ -118,7 +118,7 @@ // is the shape that usually signals a ReDoS. Measure rather than assume: // a vulnerable pattern takes exponential time on a long almost-matching // prerelease, so anything near-instant here rules it out. - $pathological = '1.0.0-'.str_repeat('a.', 5000).'!'; + $pathological = '1.0.0-' . str_repeat('a.', 5000) . '!'; $start = hrtime(true); $result = ruleAccepts(new SemVer, $pathological); diff --git a/tests/Rules/Markup/XmlTest.php b/tests/Rules/Markup/XmlTest.php index 05cf2cd..b7cacbd 100644 --- a/tests/Rules/Markup/XmlTest.php +++ b/tests/Rules/Markup/XmlTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Illuminate\Contracts\Translation\Translator; -use Illuminate\Translation\PotentiallyTranslatedString; use Simtabi\Laranail\Validation\Rules\Markup\Xml; +use Illuminate\Translation\PotentiallyTranslatedString; it('accepts well-formed XML and rejects malformed', function (): void { expect(ruleAccepts(new Xml, 'x'))->toBeTrue() @@ -47,7 +47,7 @@ }); it('validates against an XSD schema', function (): void { - $schema = sys_get_temp_dir().'/laranail-validation-test.xsd'; + $schema = sys_get_temp_dir() . '/laranail-validation-test.xsd'; file_put_contents($schema, <<<'XSD' diff --git a/tests/Rules/Net/NetRulesTest.php b/tests/Rules/Net/NetRulesTest.php index f209e98..b68ac2e 100644 --- a/tests/Rules/Net/NetRulesTest.php +++ b/tests/Rules/Net/NetRulesTest.php @@ -3,10 +3,10 @@ declare(strict_types=1); use Simtabi\Laranail\Validation\Rules\Net\Cidr; -use Simtabi\Laranail\Validation\Rules\Net\DomainName; -use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; use Simtabi\Laranail\Validation\Rules\Net\PublicIp; +use Simtabi\Laranail\Validation\Rules\Net\PrivateIp; use Simtabi\Laranail\Validation\Rules\Net\Subdomain; +use Simtabi\Laranail\Validation\Rules\Net\DomainName; // ========================================================================= // DomainName @@ -41,8 +41,8 @@ ]); it('rejects a label longer than 63 characters', function (): void { - expect(ruleAccepts(new DomainName, str_repeat('a', 63).'.com'))->toBeTrue() - ->and(ruleAccepts(new DomainName, str_repeat('a', 64).'.com'))->toBeFalse(); + expect(ruleAccepts(new DomainName, str_repeat('a', 63) . '.com'))->toBeTrue() + ->and(ruleAccepts(new DomainName, str_repeat('a', 64) . '.com'))->toBeFalse(); }); it('rejects a name longer than 253 characters', function (): void { @@ -50,8 +50,8 @@ // length, which is one of the reasons it is not enough on its own. $label = str_repeat('a', 49); // under the 63 label cap - $atLimit = implode('.', array_fill(0, 5, $label)).'.com'; // 5*49 + 4 dots + 4 - $overLimit = $atLimit.'m'; + $atLimit = implode('.', array_fill(0, 5, $label)) . '.com'; // 5*49 + 4 dots + 4 + $overLimit = $atLimit . 'm'; // Written in Pest's toHaveLength idiom deliberately: expressed as // strlen($x)->toBe(n), Rector's Pest set rewrites it and silently changed diff --git a/tests/Rules/Net/NetworkFieldsTest.php b/tests/Rules/Net/NetworkFieldsTest.php index 2690b2c..d966e43 100644 --- a/tests/Rules/Net/NetworkFieldsTest.php +++ b/tests/Rules/Net/NetworkFieldsTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\FluentRule; -use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; use Simtabi\Laranail\Validation\Rules\Net\MacAddress; -use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Rules\Net\InCidrRange; function netCheck(mixed $rule, mixed $value): bool { diff --git a/tests/Rules/Net/UrlTest.php b/tests/Rules/Net/UrlTest.php index cfb87b7..e5b3db1 100644 --- a/tests/Rules/Net/UrlTest.php +++ b/tests/Rules/Net/UrlTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\FluentRule; /* * Every value in the first block passes Laravel's own `url` rule. That is the @@ -39,7 +39,7 @@ function urlError(mixed $rule, mixed $value): string foreach ($values as $value) { expect(urlCheck(FluentRule::url()->required(), $value)) - ->toBeFalse('accepted '.json_encode($value)); + ->toBeFalse('accepted ' . json_encode($value)); } }); @@ -173,7 +173,7 @@ function urlError(mixed $rule, mixed $value): string ]); it('bounds the length', function (): void { - $long = 'https://example.com/'.str_repeat('a', 3000); + $long = 'https://example.com/' . str_repeat('a', 3000); expect(urlCheck(FluentRule::url(), $long))->toBeFalse() ->and(urlCheck(FluentRule::url()->maxLength(4000), $long))->toBeTrue(); diff --git a/tests/Rules/Network/DeliverableEmailTest.php b/tests/Rules/Network/DeliverableEmailTest.php index 9ffdf05..41045fa 100644 --- a/tests/Rules/Network/DeliverableEmailTest.php +++ b/tests/Rules/Network/DeliverableEmailTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -use Illuminate\Contracts\Cache\Repository; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Illuminate\Contracts\Cache\Repository; use Simtabi\Laranail\Validation\Actions\CachedDnsResolver; use Simtabi\Laranail\Validation\Contracts\Email\DnsResolver; +use Simtabi\Laranail\Validation\Rules\Network\DeliverableEmail; use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; use Simtabi\Laranail\Validation\Providers\ValidationServiceProvider; -use Simtabi\Laranail\Validation\Rules\Network\DeliverableEmail; use Simtabi\Laranail\Validation\Tests\Support\ThrowsOnEveryCacheCall; /** @@ -20,7 +20,7 @@ * precognitive request performs no lookup at all. */ /** - * @param list $deliverable + * @param list $deliverable */ function fakeResolver(array $deliverable, ?Closure $onCall = null): DnsResolver { diff --git a/tests/Rules/Network/ProbeRulesTest.php b/tests/Rules/Network/ProbeRulesTest.php index e637dae..46eb653 100644 --- a/tests/Rules/Network/ProbeRulesTest.php +++ b/tests/Rules/Network/ProbeRulesTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\Http; -use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; -use Simtabi\Laranail\Validation\Rules\Network\HasGravatar; +use Illuminate\Http\Client\ConnectionException; use Simtabi\Laranail\Validation\Rules\Network\ImageUrl; +use Simtabi\Laranail\Validation\Rules\Network\HasGravatar; +use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; // ========================================================================= // ImageUrl — the redesigned image-URL probe (owner decision: implement, @@ -78,8 +78,8 @@ it('reports whether the address has a gravatar via the sha256 endpoint', function (): void { Http::fake([ - 'gravatar.com/avatar/'.hash('sha256', 'alice@example.com').'*' => Http::response('', 200), - 'gravatar.com/*' => Http::response('', 404), + 'gravatar.com/avatar/' . hash('sha256', 'alice@example.com') . '*' => Http::response('', 200), + 'gravatar.com/*' => Http::response('', 404), ]); expect(ruleAccepts(new HasGravatar, 'alice@example.com'))->toBeTrue() @@ -88,8 +88,8 @@ it('normalises the address before hashing, as gravatar does', function (): void { Http::fake([ - 'gravatar.com/avatar/'.hash('sha256', 'alice@example.com').'*' => Http::response('', 200), - 'gravatar.com/*' => Http::response('', 404), + 'gravatar.com/avatar/' . hash('sha256', 'alice@example.com') . '*' => Http::response('', 200), + 'gravatar.com/*' => Http::response('', 404), ]); expect(ruleAccepts(new HasGravatar, ' Alice@Example.COM '))->toBeTrue(); diff --git a/tests/Rules/Numbers/NumbersRulesTest.php b/tests/Rules/Numbers/NumbersRulesTest.php index a10f29c..64c7480 100644 --- a/tests/Rules/Numbers/NumbersRulesTest.php +++ b/tests/Rules/Numbers/NumbersRulesTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; use Simtabi\Laranail\Validation\Rules\Numbers\Parity; +use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; // ========================================================================= // Parity @@ -15,14 +15,14 @@ expect(Parity::passes($value, Parity::EVEN))->toBe($even) ->and(Parity::passes($value, Parity::ODD))->toBe(! $even); })->with([ - 'zero' => [0, true], - 'positive even' => [4, true], - 'positive odd' => [7, false], - 'negative even' => [-4, true], - 'negative odd' => [-3, false], + 'zero' => [0, true], + 'positive even' => [4, true], + 'positive odd' => [7, false], + 'negative even' => [-4, true], + 'negative odd' => [-3, false], 'numeric string' => ['4', true], - 'signed string' => ['-3', false], - 'padded string' => [' 6 ', true], + 'signed string' => ['-3', false], + 'padded string' => [' 6 ', true], ]); it('rejects values that have no parity', function (mixed $value): void { @@ -31,14 +31,14 @@ expect(Parity::passes($value, Parity::EVEN))->toBeFalse() ->and(Parity::passes($value, Parity::ODD))->toBeFalse(); })->with([ - 'fractional' => 2.5, + 'fractional' => 2.5, 'not a number' => 'four', - 'empty' => '', - 'hex-ish' => '0x4', - 'exponent' => '1e2', - 'null' => null, - 'array' => [[2]], - 'bool' => true, + 'empty' => '', + 'hex-ish' => '0x4', + 'exponent' => '1e2', + 'null' => null, + 'array' => [[2]], + 'bool' => true, ]); it('accepts a whole float but not one beyond int range', function (): void { @@ -68,17 +68,17 @@ // these is numeric to PHP. expect(MonetaryAmount::passes($value))->toBeFalse(); })->with([ - 'exponent' => '1e3', - 'hex' => '0x1A', - 'infinity' => INF, - 'nan' => NAN, + 'exponent' => '1e3', + 'hex' => '0x1A', + 'infinity' => INF, + 'nan' => NAN, 'thousands separator' => '1,234.50', - 'currency symbol' => '$12.00', - 'trailing dot' => '12.', - 'too many decimals' => '12.345', + 'currency symbol' => '$12.00', + 'trailing dot' => '12.', + 'too many decimals' => '12.345', 'negative by default' => '-12.00', - 'empty' => '', - 'spaces only' => ' ', + 'empty' => '', + 'spaces only' => ' ', ]); it('takes the decimal count as a parameter, because currencies differ', function (): void { diff --git a/tests/Rules/Payment/PaymentRulesTest.php b/tests/Rules/Payment/PaymentRulesTest.php index 7d4fa3d..d62c38e 100644 --- a/tests/Rules/Payment/PaymentRulesTest.php +++ b/tests/Rules/Payment/PaymentRulesTest.php @@ -4,11 +4,11 @@ use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Validator; -use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; use Simtabi\Laranail\Validation\Rules\Payment\CardCvc; use Simtabi\Laranail\Validation\Rules\Payment\CardExpiry; use Simtabi\Laranail\Validation\Rules\Payment\CardNumber; use Simtabi\Laranail\Validation\Support\Payment\CardBrand; +use Simtabi\Laranail\Validation\Contracts\Payment\CardBrandCatalogue; afterEach(function (): void { Date::setTestNow(); diff --git a/tests/Rules/Postal/PostalCodeTest.php b/tests/Rules/Postal/PostalCodeTest.php index 45d416e..e586da9 100644 --- a/tests/Rules/Postal/PostalCodeTest.php +++ b/tests/Rules/Postal/PostalCodeTest.php @@ -13,32 +13,32 @@ it('accepts postcodes matching the given country', function (string $country, string $value): void { expect(ruleAccepts(new PostalCode($country), $value))->toBeTrue(); })->with([ - 'US 5-digit' => ['US', '90210'], + 'US 5-digit' => ['US', '90210'], 'GB outward+inward' => ['GB', 'SW1A 1AA'], - 'GB short' => ['GB', 'M1 1AE'], - 'NL digits only' => ['NL', '1012'], - 'NL with letters' => ['NL', '1012 AB'], - 'CA' => ['CA', 'K1A 0B1'], - 'CA no space' => ['CA', 'K1A0B1'], - 'JP' => ['JP', '100-0001'], - 'DE' => ['DE', '10115'], - 'BR' => ['BR', '01310-100'], - 'lowercase input' => ['GB', 'sw1a 1aa'], + 'GB short' => ['GB', 'M1 1AE'], + 'NL digits only' => ['NL', '1012'], + 'NL with letters' => ['NL', '1012 AB'], + 'CA' => ['CA', 'K1A 0B1'], + 'CA no space' => ['CA', 'K1A0B1'], + 'JP' => ['JP', '100-0001'], + 'DE' => ['DE', '10115'], + 'BR' => ['BR', '01310-100'], + 'lowercase input' => ['GB', 'sw1a 1aa'], 'surrounding space' => ['US', ' 90210 '], ]); it('rejects postcodes that do not match the given country', function (string $country, string $value): void { expect(ruleAccepts(new PostalCode($country), $value))->toBeFalse(); })->with([ - 'US too short' => ['US', '9021'], - 'US too long' => ['US', '902101'], - 'US letters' => ['US', 'ABCDE'], - 'GB malformed' => ['GB', '12345'], - 'CA missing final digit' => ['CA', 'K1A 0B'], - 'CA barred letter D' => ['CA', 'D1A 0B1'], + 'US too short' => ['US', '9021'], + 'US too long' => ['US', '902101'], + 'US letters' => ['US', 'ABCDE'], + 'GB malformed' => ['GB', '12345'], + 'CA missing final digit' => ['CA', 'K1A 0B'], + 'CA barred letter D' => ['CA', 'D1A 0B1'], 'CA barred letter as second' => ['CA', 'K1I 0B1'], - 'JP without hyphen' => ['JP', '1000001'], - 'NL too many letters' => ['NL', '1012 ABC'], + 'JP without hyphen' => ['JP', '1000001'], + 'NL too many letters' => ['NL', '1012 ABC'], ]); it('accepts a value valid for any of several countries', function (): void { @@ -62,7 +62,7 @@ it('reads the country from another field', function (): void { $rules = [ - 'country' => ['required', 'string'], + 'country' => ['required', 'string'], 'postcode' => PostalCode::reference('country'), ]; @@ -82,7 +82,7 @@ // Without per-row resolution, every row would be validated against the // FIRST row's country — wrong in a way that only shows when rows differ. $rules = [ - 'addresses.*.country' => ['required', 'string'], + 'addresses.*.country' => ['required', 'string'], 'addresses.*.postcode' => PostalCode::reference('addresses.*.country'), ]; @@ -118,10 +118,10 @@ // two are not countries at all. $unknown = array_values(array_filter( Patterns::countries(), - static fn (string $code): bool => Locale::getDisplayRegion('-'.$code, 'en') === $code, + static fn (string $code): bool => Locale::getDisplayRegion('-' . $code, 'en') === $code, )); - expect($unknown)->toBeEmpty('unrecognised country codes: '.implode(', ', $unknown)); + expect($unknown)->toBeEmpty('unrecognised country codes: ' . implode(', ', $unknown)); })->skip(fn (): bool => ! class_exists(Locale::class), 'ext-intl not installed'); it('uses only bounded quantifiers, so no pattern can backtrack catastrophically', function (): void { diff --git a/tests/Rules/Profanity/InlineTermListTest.php b/tests/Rules/Profanity/InlineTermListTest.php index 95d9304..e68d4a2 100644 --- a/tests/Rules/Profanity/InlineTermListTest.php +++ b/tests/Rules/Profanity/InlineTermListTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; use Simtabi\Laranail\Validation\Support\InlineTermList; +use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; /** * The bindable TermList over application-supplied arrays — the one-liner diff --git a/tests/Rules/Profanity/NoProfanityTest.php b/tests/Rules/Profanity/NoProfanityTest.php index 1ee2fa8..880f761 100644 --- a/tests/Rules/Profanity/NoProfanityTest.php +++ b/tests/Rules/Profanity/NoProfanityTest.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Illuminate\Contracts\Translation\Translator; -use Illuminate\Translation\PotentiallyTranslatedString; use Simtabi\Laranail\Validation\Contracts\TermList; +use Illuminate\Translation\PotentiallyTranslatedString; use Simtabi\Laranail\Validation\Rules\Profanity\NoProfanity; /** @@ -14,8 +14,8 @@ * test is about the MATCHING rather than about any particular vocabulary. */ /** - * @param list $terms - * @param list $allowed + * @param list $terms + * @param list $allowed */ function termList(array $terms, array $allowed = []): NoProfanity { @@ -138,7 +138,7 @@ public function allowed(): array // possessive and matches a disjoint class from the character repeats, so // the two never compete for the same input. $rule = termList(['badger'], allowed: ['badgerline']); - $hostile = str_repeat('b', 20000).str_repeat('.', 20000).str_repeat('a', 20000); + $hostile = str_repeat('b', 20000) . str_repeat('.', 20000) . str_repeat('a', 20000); $start = microtime(true); $rule->containsTerm($hostile); diff --git a/tests/Rules/RuleTierTest.php b/tests/Rules/RuleTierTest.php index 93bec2e..a3779e2 100644 --- a/tests/Rules/RuleTierTest.php +++ b/tests/Rules/RuleTierTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Illuminate\Http\Client\Factory as HttpFactory; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\Client\Factory as HttpFactory; use Simtabi\Laranail\Validation\Contracts\PrecognitionSkippable; // ========================================================================= @@ -27,7 +27,7 @@ /** @return list Absolute paths of every rule class in the library. */ function ruleSourceFiles(): array { - $directory = new RecursiveDirectoryIterator(dirname(__DIR__, 2).'/src/Rules'); + $directory = new RecursiveDirectoryIterator(dirname(__DIR__, 2) . '/src/Rules'); $files = []; foreach (new RecursiveIteratorIterator($directory) as $file) { @@ -87,7 +87,7 @@ function tierOf(string $path): string foreach ($writes as $write) { if (str_contains($source, $write)) { - $offenders[] = basename($file).' contains '.$write; + $offenders[] = basename($file) . ' contains ' . $write; } } } @@ -109,11 +109,11 @@ function tierOf(string $path): string // through it, and a rule outside the Database tier having one means // the tier label on its directory is now a lie. if (str_contains($source, '->newQuery(') || str_contains($source, 'DB::')) { - $offenders[] = tierOf($file).'/'.basename($file); + $offenders[] = tierOf($file) . '/' . basename($file); } } - expect($offenders)->toBeEmpty('database access outside the Database tier: '.implode(', ', $offenders)); + expect($offenders)->toBeEmpty('database access outside the Database tier: ' . implode(', ', $offenders)); }); it('requires every Network-tier rule to be skippable during precognition', function (): void { @@ -124,7 +124,7 @@ function tierOf(string $path): string $network = array_filter(ruleSourceFiles(), static fn (string $f): bool => tierOf($f) === 'Network'); foreach ($network as $file) { - $class = 'Simtabi\\Laranail\\Validation\\Rules\\Network\\'.basename($file, '.php'); + $class = 'Simtabi\\Laranail\\Validation\\Rules\\Network\\' . basename($file, '.php'); expect(class_exists($class) && is_a($class, PrecognitionSkippable::class, true)) ->toBeTrue("{$class} performs network IO but is not PrecognitionSkippable"); @@ -142,7 +142,7 @@ function tierOf(string $path): string continue; } - $class = 'Simtabi\\Laranail\\Validation\\Rules\\Database\\'.basename($file, '.php'); + $class = 'Simtabi\\Laranail\\Validation\\Rules\\Database\\' . basename($file, '.php'); expect(is_a($class, PrecognitionSkippable::class, true)) ->toBeFalse("{$class} is database-tier and must not opt out of precognition"); @@ -164,5 +164,5 @@ function tierOf(string $path): string $untiered = array_values(array_filter($files, static fn (string $f): bool => tierOf($f) === '')); - expect($untiered)->toBeEmpty('no tier resolved for: '.implode(', ', $untiered)); + expect($untiered)->toBeEmpty('no tier resolved for: ' . implode(', ', $untiered)); }); diff --git a/tests/Rules/Structure/DelimitedTest.php b/tests/Rules/Structure/DelimitedTest.php index cfc8de4..530d260 100644 --- a/tests/Rules/Structure/DelimitedTest.php +++ b/tests/Rules/Structure/DelimitedTest.php @@ -15,17 +15,17 @@ function delimitedError(Delimited $rule, mixed $value): string it('accepts a list whose every item passes the sub-rules', function (string $value): void { expect(ruleAccepts(new Delimited(['email']), $value))->toBeTrue(); })->with([ - 'single' => 'alice@example.com', - 'two' => 'alice@example.com,bob@example.com', - 'spaced' => 'alice@example.com, bob@example.com', + 'single' => 'alice@example.com', + 'two' => 'alice@example.com,bob@example.com', + 'spaced' => 'alice@example.com, bob@example.com', 'lots of space' => ' alice@example.com , bob@example.com ', ]); it('rejects the list when any single item fails', function (string $value): void { expect(ruleAccepts(new Delimited(['email']), $value))->toBeFalse(); })->with([ - 'first bad' => 'nope,bob@example.com', - 'last bad' => 'alice@example.com,nope', + 'first bad' => 'nope,bob@example.com', + 'last bad' => 'alice@example.com,nope', 'middle bad' => 'alice@example.com,nope,bob@example.com', ]); @@ -90,7 +90,7 @@ function delimitedError(Delimited $rule, mixed $value): string expect(ruleAccepts(new Delimited(['string']), $value))->toBeFalse(); })->with([ 'array' => [['a', 'b']], - 'int' => [42], + 'int' => [42], ]); it('refuses an empty separator at construction', function (): void { diff --git a/tests/Rules/Text/ReservedUsernameListTest.php b/tests/Rules/Text/ReservedUsernameListTest.php index 4ec1697..afe0cbf 100644 --- a/tests/Rules/Text/ReservedUsernameListTest.php +++ b/tests/Rules/Text/ReservedUsernameListTest.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; use Simtabi\Laranail\Validation\Rules\Text\Username; +use Simtabi\Laranail\Validation\Contracts\ReservedUsernameList; use Simtabi\Laranail\Validation\Support\Text\ExtendedReservedUsernameList; it('keeps the floor as the default policy', function (): void { diff --git a/tests/Rules/Text/TextRulesTest.php b/tests/Rules/Text/TextRulesTest.php index 094d095..b034ad0 100644 --- a/tests/Rules/Text/TextRulesTest.php +++ b/tests/Rules/Text/TextRulesTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; +use Illuminate\Support\Facades\Validator; +use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Simtabi\Laranail\Validation\Rules\Text\Username; use Simtabi\Laranail\Validation\Rules\Text\CaseStyle; use Simtabi\Laranail\Validation\Rules\Text\HtmlClean; use Simtabi\Laranail\Validation\Rules\Text\PersonName; -use Simtabi\Laranail\Validation\Rules\Text\Slug; -use Simtabi\Laranail\Validation\Rules\Text\Username; use Simtabi\Laranail\Validation\Rules\Text\WithoutSpaces; // ========================================================================= @@ -228,11 +228,11 @@ expect(ruleAccepts(new CaseStyle($style), $valid))->toBeTrue() ->and(ruleAccepts(new CaseStyle($style), $invalid))->toBeFalse(); })->with([ - 'camel' => [CaseStyle::CAMEL, 'helloWorld', 'HelloWorld'], + 'camel' => [CaseStyle::CAMEL, 'helloWorld', 'HelloWorld'], 'pascal' => [CaseStyle::PASCAL, 'HelloWorld', 'helloWorld'], - 'snake' => [CaseStyle::SNAKE, 'hello_world', 'hello__world'], - 'kebab' => [CaseStyle::KEBAB, 'hello-world', '-hello'], - 'title' => [CaseStyle::TITLE, 'Hello World', 'hello world'], + 'snake' => [CaseStyle::SNAKE, 'hello_world', 'hello__world'], + 'kebab' => [CaseStyle::KEBAB, 'hello-world', '-hello'], + 'title' => [CaseStyle::TITLE, 'Hello World', 'hello world'], ]); it('rejects leading, trailing and doubled separators', function (string $style, string $value): void { diff --git a/tests/Rules/Text/WordsAndSalutationTest.php b/tests/Rules/Text/WordsAndSalutationTest.php index 7609ce3..e11fd64 100644 --- a/tests/Rules/Text/WordsAndSalutationTest.php +++ b/tests/Rules/Text/WordsAndSalutationTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Simtabi\Laranail\Validation\Rules\Text\HtmlClean; use Simtabi\Laranail\Validation\Rules\Text\MaxWords; use Simtabi\Laranail\Validation\Rules\Text\MinWords; +use Simtabi\Laranail\Validation\Rules\Text\HtmlClean; use Simtabi\Laranail\Validation\Rules\Text\Salutation; // ========================================================================= diff --git a/tests/Rules/TrailingNewlineTest.php b/tests/Rules/TrailingNewlineTest.php index 96ff5bf..75a40b7 100644 --- a/tests/Rules/TrailingNewlineTest.php +++ b/tests/Rules/TrailingNewlineTest.php @@ -2,26 +2,26 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Simtabi\Laranail\Validation\Rules\Codes\Isbn; +use Simtabi\Laranail\Validation\Rules\Codes\Issn; use Simtabi\Laranail\Validation\Rules\Banking\Bic; use Simtabi\Laranail\Validation\Rules\Banking\Iban; use Simtabi\Laranail\Validation\Rules\Banking\Isin; -use Simtabi\Laranail\Validation\Rules\Codes\Isbn; -use Simtabi\Laranail\Validation\Rules\Codes\Issn; -use Simtabi\Laranail\Validation\Rules\Colour\CssColor; -use Simtabi\Laranail\Validation\Rules\Crypto\EthereumAddress; -use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; -use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; -use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; -use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; +use Simtabi\Laranail\Validation\Rules\Net\Subdomain; use Simtabi\Laranail\Validation\Rules\Net\DomainName; use Simtabi\Laranail\Validation\Rules\Net\MacAddress; -use Simtabi\Laranail\Validation\Rules\Net\Subdomain; -use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; use Simtabi\Laranail\Validation\Rules\Numbers\Parity; -use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; use Simtabi\Laranail\Validation\Rules\Text\CaseStyle; +use Simtabi\Laranail\Validation\Rules\Colour\CssColor; +use Simtabi\Laranail\Validation\Rules\Identifiers\Jwt; +use Simtabi\Laranail\Validation\Rules\Identifiers\Vin; use Simtabi\Laranail\Validation\Rules\Text\PersonName; -use Simtabi\Laranail\Validation\Rules\Text\Slug; +use Simtabi\Laranail\Validation\Rules\Postal\PostalCode; +use Simtabi\Laranail\Validation\Rules\Identifiers\SemVer; +use Simtabi\Laranail\Validation\Rules\Crypto\EthereumAddress; +use Simtabi\Laranail\Validation\Rules\Numbers\MonetaryAmount; +use Simtabi\Laranail\Validation\Rules\Fiscal\NationalIdentifier; /** * The trailing-newline sweep: every anchored single-line pattern in the rule @@ -38,22 +38,22 @@ */ it('rejects a trailing newline on an otherwise valid value', function (object $rule, string $valid): void { expect(ruleAccepts($rule, $valid))->toBeTrue() - ->and(ruleAccepts($rule, $valid."\n"))->toBeFalse(); + ->and(ruleAccepts($rule, $valid . "\n"))->toBeFalse(); })->with([ - 'Slug' => [fn (): object => new Slug, 'my-slug'], - 'Jwt' => [fn (): object => new Jwt, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.c2ln'], - 'DomainName' => [fn (): object => new DomainName, 'example.com'], - 'PersonName' => [fn (): object => new PersonName, 'Ada Lovelace'], - 'EthereumAddress' => [fn (): object => new EthereumAddress, '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'], - 'SemVer' => [fn (): object => new SemVer, '1.2.3'], - 'Subdomain' => [fn (): object => new Subdomain, 'blog'], - 'MacAddress' => [fn (): object => new MacAddress, '00:1a:2b:3c:4d:5e'], - 'Isbn' => [fn (): object => new Isbn, '0306406152'], - 'Issn' => [fn (): object => new Issn, '2049-3630'], - 'Vin' => [fn (): object => new Vin, '11111111111111111'], - 'Iban' => [fn (): object => new Iban, 'DE89370400440532013000'], - 'Bic' => [fn (): object => new Bic, 'DEUTDEFF'], - 'Isin' => [fn (): object => new Isin, 'US0378331005'], + 'Slug' => [fn (): object => new Slug, 'my-slug'], + 'Jwt' => [fn (): object => new Jwt, 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.c2ln'], + 'DomainName' => [fn (): object => new DomainName, 'example.com'], + 'PersonName' => [fn (): object => new PersonName, 'Ada Lovelace'], + 'EthereumAddress' => [fn (): object => new EthereumAddress, '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'], + 'SemVer' => [fn (): object => new SemVer, '1.2.3'], + 'Subdomain' => [fn (): object => new Subdomain, 'blog'], + 'MacAddress' => [fn (): object => new MacAddress, '00:1a:2b:3c:4d:5e'], + 'Isbn' => [fn (): object => new Isbn, '0306406152'], + 'Issn' => [fn (): object => new Issn, '2049-3630'], + 'Vin' => [fn (): object => new Vin, '11111111111111111'], + 'Iban' => [fn (): object => new Iban, 'DE89370400440532013000'], + 'Bic' => [fn (): object => new Bic, 'DEUTDEFF'], + 'Isin' => [fn (): object => new Isin, 'US0378331005'], 'CaseStyle (kebab)' => [fn (): object => new CaseStyle('kebab'), 'my-var'], ]); @@ -67,12 +67,12 @@ */ it('tolerates surrounding whitespace where the rule normalizes by design', function (object $rule, string $valid): void { expect(ruleAccepts($rule, $valid))->toBeTrue() - ->and(ruleAccepts($rule, $valid."\n"))->toBeTrue() - ->and(ruleAccepts($rule, ' '.$valid.' '))->toBeTrue(); + ->and(ruleAccepts($rule, $valid . "\n"))->toBeTrue() + ->and(ruleAccepts($rule, ' ' . $valid . ' '))->toBeTrue(); })->with([ - 'PostalCode (US)' => [fn (): object => new PostalCode('US'), '12345'], - 'CssColor' => [fn (): object => new CssColor, '#fff'], - 'MonetaryAmount' => [fn (): object => new MonetaryAmount, '10.99'], - 'Parity (even)' => [fn (): object => new Parity('even'), '12'], + 'PostalCode (US)' => [fn (): object => new PostalCode('US'), '12345'], + 'CssColor' => [fn (): object => new CssColor, '#fff'], + 'MonetaryAmount' => [fn (): object => new MonetaryAmount, '10.99'], + 'Parity (even)' => [fn (): object => new Parity('even'), '12'], 'NationalIdentifier (US SSN)' => [fn (): object => new NationalIdentifier('US'), '212-09-4553'], ]); diff --git a/tests/Rules/Vendor/VendorIdentifierTest.php b/tests/Rules/Vendor/VendorIdentifierTest.php index b277ef4..3deaf2c 100644 --- a/tests/Rules/Vendor/VendorIdentifierTest.php +++ b/tests/Rules/Vendor/VendorIdentifierTest.php @@ -7,31 +7,31 @@ it('accepts a well-formed identifier for each vendor', function (string $vendor, string $value): void { expect(VendorIdentifier::passes($value, $vendor))->toBeTrue(); })->with([ - 'ga4' => [VendorIdentifier::GOOGLE_ANALYTICS, 'G-ABCDE12345'], - 'gtm' => [VendorIdentifier::GOOGLE_TAG_MANAGER, 'GTM-ABC1234'], - 'pixel 15' => [VendorIdentifier::FACEBOOK_PIXEL, '123456789012345'], - 'pixel 16' => [VendorIdentifier::FACEBOOK_PIXEL, '1234567890123456'], - 'tenant uuid' => [VendorIdentifier::MICROSOFT_TENANT, '72f988bf-86f1-41af-91ab-2d7cd011db47'], + 'ga4' => [VendorIdentifier::GOOGLE_ANALYTICS, 'G-ABCDE12345'], + 'gtm' => [VendorIdentifier::GOOGLE_TAG_MANAGER, 'GTM-ABC1234'], + 'pixel 15' => [VendorIdentifier::FACEBOOK_PIXEL, '123456789012345'], + 'pixel 16' => [VendorIdentifier::FACEBOOK_PIXEL, '1234567890123456'], + 'tenant uuid' => [VendorIdentifier::MICROSOFT_TENANT, '72f988bf-86f1-41af-91ab-2d7cd011db47'], 'tenant alias' => [VendorIdentifier::MICROSOFT_TENANT, 'common'], - 'aws' => [VendorIdentifier::AWS_REGION, 'us-east-1'], - 'aws long' => [VendorIdentifier::AWS_REGION, 'ap-southeast-4'], + 'aws' => [VendorIdentifier::AWS_REGION, 'us-east-1'], + 'aws long' => [VendorIdentifier::AWS_REGION, 'ap-southeast-4'], 'aws govcloud' => [VendorIdentifier::AWS_REGION, 'us-gov-west-1'], - 'discord' => [VendorIdentifier::DISCORD_USERNAME, 'alice.smith'], + 'discord' => [VendorIdentifier::DISCORD_USERNAME, 'alice.smith'], ]); it('rejects a plausible but wrong identifier', function (string $vendor, string $value): void { expect(VendorIdentifier::passes($value, $vendor))->toBeFalse(); })->with([ - 'ga4 too short' => [VendorIdentifier::GOOGLE_ANALYTICS, 'G-ABCDE1234'], - 'ga4 old UA form' => [VendorIdentifier::GOOGLE_ANALYTICS, 'UA-12345-1'], - 'gtm no prefix' => [VendorIdentifier::GOOGLE_TAG_MANAGER, 'ABC1234'], - 'pixel too short' => [VendorIdentifier::FACEBOOK_PIXEL, '12345'], + 'ga4 too short' => [VendorIdentifier::GOOGLE_ANALYTICS, 'G-ABCDE1234'], + 'ga4 old UA form' => [VendorIdentifier::GOOGLE_ANALYTICS, 'UA-12345-1'], + 'gtm no prefix' => [VendorIdentifier::GOOGLE_TAG_MANAGER, 'ABC1234'], + 'pixel too short' => [VendorIdentifier::FACEBOOK_PIXEL, '12345'], 'pixel non-numeric' => [VendorIdentifier::FACEBOOK_PIXEL, '12345678901234a'], 'tenant not a uuid' => [VendorIdentifier::MICROSOFT_TENANT, '72f988bf86f141af91ab2d7cd011db47'], - 'aws no digit' => [VendorIdentifier::AWS_REGION, 'us-east'], - 'aws uppercase' => [VendorIdentifier::AWS_REGION, 'US-EAST-1'], + 'aws no digit' => [VendorIdentifier::AWS_REGION, 'us-east'], + 'aws uppercase' => [VendorIdentifier::AWS_REGION, 'US-EAST-1'], 'discord uppercase' => [VendorIdentifier::DISCORD_USERNAME, 'Alice'], - 'discord dots' => [VendorIdentifier::DISCORD_USERNAME, 'alice..smith'], + 'discord dots' => [VendorIdentifier::DISCORD_USERNAME, 'alice..smith'], 'discord too short' => [VendorIdentifier::DISCORD_USERNAME, 'a'], ]); diff --git a/tests/SchemaRulesMergeTest.php b/tests/SchemaRulesMergeTest.php index 92f339f..3fe0bda 100644 --- a/tests/SchemaRulesMergeTest.php +++ b/tests/SchemaRulesMergeTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); +use Simtabi\Laranail\Validation\RuleSet; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\FluentRule; use Simtabi\Laranail\Validation\FluentSchema; +use Illuminate\Validation\ValidationException; use Simtabi\Laranail\Validation\HasFluentRules; -use Simtabi\Laranail\Validation\RuleSet; use Simtabi\Laranail\Validation\Tests\Fixtures\MergeRulesBaseRequest; use Simtabi\Laranail\Validation\Tests\Fixtures\MergeSchemaBaseRequest; use Simtabi\Laranail\Validation\Tests\Fixtures\ProvidesMergeSchemaViaTrait; @@ -29,7 +29,7 @@ function schemaBaseWithChildRules(): MergeSchemaBaseRequest public function rules(): array { return [ - 'shared' => FluentRule::string()->required()->in(['child']), + 'shared' => FluentRule::string()->required()->in(['child']), 'child_only' => FluentRule::string()->required(), ]; } @@ -44,7 +44,7 @@ function rulesBaseWithChildSchema(): MergeRulesBaseRequest public function schema(FluentSchema $rules): array { return [ - 'shared' => $rules->string()->required()->in(['child']), + 'shared' => $rules->string()->required()->in(['child']), 'child_only' => $rules->string()->required(), ]; } @@ -62,7 +62,7 @@ function traitSchemaWithBodyRules(): FormRequest public function rules(): array { return [ - 'shared' => FluentRule::string()->required()->in(['body']), + 'shared' => FluentRule::string()->required()->in(['body']), 'body_only' => FluentRule::string()->required(), ]; } @@ -78,8 +78,8 @@ public function authorize(): bool it('merges base schema() and child rules(), applying both layers', function (): void { $request = bootFormRequest(schemaBaseWithChildRules(), [ - 'shared' => 'child', - 'base_only' => 'x', + 'shared' => 'child', + 'base_only' => 'x', 'child_only' => 'y', ]); @@ -94,8 +94,8 @@ public function authorize(): bool // 'base' satisfies the base schema() (in:base) but not the child rules() // (in:child); the more-derived child wins, so this must fail. bootFormRequest(schemaBaseWithChildRules(), [ - 'shared' => 'base', - 'base_only' => 'x', + 'shared' => 'base', + 'base_only' => 'x', 'child_only' => 'y', ])->validateResolved(); })->throws(ValidationException::class); @@ -104,7 +104,7 @@ public function authorize(): bool // base_only exists only on the base schema(); omitting it must fail, // proving the base layer is merged in rather than shadowed. bootFormRequest(schemaBaseWithChildRules(), [ - 'shared' => 'child', + 'shared' => 'child', 'child_only' => 'y', ])->validateResolved(); })->throws(ValidationException::class); @@ -113,8 +113,8 @@ public function authorize(): bool it('merges base rules() and child schema(), applying both layers', function (): void { $request = bootFormRequest(rulesBaseWithChildSchema(), [ - 'shared' => 'child', - 'base_only' => 'x', + 'shared' => 'child', + 'base_only' => 'x', 'child_only' => 'y', ]); @@ -127,8 +127,8 @@ public function authorize(): bool it('lets the child schema() win the shared key over the base rules()', function (): void { bootFormRequest(rulesBaseWithChildSchema(), [ - 'shared' => 'base', - 'base_only' => 'x', + 'shared' => 'base', + 'base_only' => 'x', 'child_only' => 'y', ])->validateResolved(); })->throws(ValidationException::class); @@ -137,9 +137,9 @@ public function authorize(): bool it('lets a body rules() win over a trait-provided schema()', function (): void { $request = bootFormRequest(traitSchemaWithBodyRules(), [ - 'shared' => 'body', + 'shared' => 'body', 'trait_only' => 'x', - 'body_only' => 'y', + 'body_only' => 'y', ]); $request->validateResolved(); @@ -153,9 +153,9 @@ public function authorize(): bool // 'trait' satisfies the trait schema() but not the body rules(); the body // definition is more specific than the trait import, so it wins → fail. bootFormRequest(traitSchemaWithBodyRules(), [ - 'shared' => 'trait', + 'shared' => 'trait', 'trait_only' => 'x', - 'body_only' => 'y', + 'body_only' => 'y', ])->validateResolved(); })->throws(ValidationException::class); @@ -215,9 +215,9 @@ public function schema(FluentSchema $rules): array }; $booted = bootFormRequest($request, [ - 'shared' => 'base', - 'base_only' => 'x', - 'child_only' => 'y', + 'shared' => 'base', + 'base_only' => 'x', + 'child_only' => 'y', 'schema_only' => 'z', ]); $booted->validateResolved(); diff --git a/tests/Schemas/PersonNameSchemaTest.php b/tests/Schemas/PersonNameSchemaTest.php index 608fb03..2e41c22 100644 --- a/tests/Schemas/PersonNameSchemaTest.php +++ b/tests/Schemas/PersonNameSchemaTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -use Illuminate\Translation\ArrayLoader; use Illuminate\Translation\Translator; -use Simtabi\Laranail\Validation\Rules\Text\PersonName; +use Illuminate\Translation\ArrayLoader; use Simtabi\Laranail\Validation\RuleSet; +use Simtabi\Laranail\Validation\Rules\Text\PersonName; use Simtabi\Laranail\Validation\Schemas\PersonNameSchema; /* @@ -26,10 +26,10 @@ it('refuses a payload carrying no name at all', function (): void { $payloads = [ - 'no keys at all' => [], - 'all empty strings' => ['first_name' => '', 'middle_name' => '', 'last_name' => ''], - 'all null' => ['first_name' => null, 'middle_name' => null, 'last_name' => null], - 'whitespace only' => ['first_name' => ' ', 'middle_name' => "\t", 'last_name' => ' '], + 'no keys at all' => [], + 'all empty strings' => ['first_name' => '', 'middle_name' => '', 'last_name' => ''], + 'all null' => ['first_name' => null, 'middle_name' => null, 'last_name' => null], + 'whitespace only' => ['first_name' => ' ', 'middle_name' => "\t", 'last_name' => ' '], 'only unrelated keys' => ['email' => 'a@b.test'], ]; @@ -44,13 +44,13 @@ // path the application actually uses. The validator has to agree with the // schema exactly: anything holding one of the three must validate. $combinations = [ - 'first only' => ['first_name' => 'Ada'], - 'middle only' => ['middle_name' => 'Prince'], - 'last only' => ['last_name' => 'Bono'], + 'first only' => ['first_name' => 'Ada'], + 'middle only' => ['middle_name' => 'Prince'], + 'last only' => ['last_name' => 'Bono'], 'first and middle' => ['first_name' => 'Ada', 'middle_name' => 'Byron'], - 'first and last' => ['first_name' => 'Ada', 'last_name' => 'Lovelace'], - 'middle and last' => ['middle_name' => 'Byron', 'last_name' => 'Lovelace'], - 'all three' => ['first_name' => 'Ada', 'middle_name' => 'Byron', 'last_name' => 'Lovelace'], + 'first and last' => ['first_name' => 'Ada', 'last_name' => 'Lovelace'], + 'middle and last' => ['middle_name' => 'Byron', 'last_name' => 'Lovelace'], + 'all three' => ['first_name' => 'Ada', 'middle_name' => 'Byron', 'last_name' => 'Lovelace'], ]; $schema = PersonNameSchema::make(); @@ -58,7 +58,7 @@ foreach ($combinations as $label => $combination) { $result = $schema->toRuleSet()->check($schema->normalise($combination)); - expect($result->passes())->toBeTrue("{$label}: ".($result->errors()->first() ?? '')); + expect($result->passes())->toBeTrue("{$label}: " . ($result->errors()->first() ?? '')); } }); @@ -119,7 +119,7 @@ // given names, or a patronymic, or one name, is not an edge case — the // three-column split is a guess about a naming culture. $shapes = [ - 'two fields' => [['given_name', 'family_name'], ['family_name' => 'Mokoena']], + 'two fields' => [['given_name', 'family_name'], ['family_name' => 'Mokoena']], 'four fields' => [ ['first_name', 'second_name', 'third_name', 'last_name'], ['second_name' => 'Byron', 'last_name' => 'Lovelace'], @@ -135,7 +135,7 @@ $result = $schema->toRuleSet()->check($schema->normalise($data)); expect($schema->fields())->toBe($fields) - ->and($result->passes())->toBeTrue("{$label}: ".($result->errors()->first() ?? '')); + ->and($result->passes())->toBeTrue("{$label}: " . ($result->errors()->first() ?? '')); } }); @@ -150,11 +150,11 @@ it('normalises exactly the fields it was declared with', function (): void { expect(PersonNameSchema::make('given_name', 'family_name')->normalise([ - 'given_name' => ' Ada ', + 'given_name' => ' Ada ', 'family_name' => ' ', - 'email' => 'ignored@example.test', + 'email' => 'ignored@example.test', ]))->toBe([ - 'given_name' => 'Ada', + 'given_name' => 'Ada', 'family_name' => null, ]); }); @@ -163,9 +163,9 @@ // A partial row is how an untouched optional input becomes '' in the // database: present, not null, sorting before every real value. expect(PersonNameSchema::make()->normalise([]))->toBe([ - 'first_name' => null, + 'first_name' => null, 'middle_name' => null, - 'last_name' => null, + 'last_name' => null, ]); }); @@ -183,13 +183,13 @@ expect(PersonNameSchema::make()->toRuleSet()->check(['first_name' => $value])->passes()) ->toBeTrue(); })->with([ - 'two' => ['Ada Byron'], - 'three' => ['Ada Byron Gordon'], - 'doubled space' => ['Ada Byron'], + 'two' => ['Ada Byron'], + 'three' => ['Ada Byron Gordon'], + 'doubled space' => ['Ada Byron'], 'compound family name' => ['Ait Ben Haddou'], - 'hyphenated' => ['Jean-Luc'], - 'apostrophe' => ["O'Neill"], - 'non-latin' => ['李 明'], + 'hyphenated' => ['Jean-Luc'], + 'apostrophe' => ["O'Neill"], + 'non-latin' => ['李 明'], ]); it('can insist on exactly one name per field', function (): void { @@ -250,9 +250,9 @@ expect(PersonNameSchema::make()->toRuleSet()->check(['first_name' => $value])->passes()) ->toBeFalse(); })->with([ - 'digits' => ['Henry 8'], - 'markup' => ['