Skip to content

Sets

So far you've seen hardcoded Sets via Set::of() and integers one via Set::integers(). But there are much more.

Tip

You can find more on Packagist.

Common methods

Map

For all Set objects you can transform the generated values with a function of your own.

Let's say the code you want to prove needs a Password object. You can wrap strings in your object like this:

use Innmind\BlackBox\Set;

$set = Set::strings()
    ->map(static fn(string $string) => new Password($string));

Now if you use $set in a proof it will generate instances of Password with a random string inside it.

Flat map

This method allows to generate a Set configured from a randomly generated value from another Set.

In most cases you'll want to use the Set::composite(). This method will be useful for advanced cases.

One example is the ability to randomly generate Sets with associated data. Let's say you want to randomly generate strings defining types (int, string, etc...) with an associated Set to generate values of this type in order to build an imaginary Type class:

use Innmind\BlackBox\{
    Set,
    Set\Seed,
};

$pairs = Set::of(
    ['int', Set::integers()],
    ['string', Set::strings()],
    ['float', Set::realNumbers()],
    ['bool', Set::of(true, false)],
    // etc...
);

$types = $pairs->flatMap(static function(Seed $pair) {
    [$type, $values] = $pair->unwrap();

    return $values->map(static fn($value) => new Type($type, $value));
});

The $types Set could generate the values new Type('bool', true), new Type('bool', false), new Type('int', 42), etc...

This example is simple enough and could be expressed without the use of flatMap. Like this:

use Innmind\BlackBox\Set;

$types = Set::either(
    Set::integers()->map(static fn($value) => ['int', $value]),
    Set::strings()->map(static fn($value) => ['string', $value]),
    Set::realNumbers()->map(static fn($value) => ['float', $value]),
    Set::of(true, false)->map(static fn($value) => ['bool', $value]),
)
    ->map(static function($pair) {
        [$type, $value] = $pair;

        return new Type($type, $value);
    });

But say that now you want multiple values instead of a single one. With flatMap it's straightforward, unlike with the other approach.

use Innmind\BlackBox\{
    Set,
    Set\Seed,
};

$pairs = Set::of(
    ['int', Set::integers()],
    ['string', Set::strings()],
    ['float', Set::realNumbers()],
    ['bool', Set::of(true, false)],
    // etc...
);

$types = $pairs->flatMap(static function(Seed $pair) {
    [$type, $values] = $pair->unwrap();

    return Set::sequence($values)->map(
        static fn($value) => new Type($type, $value),
    );
});
use Innmind\BlackBox\Set;

$types = Set::either(
    Set::sequence(Set::integers())->map(static fn($value) => ['int', $value]),
    Set::sequence(Set::strings())->map(static fn($value) => ['string', $value]),
    Set::sequence(Set::realNumbers())->map(static fn($value) => ['float', $value]),
    Set::sequence(Set::of(true, false))->map(static fn($value) => ['bool', $value]),
)
    ->map(static function($pair) {
        [$type, $value] = $pair;

        return new Type($type, $value);
    });

As you can see with flatMap you can locally define what you want without having to change the Set you rely on. Unlike the alternative where you need to change the initial Set and thus impacting any other Set that could depend on it.

Shrinking

In the examples above the value passed as argument to the flatMap callable can't be shrunk. This is because we call ->unwrap() to acces to the real value behind the Seed object.

When the Seed value is unwrapped, BlackBox can no longer track how it's used and thus can no longer shrink it.

In the examples above the seeded value can't be shrunk anyway because it comes from user provided values via Set::of().

A simple example to demonstrate the use case is prefixing a string with an int:

use Innmind\BlackBox\{
    Set,
    Set\Seed,
};

$set = Set::integers()->flatMap(
    static fn(Seed $int) => Set::strings()->map(
        static fn(string $string) => $int->map(
            static fn(int $int) => $int.$string,
        ),
    ),
);

This way BlackBox knows every transformations of a seeded value and re-apply then after shrinking it.

And you can also compose multiple Seeds via the Seed::flatMap() method.

Randomness

By default the Set returned by flatMap will produce values with the same seed (the callable argument).

If you want a more wide range of seeded values you should call the ->randomize() method after ->flatMap().

Filter

To reuse the password example from above. Say that your password needs to contain the character $. You can do:

use Innmind\BlackBox\Set;

$set = Set::strings()
    ->filter(static fn(string $string) => \str_contains($string, '$'))
    ->map(static fn(string $string) => new Password($string));
Warning

This is an example. You should not enforce your passwords to have a specific value in it. The strength is based on length. (US and French recommendations)

Nullable

If you need a Set to also generate a null value you can simply do:

$set = $set->nullable();

Primitives

Strings

use Innmind\BlackBox\Set;

Set::strings()->chars();

This describes any chars that can be returned by the \chr() function.

use Innmind\BlackBox\Set;

Set::strings()->chars()->lowercaseLetter();

This describes the range a..z.

use Innmind\BlackBox\Set;

Set::strings()->chars()->uppercaseLetter();

This describes the range A..Z.

