PHPackages                             affinity4/validate - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. affinity4/validate

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

affinity4/validate
==================

Trait which uses magic methods to automatically validate properties using annotations

0.0.1(4y ago)041MITPHP

Since May 17Pushed 4y ago1 watchersCompare

[ Source](https://github.com/affinity4/validate)[ Packagist](https://packagist.org/packages/affinity4/validate)[ RSS](/packages/affinity4-validate/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (4)Dependencies (3)Versions (2)Used By (0)

Affinity4/Validate
==================

[](#affinity4validate)

[![Affinity4](https://camo.githubusercontent.com/b27388e2df3e2c83d3d738365d4755b81b363915d5ac856583f3c3dff422fd7a/68747470733a2f2f636972636c6563692e636f6d2f67682f616666696e697479342f76616c69646174652e7376673f7374796c653d737667)](https://circleci.com/gh/affinity4/validate)

Affinity4 Validate is a trait which can be added to any class to enable protected/private property validation.

Once Affinity4 validate is added to a class, any private or protected property can have complex validation assiated by added the @validation docblock tag

```
use Affinity4\Validate\Validate;

class ClassWithValidation
{
    use Validate;

    /**
     * @validation type(int:unsigned)
     */
    protected $id;

    /**
     * @validation type(string:alphanumeric)
     */
    protected $username;
}

$Validate = new ClassWithValidation;

$Validate->id = null
$Validate->username = 'not_alphanumeric123';

if (!$Validate->isValid()) {
    /*
    The validation errors are grouped by property...
    $validations_errors = [
        'id' => ValidationError([
            [
                "class" => "\ClassWithValidation",
                "property" => "id"
                "value" => null,
                "error" => "Must be of type string"
            ]
        ]),
        'username => ValidationError([
            [
                "class" => "\ClassWithValidation",
                "property" => "username",
                'value' => "not_alphanumeric123",
                'error' => "Must be alphanumeric (letters and numbers only)"
            ]
        ])
    ]
    */
    foreach ($Validate->getValidationErrors() as $ValidationErrors) {
        /*
        NOTE:
        $ValidationErrors is an instance of \Affinity4\Validate\ValidationErrors
        See section ValidationErrors object
        */
    }
}
```

Why only private and protected properties?
------------------------------------------

[](#why-only-private-and-protected-properties)

Affinity4 Validate use the \_\_set magic method to validate each property which has a @validation annotation. This means only private/protected properties can be validated.

Validators
----------

[](#validators)

@validation strings have three parts:

type($type:$validator|$validator)

The $type validation will happen first. This will be a straight forward check of the type (e.g. is\_int is\_string etc)

The $validators are a pipe separated list of validations to happen after the $type validator, which the exception of cast, which is more like a before middleware to attempt to cast the value to the correct type before validation

### String

[](#string)

Validates property value is a string. NOTE: Integers will not pass this validation. Castable types (e.g. objects with \_\_toString) will not pass this validation unless 'cast' is passed as a validator. See Cast section below

ValueStatus123Fail'123'Pass```
@validation type(string)
```

Int
---

[](#int)

Validates property value is an integer. NOTE: Numeric strings will not pass this validation. Castable types (e.g. a callable returning an int) will not pass this validation unless 'cast' is passed as a validator. See Cast section below

ValueStatus'123'Fail123Pass```
@validation type(int)
```

### Numeric

[](#numeric)

Valdiates a value is numeric

ValueStatus"0x539"Fail"123"Pass```
/**
     * Numeric
     *
     * @validation type(numeric)
     *
     * @var numeric
     */
    protected $numeric;
```

### Alpha

[](#alpha)

**NOTE: Strings only**
Validates a string is alphabet characters only (no numbers or symbols)

ValueStatus"123"Fail"alpha!"Fail"abcDEF"Pass```
/**
     * Alpha
     *
     * @validation type(string:alpha)
     *
     * @var string
     */
    protected $alpha;
```

### Alnum/Alphanum/Alphanumeric

[](#alnumalphanumalphanumeric)

**NOTE: Strings only**
Validates a string contains only alphanumeric characters (numbers and letters only)

ValueStatus"i-am-not-alnum"Fail"iAmAlnum123"Pass```
@validation type(string:alnum)
// or @validation type(string:alphanum)
// or @validation type(string:alphanumeric)
```

### snakecase

[](#snakecase)

**NOTE: Strings only**
Validates a string contains is snake\_case

ValueStatus"kebab-case"Fail"snake\_case"Pass```
@validation type(string:snakecase)
```

### kebabcase

[](#kebabcase)

**NOTE: Strings only**
Validates a string is kebab-case

ValueStatus"snake\_case"Fail"kebab-case"Pass```
@validation type(string:kebabcase)
```

### constantcase/uppersnakecase

[](#constantcaseuppersnakecase)

**NOTE: Strings only**
Validates a string is CONSTANT\_CASE (aka UPPER\_SNAKE\_CASE)

ValueStatus"snake\_case"Fail"CONSTANT\_CASE"Pass```
@validation type(string:constantcase)
// or @validation type(string:uppersnakecase)
```

### camelcase

[](#camelcase)

**NOTE: Strings only**
Validates a string is camelCase

ValueStatus"snake\_case"Fail"PascalCase"Fail"camelCase"Pass```
@validation type(string:camelcase)
```

### pascalcase/camelcaps/studlycaps/capitalcase

[](#pascalcasecamelcapsstudlycapscapitalcase)

**NOTE: Strings only**
Validates a string is PascalCase (aka CamelCaps, aka StudlyCaps, aka CapitalCase)

ValueStatus"snake\_case"Fail"camelCase"Fail"PascalCase"Pass```
@validation type(string:pascalcase)
// or @validation type(string:camelcaps)
// or @validation type(string:studlycaps)
// or @validation type(string:capitalcase)
```

### traincase

[](#traincase)

**NOTE: Strings only**
Validates a string is Train-Case

ValueStatus"COBOL-CASE"Fail"camelCase"Fail"Train-Case"Pass```
@validation type(string:traincase)
```

### Unsiged

[](#unsiged)

Validates an integer is a positive value, above 0

ValueStatus-123Fail123Pass```
@validation type(int:unsigned)
```

### Cast

[](#cast)

**NOTE: Strings and Integers only**
Will attempt to "cast" an invalid value to the correct type, if possible

This will check if an object has a \_\_toString method, or will attempt to retreive return values of callables as the desired type.

**NOTE: Cast happens before any validation occurs. You can think of it more like a before middleware.**

#### Cast to int

[](#cast-to-int)

`type(int:cast)`

FromTo'123'123#### Cast to string

[](#cast-to-string)

`type(string:cast)`

FromTofalse'false'123'123'```
/*
 * @validation type(int:unsigned|cast)
 */
protected $id;

/*
 * @validation type(string:alnum|cast)
 */
protected $username;

// ...

class User {
    // ...
    public function __toString()
    {
        return 'user' . $this->user_id;
    }
}

$Class->username = new User; // "user001";
```

### Match

[](#match)

**String only**
Will match a vlaue based on the regex pattern provided

#### NOTES

[](#notes)

1. Do not wrap pattern in quotes
2. Do not use regex delimiters. The default delimiter used internally is /. If you need to change this you should create a custom validation rule using the addValidationRule() method

```
/**
 * Mobile
 *
 * Matches an Irish mobile number:
 * +3538123456789
 * 003538123456789
 * +353 81 234 5678
 * 00353 81 234 5678
 *
 * @validation match(^(?:0|(?:00|\+)353)\s*8\d{1}\s*\d{3}\s*\d{4})
 *
 * @var string
 */
protected $mobile;
```

### Replace

[](#replace)

Will attempt to replace a matched pattern with a replacement string.

Notes:

1. Do not qoute pattern or replace strings
2. Do not use regex delimiters. The default delimiter used internally is /. If you need to change this you should create a custom validation rule using the addValidationRule() method
3. Uses preg\_replace internally
4. You *CANNOT* pass an array as the replacement value. Only strings are allowed
5. You *CAN* use variable placeholders the same way as in preg\_replace e.g. To encrypt a credit card number you would use replace((\\d{4})\\s\*(\\d{4})\\s\*(\\d{4}), \*\*\*\* \*\*\*\* ${3}) // returns: \*\*\*\* \*\*\*\* 1234

```
/**
 * Credit Card Number
 *
 * Matches an a credit card number (e.g. 1234 1234 1234) and encrypts it (e.g **** **** 1234):
 *
 * @validation replace((\d{4})\s*(\d{4})\s*(\d{4}), **** **** ${3})
 *
 * @var string
 */
protected $credit_card_number;
```

ValidationErrors Class
----------------------

[](#validationerrors-class)

Each group of errors is wrapped in the ValidationErrors class. This is to allow for easier accessing of specific errors and their keys than simply looping over the array of validation errors

For example imaginae a property which has multiple validations, like a password with custom validation added.

1. Validates the length must be greater than 8 characters
2. Validates there is at lease one capital letter
3. Validates there is at least one number

Without the ValidationErrors class the validations would be grouped in an array like so:

```
[
    "password" => [
        [
            "class"     => "\Acme\ClassWithValidation",
            "property"  => "password",
            "value"     => "password",
            "error"     => "Password length must be greater than 8 characters"
        ],
        [
            "class"     => "\Acme\ClassWithValidation",
            "property"  => "password",
            "value"     => "password",
            "error"     => "Password must have at least one capital letter"
        ],
        [
            "class"     => "\Acme\ClassWithValidation",
            "property"  => "password",
            "value"     => "password",
            "error"     => "Password must have at least one number"
        ]
    ]
]
```

You would then have to do a loop, or check how many errors there are to even see if a loop is necessary, and you would still have to use indexes for all scenarios:

```
$errors['password'][0]['error'] // Password length must be greater than 8 characters

$errors['password'][3]['error'] // Ooops: There's no index 3, but a very easy and common mistake to make

// Or what if you just wanted all the password errors?
$password_errors = []
foreach ($errors['password'] as $error)
{
    $password_errors[] = $error['error'];
}
```

With the ValidationErrors class containing the array, we have numerous helpful methods available to travers the array and get exactly what data we want without loops or array keys

```
$password_errors = $Validate->getValidationErrors('password')->errors();
/*
$password_errors = [
    "Password length must be greater than 8 characters",
    "Password must have at least one capital letter",
    "Password must have at least one number"
]
*/
```

You can also easily navigate through the validation errors using the first(), next(), prev() and last() methods. These will expose the array items via properties (in place of the keys)

```
$Stub->getValidationErrors('password')->count(); // 3
$PasswordValidationErrors = $Stub->getValidationErrors('password');
$PasswordValidationErrors->first()->error; // Password length must be greater than 8 characters
$PasswordValidationErrors->next()->error; // Password must have at least one capital letter
$PasswordValidationErrors->prev()->error; // Password length must be greater than 8 characters
$PasswordValidationErrors->last()->error; // Password must have at least one number

// We also can get class, property and value
$PasswordValidationErrors->first()->class; // \Acme\Validate
$PasswordValidationErrors->first()->property; // 'password'
$PasswordValidationErrors->first()->value; // 'password'
```

**IMPORTANT**: We must store the ValidationError instance in a variable to use first, next, prev and last methods, since getValidationErrors($key) will return a new instance of ValidationErrors each time it is called

TODO
----

[](#todo)

1. Add type(string:traincase) Train-Case
2. Add type(string:uppercase)
3. Add type(string:lowercase)
4. Add type(string:flatcase)
5. Add type(string:upperflatcase)
6. Add type(string:hex) validator. Validates string is a hexadecimal value. ctype\_xdigit($value)
7. Add type(string:no\_whitespace) validator. Validates string has no whitespace (e.g. \\r\\n\\t). !ctype\_space($value) &amp;&amp; ctype\_print($value)
8. Add to(snakecase)
9. Add to(kebabcase)
10. Add to(constantcase) aka macrocase/uppersnakecase
11. Add to(macrocase) aka constantcase/uppersnakecase
12. Add to(uppersnakecase) aka constantcase/macrocase
13. Add to(cobolcase) aka upperkebabcase
14. Add to(upperkebabcase) aka cobolcase
15. Add to(camelcase)
16. Add to(camelcaps) aka pascalcase
17. Add to(pascalcase) aka camelcaps
18. Add to(uppercase)
19. Add to(lowercase)
20. Add to(flatcase)
21. Add to(upperflatcase)
22. Allow multiple validations to pass e.g. type(string:any(kebabcase, snakecase)). NOTE: validation "functions" (e.g. regex($pattern)) are not allowed inside any. Custom validators should instead be created using addValidationRule() and the name should be used inside any()
23. Allow any() to be used to allow multiple valid types e.g. type(any(string,int,null)). NOTE: No additioanl validators can be used in this case e.g. type(any(string,null):cast|kebabcase) since allowing multiple types could complex validation scenarios with potentially unexpected results
24. Add chaining/fluent interface

    ```
    @validation type(string:cast|kebabcase).to(snakecase) // Fails if not a string formatted as kebabcase. Otherwise converts kebabcase to snakecase (e.g. "i-am-a-kebab" to "i_am_a_kebab")
    // or
    @validation match(\s+).to(lowercase|snakecase) // Fails if not a sentence (no spaces found). Otherwise, converts sentences to lowercase snakecase (e.g. "I Am A Title" to "i_am_a_title")
    // or
    @validation match(\d{4}\s*\s{4}\s*\d{4}).replace((\d{4})\s*(\d{4})\s*(\d{4}), **** **** ${3}) // Fails if not a credit card number. Otherwise, encrypts it (e.g. **** **** 1234)
    ```

###  Health Score

19

—

LowBetter than 9% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity4

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1507d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/8245712?v=4)[Affinity4](/maintainers/Affinity4)[@affinity4](https://github.com/affinity4)

---

Top Contributors

[![lukewatts](https://avatars.githubusercontent.com/u/4622166?v=4)](https://github.com/lukewatts "lukewatts (16 commits)")

---

Tags

phpvalidationvalidatorvalidationtrait

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/affinity4-validate/health.svg)

```
[![Health](https://phpackages.com/badges/affinity4-validate/health.svg)](https://phpackages.com/packages/affinity4-validate)
```

###  Alternatives

[composer/semver

Version comparison library that offers utilities, version constraint parsing and validation.

3.3k522.3M983](/packages/composer-semver)[giggsey/libphonenumber-for-php

A library for parsing, formatting, storing and validating international phone numbers, a PHP Port of Google's libphonenumber.

5.0k159.6M523](/packages/giggsey-libphonenumber-for-php)[respect/validation

The most awesome validation engine ever created for PHP

6.0k39.9M413](/packages/respect-validation)[propaganistas/laravel-phone

Adds phone number functionality to Laravel based on Google's libphonenumber API.

3.0k39.7M145](/packages/propaganistas-laravel-phone)[craftcms/cms

Craft CMS

3.6k3.6M3.0k](/packages/craftcms-cms)[opis/json-schema

Json Schema Validator for PHP

65243.6M298](/packages/opis-json-schema)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
