This is a lightweight library to check if a given array conforms to a certain specification
Add this to composer.json
:
"require": {
"adeptoas/sniff-array": "^1.0.0"
}
Make sure to merge your require
-blocks!
__construct(array $spec, bool $throw = false)
Initialize an ArraySniffer with the given spec
. Error handling can be specified via throw
, else every sniff will simply return false
.
sniff(array $array): bool
Start checking array
for conformity in respect to the current ArraySniffer
instance's specification.
static arrayConformsTo(array $spec, array $data, bool $throw = false): bool
Check if array
conforms to spec
. This does the same as calling sniff
on an ArraySniffer
instance, but is mainly for one-time-only checks
This library provides several sniffers for primitive / SPL types in PHP. They are mainly needed for internal verification purposes, but you can use them as stand-alone verification sniffers if you want to.
static forType(string $type): SplSniffer
Return a subclass of SplSniffer
suitable for checking conformity to type
.
sniff(mixed $val, bool $isStrict = false): bool
Check if val
conforms to the specification implied by the current SplSniffer
instance's type. isStrict
specifies if certain edge cases should be deemed valid or not (see below for further explanation)
The basic type identifications delivered with this library are
- string
- int
- number
- bool
- mixed
- array
- object
Some common aliases are also implemented:
- boolean => bool
- integer => int
- numeric => number
- any => mixed
- empty => null
- class => object
Strict specifications are denoted by the type followed by an exclamation sign !
and narrow down the possible accepted values. In particular:
- string! doesn't accept the empty string
''
- int! doesn't accept
0
- number! doesn't accept
0
orNAN
- array! doesn't accept empty arrays
[]
- object! doesn't accept stdClass objects with 0 fields
new stdClass()
- mixed! doesn't accept arbitrary arrays, only pure primitives
array
as primitive specification simply denotes any arbitrary array of whatever form, while an explicitly specified nested array specification demands exact matches
Any array specification is itself arranged as an associative array.
[
'key' => 'string',
'otherKey' => 'int'
]
for example is a valid specification for
[
'key' => 'value',
'otherKey' => 42
]
Specifications can of course be nested.
[
'key' => [
'first' => 'number',
'second' => 'bool'
]
]
matches for example
[
'key' => [
'first' => INF,
'second' => false
]
]
but (obviously) not
[
'key' => 'someString'
]
Specification keys can be appended by some RegExp-like features to expand matching functionality, namely:
{a,b}
for minimum a and maximum b matches{,b}
for zero to b matches{a,}
for a to INF matches
+
for one or more matches (equal to{1,}
)*
for zero or more matches (equal to{0,}
)?
for an optional match (equal to{0,1}
)
Keys that allow for 0 matches by use of this RegExp typeset can either be explicitly set to null
or implicitly dropped.
These RegExp rules can of course also be applied to nested array specification structures.
[
'key' => 'string',
'optional?' => 'int',
'any*' => [
'foo' => 'bool',
'bar' => [
'one+' => 'number!',
'two{3,5}' => 'number!'
]
]
]
matches
[
'key' => 'value',
'optional' => 0,
'any' => [
[
'foo' => true,
'bar' => [
'one' => 123,
'two' => [456, 789, 321, 654]
]
], [
'foo' => false,
'bar' => [
'one' => [3.141592, 6.283185],
'two' => [1.414213, 2.718281, 1.618033]
]
]
]
]
as well as
[
'key' => 'value',
'any' => [
'foo' => true,
'bar' => [
'one' => 123,
'two' => [1.1, 2, 3.3, 4, 5.5]
]
]
]
Multiple types can be joined by using the bar |
sign. This does not infer with any of the other type rules.
[
'key' => 'string!|bool',
'otherKey' => 'int|array'
]
is a valid specification for
[
'key' => 'element',
'otherKey' => 123
]
as well as
[
'key' => true,
'otherKey' => 123
]
and
[
'key' => false,
'otherKey' => ['foo', 'bar']
]
Arrays that are sequential at root level can be checked for specification match by using the key __root
. This can be especially useful for bulk mode handling a given specification
[
'__root+' => [
'foo' => 'bool',
'bar?' => 'int'
]
]
matches
[
[
'foo' => 'true',
'bar' => 0
], [
'foo' => 'true'
], [
'foo' => 'false',
'bar' => 42
]
]
Some Sniffers, particularly StringSniffer
, ObjectSniffer
and MixedArraySniffer
, support specifying additional sniff data
via the usage of ::
This colon "operator" supports multiple repeated arguments. The effect of an argument is specified by the respective Sniffer class.
For StringSniffer
, the colon data can be used to specify a matching regular expression (RegExp).
The conformity to this RegExp will be checked in addition to the usual check by using preg_match
as specified in the PHP standard library
[
'foo*' => 'string::^[A-Z][a-z]*$'
]
matches
[
'foo' => ['Hello', 'World']
]
but not
[
'foo' => ['eHlo', 'World', '!']
]
Please note that the RegExp will automatically be wrapped in convenient PCRE bounds, (i.e. //) if not wrapped already
For ObjectSniffer
, the colon data can be used to specify instance class names. They can be fully namespaced,
but standard "short name" checks will also be performed.
[
'object' => 'class::MyClass'
]
matches
[
'object' => new MyClass()
]
but neither
[
'object' => new MyOtherClass()
]
nor
[
'object' => new stdClass()
]
Please also note that multiple classes can be specified using the same colon ::
operator in a repeated fashion
[
'object' => 'class::MyClass::MyOtherClass'
]
accounts for both
[
'object' => new MyClass()
]
and
[
'object' => new MyOtherClass()
]
For MixedArraySniffer
, the colon data can be used to specify whether an associative or a sequential array is desired.
[
'values' => 'array::sequential'
]
matches
[
'values' => [1, 2, 'one', 'two', true, false]
]
but not
[
'values' => [
'key' => 'value'
]
]
whereas the specification
[
'dict' => 'array::associative'
]
matches
[
'dict' => [
'one' => 1,
'two' => 2
]
]
but not the values
example from above. Common aliases seq
for sequential
and assoc
for associative
are installed for convenience,
so that
[
'members' => 'array::seq',
'relations' => 'array::assoc'
]
is a valid specification. More than one specified array type will be discarded and sniffed as false
or throw an exception, respectively.
Examples will be added to the Examples/
directory once I come up with meaningful examples that don't involve confidential data from my dayjob.