This guide covers array validation using the ArrayValidator class, which handles indexed arrays (lists) with optional item validation.
use Lemmon\Validator\Validator;
// Validate indexed arrays
$validator = Validator::isArray();
$result = $validator->validate([1, 2, 3, 'foo']);
// Result: [1, 2, 3, 'foo']
$result = $validator->validate(['a', 'b', 'c']);
// Result: ['a', 'b', 'c']
$result = $validator->validate([]);
// Result: []// Optional array (allows null)
$optional = Validator::isArray();
$result = $optional->validate(null); // null
// Required array
$required = Validator::isArray()->required();
$required->validate(null); // Throws ValidationException
// Array with default value
$withDefault = Validator::isArray()->default(['default']);
$result = $withDefault->validate(null); // ['default']The ArrayValidator only accepts indexed arrays (lists). Associative arrays will be rejected:
$validator = Validator::isArray();
// This will throw ValidationException
$validator->validate(['key' => 'value']);For associative arrays, use Validator::isAssociative() instead. See the Object & Schema Validation guide for complete documentation on associative array validation.
Use the items() method to validate each item in the array:
// Array of strings
$stringArray = Validator::isArray()->items(Validator::isString());
$result = $stringArray->validate(['foo', 'bar', 'baz']);
// Result: ['foo', 'bar', 'baz']
// Array of integers
$intArray = Validator::isArray()->items(Validator::isInt());
$result = $intArray->validate([1, 2, 3, 4]);
// Result: [1, 2, 3, 4]
// Array of emails
$emailArray = Validator::isArray()->items(
Validator::isString()->email()
);
$result = $emailArray->validate([
'user@example.com',
'admin@test.org'
]);// Array of user objects
$userArray = Validator::isArray()->items(
Validator::isAssociative([
'name' => Validator::isString()->required(),
'age' => Validator::isInt()->min(0)->max(150),
'email' => Validator::isString()->email()
])
);
$users = [
['name' => 'John', 'age' => 30, 'email' => 'john@example.com'],
['name' => 'Jane', 'age' => 25, 'email' => 'jane@example.com']
];
$result = $userArray->validate($users);// Array of integers with coercion
$intArray = Validator::isArray()->items(
Validator::isInt()->coerce()
);
$result = $intArray->validate(['1', '2', '3']);
// Result: [1, 2, 3] (strings converted to integers)The filterEmpty() method removes empty values (empty strings and null) from arrays and automatically reindexes them to maintain the indexed array structure:
$validator = Validator::isArray()->filterEmpty();
// Remove empty values and reindex
$result = $validator->validate(['apple', '', 'banana', null, 'cherry']);
// Result: ['apple', 'banana', 'cherry'] (properly reindexed: [0, 1, 2])
// Works with mixed data types
$mixedValidator = Validator::isArray()->filterEmpty();
$result = $mixedValidator->validate([1, '', 2, null, 3, 0, false]);
// Result: [1, 2, 3, 0, false] (only empty strings and null removed)// Filter empty values then validate remaining items
$emailValidator = Validator::isArray()
->filterEmpty() // Remove empty values first
->items(Validator::isString()->email()); // Then validate emails
$emails = ['john@example.com', '', 'jane@example.com', null];
$result = $emailValidator->validate($emails);
// Result: ['john@example.com', 'jane@example.com'] (filtered)// Form data with optional fields
$tagsValidator = Validator::isArray()
->filterEmpty() // Remove empty tag inputs
->items(Validator::isString()->minLength(2)); // Validate remaining tags
$formTags = ['php', '', 'javascript', null, 'css', ''];
$result = $tagsValidator->validate($formTags);
// Result: ['php', 'javascript', 'css']
// CSV-like data processing
$csvRowValidator = Validator::isArray()
->filterEmpty() // Remove empty CSV cells
->items(Validator::isString()->pipe('trim')); // Clean remaining values
$csvRow = ['John', '', 'Doe', null, '30', ''];
$result = $csvRowValidator->validate($csvRow);
// Result: ['John', 'Doe', '30']The ArrayValidator supports several coercion strategies when coerce() is enabled:
$validator = Validator::isArray()->coerce();
// String to single-item array
$result = $validator->validate('single');
// Result: ['single']
// Number to single-item array
$result = $validator->validate(123);
// Result: [123]
// Boolean to single-item array
$result = $validator->validate(true);
// Result: [true]
// Empty string to empty array
$result = $validator->validate('');
// Result: []$validator = Validator::isArray()->coerce();
// Associative array gets converted to indexed array (values only)
$result = $validator->validate(['key1' => 'value1', 'key2' => 'value2']);
// Result: ['value1', 'value2']
$result = $validator->validate(['a' => 1, 'b' => 2, 'c' => 3]);
// Result: [1, 2, 3]// Without required - null passes through
$validator = Validator::isArray()->coerce();
$result = $validator->validate(null); // null
// With default - null uses default
$validator = Validator::isArray()->coerce()->default(['default']);
$result = $validator->validate(null); // ['default']
// With required - null throws error
$validator = Validator::isArray()->coerce()->required();
$validator->validate(null); // Throws ValidationExceptionUse notEmpty() as a clearer alternative to minItems(1) when you just need at least one item.
By default it skips null; add required() if null should fail.
$validator = Validator::isArray()->notEmpty();
$validator->validate([1]); // Valid
$validator->validate([]); // Throws ValidationExceptionTo ignore empty values first, combine it with filterEmpty():
$validator = Validator::isArray()->filterEmpty()->notEmpty();
$validator->validate(['', null]); // Throws ValidationException (becomes [])Use minItems() and maxItems() to validate array length, similar to minLength() and maxLength() for strings:
// Minimum items constraint
$validator = Validator::isArray()->minItems(3);
$result = $validator->validate([1, 2, 3]); // Valid
$result = $validator->validate([1, 2, 3, 4]); // Valid
$validator->validate([1, 2]); // Throws ValidationException: "Value must contain at least 3 items"
// Maximum items constraint
$validator = Validator::isArray()->maxItems(5);
$result = $validator->validate([1, 2, 3]); // Valid
$result = $validator->validate([1, 2, 3, 4, 5]); // Valid
$validator->validate([1, 2, 3, 4, 5, 6]); // Throws ValidationException: "Value must contain at most 5 items"
// Combined constraints
$validator = Validator::isArray()->minItems(2)->maxItems(4);
$result = $validator->validate([1, 2]); // Valid
$result = $validator->validate([1, 2, 3, 4]); // Valid
$validator->validate([1]); // Throws ValidationException: "Value must contain at least 2 items"
$validator->validate([1, 2, 3, 4, 5]); // Throws ValidationException: "Value must contain at most 4 items"$validator = Validator::isArray()
->minItems(3, 'Array must have at least 3 elements')
->maxItems(10, 'Array cannot exceed 10 elements');The contains() method validates that an array contains a specific value or an item matching a validator:
// Contains specific value (strict comparison)
$validator = Validator::isArray()->contains('banana');
$result = $validator->validate(['apple', 'banana', 'cherry']); // Valid
$validator->validate(['apple', 'cherry']); // Throws ValidationException
// Contains value with strict type checking
$validator = Validator::isArray()->contains(0);
$result = $validator->validate([0, 1, 2]); // Valid (finds integer 0)
$validator->validate(['0', 1, 2]); // Throws ValidationException (string '0' !== integer 0)
// Contains item matching validator
$validator = Validator::isArray()->contains(Validator::isString()->email());
$result = $validator->validate(['not-email', 'test@example.com', 'also-not-email']); // Valid
$validator->validate(['not-email', 'also-not-email']); // Throws ValidationException
// Contains item matching complex validator
$validator = Validator::isArray()->contains(Validator::isInt()->positive());
$result = $validator->validate([-1, 0, 5, -2]); // Valid (contains positive integer 5)
$validator->validate([-1, 0, -2]); // Throws ValidationException (no positive integers)// Validate all items are strings AND array contains an email
$validator = Validator::isArray()
->items(Validator::isString())
->contains(Validator::isString()->email());
$result = $validator->validate(['hello', 'test@example.com', 'world']); // Valid
$validator->validate(['hello', 'world']); // Throws ValidationException (no email found)
$validator->validate(['hello', 123, 'test@example.com']); // Throws ValidationException (item validation fails first)// For specific array validation, use custom validation
$validator = Validator::isArray()->satisfies(
fn($value) => in_array($value, [[1, 2], [3, 4]], true),
'Array must be exactly [1, 2] or [3, 4]'
);
$result = $validator->validate([1, 2]); // Valid
$validator->validate([1, 2, 3]); // Throws ValidationException
// Note: in() is not available on ArrayValidator as it doesn't make semantic sense for complex types// Nullify empty arrays
$validator = Validator::isArray()->nullifyEmpty();
$result = $validator->validate([]); // null
$result = $validator->validate([1, 2]); // [1, 2]
// Default for empty arrays
$validator = Validator::isArray()->default(['fallback']);
$result = $validator->validate(null); // ['fallback']// Array that can contain strings or numbers
$mixedArray = Validator::isArray()->items(
Validator::anyOf([
Validator::isString(),
Validator::isInt(),
Validator::isFloat()
])
);
$result = $mixedArray->validate(['hello', 42, 3.14]);
// Result: ['hello', 42, 3.14]// Array of arrays
$nestedArray = Validator::isArray()->items(
Validator::isArray()->items(Validator::isString())
);
$result = $nestedArray->validate([
['a', 'b'],
['c', 'd'],
['e', 'f']
]);use Lemmon\Validator\ValidationException;
$validator = Validator::isArray();
try {
$validator->validate('not an array');
} catch (ValidationException $e) {
echo $e->getMessage(); // "Validation failed"
print_r($e->getErrors()); // ['Value must be an array']
}$validator = Validator::isArray()->items(Validator::isString());
try {
$validator->validate(['valid', 123, 'also valid']);
} catch (ValidationException $e) {
// Errors preserve array indices to identify which item failed
print_r($e->getErrors());
// Output:
// [
// '1' => ['Value must be a string']
// ]
// Flattened errors show full path with index
$flattened = $e->getFlattenedErrors();
// [
// ['path' => '1', 'message' => 'Value must be a string']
// ]
}$validator = Validator::isArray()->items(Validator::isInt()->coerce());
[$valid, $result, $errors] = $validator->tryValidate(['1', 'invalid', '3']);
if (!$valid) {
echo "Validation failed:\n";
print_r($errors);
// Output:
// [
// '1' => ['Value must be an integer']
// ]
// Flattened errors preserve array indices
$flattened = ValidationException::flattenErrors($errors);
// [
// ['path' => '1', 'message' => 'Value must be an integer']
// ]
} else {
echo "Valid array:\n";
print_r($result);
}For arrays with complex item validators (like associative arrays), errors preserve the full path including array indices:
$schema = Validator::isAssociative([
'users' => Validator::isArray()->items(Validator::isAssociative([
'name' => Validator::isString()->required(),
'email' => Validator::isString()->email()->required(),
])),
]);
$input = [
'users' => [
['name' => 'John'], // Missing email
['name' => 'Jane', 'email' => 'invalid-email'], // Invalid email
],
];
try {
$schema->validate($input);
} catch (ValidationException $e) {
$flattened = $e->getFlattenedErrors();
// [
// ['path' => 'users.0.email', 'message' => 'Value is required'],
// ['path' => 'users.1.email', 'message' => 'Value must be a valid email address']
// ]
}Use uniqueField() to validate that a nested field is unique across all array items. All members of a duplicate group receive errors with field-level paths automatically. Items where the field is null or missing are skipped.
$schema = Validator::isAssociative([
'symlinks' => Validator::isArray()
->items(Validator::isAssociative([
'source' => Validator::isString()->default('.'),
'destination' => Validator::isString()->required(),
]))
->uniqueField('destination')
->required(),
]);
$input = [
'symlinks' => [
['source' => 'path1', 'destination' => '/same/path'],
['source' => 'path2', 'destination' => '/unique/path'],
['source' => 'path3', 'destination' => '/same/path'], // Duplicate
],
];
try {
$schema->validate($input);
} catch (ValidationException $e) {
$flattened = $e->getFlattenedErrors();
// [
// ['path' => 'symlinks.0.destination', 'message' => "Value '/same/path' is not unique (also at index 2)"],
// ['path' => 'symlinks.2.destination', 'message' => "Value '/same/path' is not unique (also at index 0)"],
// ]
}Custom message:
->uniqueField('destination', 'Destination must be unique across all symlinks')For validations beyond uniqueness (ordering, dependencies, custom logic), use satisfies() on the array validator. Structure errors as [arrayIndex => [fieldName => [errorMessage]]] to get field-level paths.
If you don't need field-level errors, you can use a simpler approach that returns a single error at the array level:
->satisfies(
function ($items) {
$destinations = array_column($items, 'destination');
return count($destinations) === count(array_unique($destinations));
},
'All destination values must be unique'
)This will produce an error at symlinks rather than symlinks.2.destination.
// Validate array of uploaded files
$fileValidator = Validator::isArray()->items(
Validator::isAssociative([
'name' => Validator::isString()->required(),
'size' => Validator::isInt()->min(1)->max(10485760), // Max 10MB
'type' => Validator::isString()->in([
'image/jpeg', 'image/png', 'image/gif'
])
])
);// Array of unique tags
$tagValidator = Validator::isArray()->items(
Validator::isString()
->pattern('/^[a-zA-Z0-9-_]+$/', 'Tags can only contain letters, numbers, hyphens, and underscores')
->minLength(2)
->maxLength(50)
);
$result = $tagValidator->validate(['php', 'validation', 'array-handling']);// Validate configuration arrays
$configValidator = Validator::isArray()->items(
Validator::anyOf([
Validator::isString(),
Validator::isInt(),
Validator::isBool(),
Validator::isArray() // Allow nested arrays
])
);
$config = ['debug' => true, 'timeout' => 30, 'hosts' => ['localhost', '127.0.0.1']];
$result = $configValidator->validate($config);- Learn about Object & Schema Validation for structured data
- Explore Custom Validation for business logic and complex validation scenarios
- Check out Error Handling for advanced error management
- See Form Validation Examples for practical examples