use Innmind\BlackBox\Set;

Set::strings()->chars()->number();

This describes the range 0..9.

use Innmind\BlackBox\Set;

Set::strings()->chars()->ascii();

This describes any character that you can typically find on your keyboard.

use Innmind\BlackBox\Set;

Set::strings()->chars()->alphanumerical();

This describes any character from ->lowercaseLetter(), ->uppercaseLetter() or ->number().

use Innmind\BlackBox\Set;

Set::strings();

This describes any string of a length between 0 and 128 containing any character from Set::strings()->chars().

use Innmind\BlackBox\Set;

Set::strings()->between($min, $max);

This is the same as Set::strings() but you specify the length range.

use Innmind\BlackBox\Set;

Set::strings()->atMost($max);
use Innmind\BlackBox\Set;

Set::strings()->atLeast($min);
use Innmind\BlackBox\Set;

Set::strings()->madeOf($set);

This describes any string made of the characters you specify (ie Set::strings()->madeOf(Set::strings()->chars()->alphanumerical()))

You can specify the length range via Set::strings()->madeOf(Set)->between(min, max).

use Innmind\BlackBox\Set;

Set::strings()->unicode()->char();

This describes any single unicode character.

use Innmind\BlackBox\Set;

Set::strings()->unicode();

This is the same as Se::strings()->madeOf(Set::strings()->unicode()->char()).

use Innmind\BlackBox\Set;

Set::strings()->unicode()->controlCharater();
Set::strings()->unicode()->basicLatin();
Set::strings()->unicode()->spacingModifierLetters();
// etc...

This set provides all the unicode blocks as dedicated methods.

use Innmind\BlackBox\Set;

Set::strings()->unsafe();

This describes any string that could break your code. You can use this to test the robustness of your code.

Integers

use Innmind\BlackBox\Set;

Set::integers();

This describes any integer between \PHP_INT_MIN and \PHP_INT_MAX.

use Innmind\BlackBox\Set;

Set::integers()->between($min, $max);

This describes any integer between the bounds you specify.

use Innmind\BlackBox\Set;

Set::integers()->above($min);
use Innmind\BlackBox\Set;

Set::integers()->below($max);
use Innmind\BlackBox\Set;

Set::integers()->exceptZero();
use Innmind\BlackBox\Set;

Set::integers()->naturalNumbers();

This is the same as Set::integers()->above(0).

use Innmind\BlackBox\Set;

Set::integers()->naturalNumbersExceptZero();

This is the same as Set::integers()->above(1).

The bounds are included in the values that can be generated

Real numbers

use Innmind\BlackBox\Set;

Set::realNumbers();

This describes any float between \PHP_INT_MIN and \PHP_INT_MAX.

use Innmind\BlackBox\Set;

Set::realNumbers()->between($min, $max);

This describes any float between the bounds you specify.

use Innmind\BlackBox\Set;

Set::realNumbers()->above($min);
use Innmind\BlackBox\Set;

Set::realNumbers()->below($max);

The bounds are included in the values that can be generated

Type

use Innmind\BlackBox\Set;

Set::type();

This describes any type that is supported by PHP. This is useful to prove a code doesn't depend on the type of its arguments.

User defined values

Elements

use Innmind\BlackBox\Set;

Set::of(...$values);

This describes all the values that you put in (ie Set::of(true, false) to describe booleans).

From a generator

use Innmind\BlackBox\Set;

Set::generator(static function() {
    yield from $values;
});

This describes values that you will provide via a Generator.

Higher order Sets

Composite

This Set allows to aggregate multiple values to a new one. Let's say you have a User class, you could desribe it via:

Set::compose(
    static fn(string $firstname, string $lastname) => new User(
        $firstname,
        $lastname,
    ),
    Set::strings()->atLeast(1),
    Set::strings()->atLeast(1),
);

Any additionnal Set provided will give access to a new argument to the callable.

Either

You can think of this Set as an OR.

use Innmind\BlackBox\Set;

Set::either(Set::integers(), Set::strings());

This describes any integer or string.

Sequence

Set::sequence(Set) describes a list (an array of consecutive values) of values of the given Set type.

use Innmind\BlackBox\Set;

Set::sequence(Set::integers());

This describes any list of integers.

By default the list contains between 0 and 100 elements, you can change this via Set::sequence(Set)->between(min, max), ->atLeast() or ->atMost().

The bounds are included.

Tuple

This is a special case of Composite. Both examples does the same thing.

Set::tuple(
    Set::integers(),
    Set::integers(),
)
Set::compose(
    static fn(int $a, int $b) => [$a, $b],
    Set::integers(),
    Set::integers(),
)

Call

Set::call(static function() {
    return $someValue;
})

This set is useful when building the Model to tests via properties. If BlackBox shrinks properties it will call the provided callable at each shrinking step. This allows to get rid of any state inside your Model between each run.

Specific types

Email

use Innmind\BlackBox\Set;

Set::email();

This describes any valid email string.

Uuid

use Innmind\BlackBox\Set;

Set::uuid();
This describes any valid UUID.