Skip to content

MF1DD/ObjectBuilder

Repository files navigation

ObjectBuilder

Automatic object creation with random values for PHP 8.2+.

Why?

Manually creating test objects is repetitive, error-prone, and time-consuming — especially for deeply nested object graphs with dozens of properties. Every constructor change forces you to update all test fixtures.

ObjectBuilder solves this: class name in, fully populated instance out. Recursive, type-safe, with semantically meaningful values.

Advantages

  • No boilerplateObjectBuilder::init(Foo::class)->build() instead of manual new Foo(...) with 10 parameters
  • Type-safe — reflection-based, supports all native and custom types including enums, interfaces, traits, abstract, and readonly classes
  • Deep object graphs — nested dependencies are resolved automatically and recursively (Person → Address → Street)
  • Semantic values — detects property names (email, timezone, firstname) and generates matching random values
  • Constraints — value ranges and formats via the with() API: ->with('age', ['min' => 18, 'max' => 65])
  • Overridable — set specific properties to fixed values while keeping the rest random
  • Extensible — custom type builders and stock class handlers can be registered, builders are swappable
  • No framework — pure PHP library, zero external runtime dependencies except nikic/php-parser

Compatibility

PHP Version Status
8.2 Fully supported, CI-tested
8.3 Fully supported, CI-tested
8.4 Fully supported, CI-tested
8.5 CI-tested (once available)
  • Runtime dependency: nikic/php-parser ^5.0
  • No framework (Symfony, Laravel, etc.) required
  • Package name: mf1dd/object-builder

Basic Usage

Simple classes are automatically populated with random values.

class Address
{
    public function __construct(
        private readonly mixed $street,
        private readonly string|int $zip,
        private readonly string $city,
        private readonly ?string $country,
        private readonly bool $mainResidence,
    ) {}
}

$result = ObjectBuilder::init(Address::class)->build();
// returns instance of Address with random values

You can override specific values. Unset values are generated randomly. Nested objects are resolved automatically.

class Person
{
    public function __construct(
        private readonly Name $name,
        private readonly int $age,
        private readonly Address $address,
    ) {}
}

$result = ObjectBuilder::init(Person::class, [
    'age' => 25,
    'name' => [
        'firstName' => 'Max',
        'lastName' => 'Mustermann'
    ],
    'address' => [
        'city' => 'Berlin',
    ]
])->build();
// $result->getAge() === 25
// $result->getName()->getFirstName() === 'Max'
// $result->getAddress()->getCity() === 'Berlin'
// $result->getAddress()->getZip() === random int|string

Enumerations

enum MyEnumeration: string
{
    case OK = 'OK';
    case WARNING = 'WARNING';
    case ERROR = 'ERROR';
}
$result = ObjectBuilder::init(MyEnumeration::class)->build();
// returns one of MyEnumeration cases

You can specify which values the enum should use.

$result = ObjectBuilder::init(MyEnumeration::class, ['OK'])->build();
// returns MyEnumeration::OK

$result = ObjectBuilder::init(MyEnumeration::class, ['WARNING', 'ERROR'])->build();
// returns one of MyEnumeration::WARNING|MyEnumeration::ERROR

Traits

For traits, an anonymous class is created that uses the trait. Parameters passed to the TraitBuilder are ignored.

$result = ObjectBuilder::init(MyTrait::class)->build();
// returns {class@anonymous/...}

Interfaces

The given interface is loaded and a class is dynamically generated from it. It returns the interface with the required methods and implements the interface.

The return value of methods is determined and a random value is assigned.

$result = ObjectBuilder::init(MyInterface::class)->build();
// returns Object of MyInterfaceClass
$value = $result->myMethod()
// returns random value matching its return type.

You can specify which values the methods should return. Pass an array with the method name as the key.

$options = [
    'getMyString' => 'testString'
];

$result = ObjectBuilder::init(MyInterface::class, $options)->build();
// returns Object of MyInterfaceClass
$value = $result->getMyString()
// returns 'testString'

If a method returns an object and you want to set values in it, that works too.

$options = [
    'getMyObject' => new SomeObject('Gustav', 27)
];

$result = ObjectBuilder::init(MyInterface::class, $options)->build();
// returns Object of MyInterfaceClass
$value = $result->getMyObject()
/**
 * returns class SomeObject {
 *      string $name => "Gustav",
 *      int $age => 27,
 * }
 */

