Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 247 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,264 @@
# Simple component template
# Aegisora String Length Rule

[![Latest Version](https://img.shields.io/packagist/v/aegisora/string-length-rule?style=flat-square)](https://packagist.org/packages/aegisora/string-length-rule)
[![Total Downloads](https://img.shields.io/packagist/dt/aegisora/string-length-rule?style=flat-square)](https://packagist.org/packages/aegisora/string-length-rule)
![Code Coverage Badge](./badge.svg)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
![PHPStan Badge](https://img.shields.io/badge/PHPStan-level%209-brightgreen.svg?style=flat)

String Length Rule provides a simple, rule-based string length validation implementation for the Aegisora ecosystem.

It is built on top of [`aegisora/rule-contract`](https://github.com/Aegisora/rule-contract) and follows its strict validation architecture, ensuring consistent and predictable behavior across applications.

This rule is useful for validating user input, form fields, usernames, passwords, API request parameters, database column constraints, and any other string that must satisfy a length boundary.

---

## 📑 Table of Contents
- [Features](#-features)
- [Installation](#-installation)
- [Core Concept](#-core-concept)
- [Basic Usage](#-basic-usage)
- [Valid vs Invalid](#-valid-vs-invalid)
- [Validation Result](#-validation-result)
- [Guardian Usage](#-guardian-usage)
- [Real-World Examples](#-real-world-examples)
- [Factory Methods](#-factory-methods)
- [Architecture](#-architecture)
- [License](#-license)
- [Contributing](#-contributing)
- [Support](#-support)

---

## ✨ Features
- 🔹 Lightweight and dependency-free except `aegisora/rule-contract`
- 🔹 Validates a string length against a lower bound, an upper bound, or a range
- 🔹 Supports strict (`>`, `<`) and inclusive (`>=`, `<=`) comparisons
- 🔹 Counts characters (not bytes) via native `mb_strlen()`, so multibyte strings are measured correctly
- 🔹 Rejects non-string input as an invalid context
- 🔹 Fully compatible with Aegisora validation pipeline
- 🔹 Strict `Context` → `Result` validation flow
- 🔹 No raw booleans — only structured results
- 🔹 Safe execution via base `Rule` abstraction
- 🔹 Expressive factory API for every boundary variation
- 🔹 Ready to use out of the box

---

## 📦 Installation

```bash
composer require aegisora/string-length-rule
```

---

## 🚀 Core Concept

This package implements a single validation rule with several factory variations:

- accepts a string value via `Context`
- checks whether the string length satisfies the configured boundary
- returns a standardized `Result`

Under the hood it wraps the common boilerplate:

```php
$length = mb_strlen($value);

if ($length < $min || $length > $max) {
// length is out of the allowed boundary
}
```

into a reusable rule that reports its outcome through a `Result` object instead of a raw boolean.

---

## 🏗️ Basic Usage

```php
use Aegisora\RuleContract\Models\Context;
use Aegisora\Rules\StringLengthRule;

$result = StringLengthRule::createGreaterThanOrEqualTo(8)->validate(Context::create('super-secret'));

if ($result->isValid()) {
// length satisfies the boundary
} else {
// length is out of the allowed boundary
}
```

---

## ✅ Valid vs Invalid

The rule passes when the string length satisfies the configured boundary and fails otherwise. The length is measured in characters via `mb_strlen()`.

### Lower bound

```php
StringLengthRule::createGreaterThan(3)->validate(Context::create('abcd')); // valid — 4 > 3
StringLengthRule::createGreaterThan(3)->validate(Context::create('abc')); // invalid — 3 is not > 3

StringLengthRule::createGreaterThanOrEqualTo(3)->validate(Context::create('abc')); // valid — 3 >= 3
StringLengthRule::createGreaterThanOrEqualTo(3)->validate(Context::create('ab')); // invalid — 2 < 3
```

### Upper bound

```php
StringLengthRule::createLessThan(3)->validate(Context::create('ab')); // valid — 2 < 3
StringLengthRule::createLessThan(3)->validate(Context::create('abc')); // invalid — 3 is not < 3

StringLengthRule::createLessThanOrEqualTo(3)->validate(Context::create('abc')); // valid — 3 <= 3
StringLengthRule::createLessThanOrEqualTo(3)->validate(Context::create('abcd')); // invalid — 4 > 3
```

### Range

```php
StringLengthRule::createBetween(2, 4)->validate(Context::create('abc')); // valid — 2 <= 3 <= 4
StringLengthRule::createBetween(2, 4)->validate(Context::create('a')); // invalid — 1 < 2

StringLengthRule::createBetweenExclusive(2, 4)->validate(Context::create('abc')); // valid — 2 < 3 < 4
StringLengthRule::createBetweenExclusive(2, 4)->validate(Context::create('ab')); // invalid — 2 is not > 2

StringLengthRule::createBetweenMinExclusive(2, 4)->validate(Context::create('abcd')); // valid — 2 < 4 <= 4
StringLengthRule::createBetweenMinExclusive(2, 4)->validate(Context::create('ab')); // invalid — 2 is not > 2

StringLengthRule::createBetweenMaxExclusive(2, 4)->validate(Context::create('ab')); // valid — 2 <= 2 < 4
StringLengthRule::createBetweenMaxExclusive(2, 4)->validate(Context::create('abcd')); // invalid — 4 is not < 4
```

---

## 🧪 Validation Result

If the length satisfies the boundary, the rule returns a valid result.

`$result->isValid(); // true`

If the length is out of the boundary, the rule returns an invalid result.

```php
$result->isValid(); // false
$result->getFailedRuleCode(); // string_length_rule
```

If the context value is not a string, the rule throws:

`Aegisora\RuleContract\Exceptions\InvalidRuleContextException`

---

## 🔗 Guardian Usage

This rule can be used together with `aegisora/guardian` to build fluent validation pipelines.

```php
use Aegisora\Guardian\Guardian;
use Aegisora\Rules\StringLengthRule;
use App\Exceptions\InvalidUsernameException;

$guardian = new Guardian();

$guardian
->that($username)
->must(StringLengthRule::createBetween(3, 32), new InvalidUsernameException())
->validate();
```

If the length is out of the allowed boundary, `Guardian` throws the provided domain exception.

---

## 🧭 Real-World Examples

String Length Rule is useful for enforcing length constraints before values are persisted or processed.

Examples

```text
User Registration:

require a username between 3 and 32 characters
```
```text
Security:

require a password of at least 8 characters
```
```text
Database:

ensure a value fits a VARCHAR column limit
```
```text
API:

reject request parameters that exceed a maximum length
```

---

## 🧩 Factory Methods
`StringLengthRule::createGreaterThan($length);`
- passes when the string length is strictly greater than `$length`

`StringLengthRule::createGreaterThanOrEqualTo($length);`
- passes when the string length is greater than or equal to `$length`

`StringLengthRule::createLessThan($length);`
- passes when the string length is strictly less than `$length`

`StringLengthRule::createLessThanOrEqualTo($length);`
- passes when the string length is less than or equal to `$length`

`StringLengthRule::createBetween($min, $max);`
- passes when the string length is between `$min` and `$max`, both boundaries inclusive (`$min <= length <= $max`)

`StringLengthRule::createBetweenExclusive($min, $max);`
- passes when the string length is between `$min` and `$max`, both boundaries exclusive (`$min < length < $max`)

`StringLengthRule::createBetweenMinExclusive($min, $max);`
- passes when the string length is between `$min` (exclusive) and `$max` (inclusive) (`$min < length <= $max`)

`StringLengthRule::createBetweenMaxExclusive($min, $max);`
- passes when the string length is between `$min` (inclusive) and `$max` (exclusive) (`$min <= length < $max`)

`StringLengthRule::createGreaterThanOrEqualTo($length)->validate($context);`
- `$context` — `Context` wrapping the string value to validate

---

## 🏛️ Architecture

This package relies on [`aegisora/rule-contract`](https://github.com/Aegisora/rule-contract).

Flow:
1. `validate()` is called
2. `Context` is passed in
3. The string value is extracted from context (non-strings raise `InvalidRuleContextException`)
4. The length is measured with `mb_strlen()`
5. The length is compared against the configured boundary
6. `Result` is returned — valid on success, invalid with the `string_length_rule` code on failure

All logic is safely handled by Rule contract.

---

## ⚖️ License

This package is open-source and licensed under the MIT License. See the LICENSE for details.
This package is open-source and licensed under the MIT License. See the [LICENSE](LICENSE) for details.

---

## 🌱 Contributing

Contributions are welcome and greatly appreciated!. See the CONTRIBUTING for details.
Contributions are welcome and greatly appreciated! See the [CONTRIBUTING](CONTRIBUTING.md) for details.

---

Expand Down
8 changes: 4 additions & 4 deletions badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 47 additions & 3 deletions clover.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1648587867">
<project timestamp="1648587867">
<metrics files="0" loc="0" ncloc="0" classes="0" methods="0" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="0" coveredelements="0"/>
<coverage generated="1787404195">
<project timestamp="1787404195">
<file name="/home/runner/work/string-length-rule/string-length-rule/src/StringLengthRule.php">
<class name="Aegisora\Rules\StringLengthRule" namespace="global">
<metrics complexity="19" methods="11" coveredmethods="11" conditionals="0" coveredconditionals="0" statements="27" coveredstatements="27" elements="38" coveredelements="38"/>
</class>
<line num="17" type="method" name="__construct" visibility="private" complexity="1" crap="1" count="42"/>
<line num="23" type="stmt" count="42"/>
<line num="24" type="stmt" count="42"/>
<line num="25" type="stmt" count="42"/>
<line num="26" type="stmt" count="42"/>
<line num="29" type="method" name="createGreaterThan" visibility="public" complexity="1" crap="1" count="4"/>
<line num="31" type="stmt" count="4"/>
<line num="34" type="method" name="createGreaterThanOrEqualTo" visibility="public" complexity="1" crap="1" count="17"/>
<line num="36" type="stmt" count="17"/>
<line num="39" type="method" name="createLessThan" visibility="public" complexity="1" crap="1" count="4"/>
<line num="41" type="stmt" count="4"/>
<line num="44" type="method" name="createLessThanOrEqualTo" visibility="public" complexity="1" crap="1" count="3"/>
<line num="46" type="stmt" count="3"/>
<line num="49" type="method" name="createBetween" visibility="public" complexity="1" crap="1" count="5"/>
<line num="53" type="stmt" count="5"/>
<line num="56" type="method" name="createBetweenExclusive" visibility="public" complexity="1" crap="1" count="3"/>
<line num="60" type="stmt" count="3"/>
<line num="63" type="method" name="createBetweenMinExclusive" visibility="public" complexity="1" crap="1" count="3"/>
<line num="67" type="stmt" count="3"/>
<line num="70" type="method" name="createBetweenMaxExclusive" visibility="public" complexity="1" crap="1" count="3"/>
<line num="74" type="stmt" count="3"/>
<line num="77" type="method" name="executeValidate" visibility="protected" complexity="3" crap="3" count="42"/>
<line num="79" type="stmt" count="42"/>
<line num="81" type="stmt" count="42"/>
<line num="82" type="stmt" count="14"/>
<line num="85" type="stmt" count="28"/>
<line num="86" type="stmt" count="16"/>
<line num="87" type="stmt" count="28"/>
<line num="90" type="method" name="isSatisfiedBy" visibility="private" complexity="7" crap="7" count="28"/>
<line num="92" type="stmt" count="28"/>
<line num="93" type="stmt" count="21"/>
<line num="95" type="stmt" count="21"/>
<line num="96" type="stmt" count="6"/>
<line num="100" type="stmt" count="22"/>
<line num="101" type="stmt" count="18"/>
<line num="103" type="stmt" count="18"/>
<line num="104" type="stmt" count="6"/>
<line num="108" type="stmt" count="16"/>
<metrics loc="111" ncloc="111" classes="1" methods="11" coveredmethods="11" conditionals="0" coveredconditionals="0" statements="27" coveredstatements="27" elements="38" coveredelements="38"/>
</file>
<metrics files="1" loc="111" ncloc="111" classes="1" methods="11" coveredmethods="11" conditionals="0" coveredconditionals="0" statements="27" coveredstatements="27" elements="38" coveredelements="38"/>
</project>
</coverage>
33 changes: 24 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
{
"name": "aegisora/<REPOSITORY_NAME>",
"description": "<REPOSITORY_DESCRIPTION>",
"name": "aegisora/string-length-rule",
"description": "String Length Rule provides a simple, rule-based string length validation implementation for the Aegisora ecosystem",
"type": "library",
"keywords": ["php", "aegisora", "aegisora-ecosystem"],
"homepage": "https://github.com/Aegisora/<REPOSITORY_NAME>",
"keywords": [
"php",
"aegisora",
"aegisora-ecosystem",
"rule",
"validation",
"validator",
"string",
"string-length",
"length",
"min-length",
"max-length",
"range",
"mbstring"
],
"homepage": "https://github.com/Aegisora/string-length-rule",
"support": {
"issues": "https://github.com/Aegisora/<REPOSITORY_NAME>/issues",
"source": "https://github.com/Aegisora/<REPOSITORY_NAME>"
"issues": "https://github.com/Aegisora/string-length-rule/issues",
"source": "https://github.com/Aegisora/string-length-rule"
},
"authors": [
{
Expand All @@ -17,7 +31,8 @@
],
"license": "MIT",
"require": {
"php": ">=7.4"
"php": ">=7.4",
"aegisora/rule-contract": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6",
Expand All @@ -26,12 +41,12 @@
},
"autoload": {
"psr-4": {
"Aegisora\\<PACKAGE>\\": "src/"
"Aegisora\\Rules\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Aegisora\\<PACKAGE>\\Tests\\": "tests/"
"Aegisora\\Rules\\Tests\\": "tests/"
}
},
"config": {
Expand Down
Loading
Loading