It is also possible to pass individual parameters to the object.

$options = [
    'getMyObject' => ['name' => 'Bernhard']
];

$result = ObjectBuilder::init(MyInterface::class, $options)->build();
// returns Object of MyInterfaceClass
$value = $result->getMyObject()
/**
 * returns class SomeObject {
 *      string $name => "Bernhard",
 *      int $age => 27356453,
 * }
 */

Readonly Classes

Readonly classes (PHP 8.2+) are supported. Properties are automatically populated in the constructor — including nested ones.

readonly class ReadonlyPerson
{
    public function __construct(
        public string $name,
        public int $age,
        public ?ReadonlyAddress $address = null,
    ) {}
}

$result = ObjectBuilder::init(ReadonlyPerson::class, [
    'name' => 'Alice',
    'address' => ['street' => 'Main St', 'city' => 'Springfield'],
])->build();
// $result->name === 'Alice'
// $result->address->street === 'Main St'

Abstract Classes

Abstract classes are resolved via existing concrete subclasses. The builder automatically finds a suitable implementation.

abstract class AbstractVehicle
{
    public function __construct(public readonly string $brand) {}
    abstract public function getType(): string;
}

class Car extends AbstractVehicle
{
    public function getType(): string { return 'car'; }
}

$result = ObjectBuilder::init(AbstractVehicle::class)->build();
// returns instance of Car (or another concrete subclass)

Stock Classes (PHP Built-Ins)

Built-in PHP classes like DateInterval, DatePeriod, DateTime, DateTimeImmutable, ReflectionFunction, ArrayObject, and SplFileInfo are automatically supported.

$interval = ObjectBuilder::init(DateInterval::class)->build();
// returns DateInterval('P7D')

$date = ObjectBuilder::init(DateTimeImmutable::class)->build();
// returns random DateTimeImmutable instance

$ref = ObjectBuilder::init(ReflectionFunction::class)->build();
// returns new ReflectionFunction('strlen')

Custom handlers for additional stock classes can be registered:

use MF1DD\Application\Services\StockClassHandlerService;

StockClassHandlerService::register(new MyCustomHandler());

Value Constraints (with())

The with() method allows setting constraints for value ranges.

use MF1DD\UserInterface\ObjectBuilder;

$result = ObjectBuilder::init(Person::class)
    ->with('age', ['min' => 18, 'max' => 65])
    ->with('email', ['format' => 'email'])
    ->build();
// $result->getAge() is between 18 and 65
// $result->getEmail() is a random email address

Available constraints:

  • min / max — value range for int and float
  • min_length / max_length — string length
  • format — predefined formats: email, url, uuid

Semantic String Detection

The StringBuilder recognizes specific property names and generates matching values:

class User
{
    public function __construct(
        public readonly string $timezone,    // random IANA timezone
        public readonly string $countrycode, // random ISO country code
        public readonly string $email,       // random email address
        public readonly string $firstname,   // random first name
        public readonly string $lastname,    // random last name
        public readonly string $city,        // random city name
        public readonly string $street,      // random street name
        public readonly string $zip,         // random postal code
        public readonly string $phone,       // random phone number
        public readonly string $uuid,        // random UUID v4
        public readonly string $url,         // random URL
    ) {}
}

$result = ObjectBuilder::init(User::class)->build();
// All properties contain semantically meaningful random values

Custom Type Builders

Custom type builders can be registered for specific data types:

use MF1DD\Application\Services\DataTypeService;
use MF1DD\Domain\DataTypeInterface;
use MF1DD\Domain\Dto\Property;

class CustomBuilder implements DataTypeInterface
{
    public function build(): mixed
    {
        return 'custom value';
    }

    public function setProperty(Property $property): self
    {
        return $this;
    }

    public function buildAsString(): string
    {
        return "'custom value'";
    }
}

DataTypeService::register('custom_type', new CustomBuilder());

Custom Builder Override

The automatically selected builder can be overridden:

use MF1DD\Domain\ClassBuilderInterface;

$result = ObjectBuilder::init(MyClass::class)
    ->withBuilder($myCustomBuilder)
    ->build();

About

For automatically creating objects. Objects are created with random values

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors