Automatic object creation with random values for PHP 8.2+.
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.
- No boilerplate —
ObjectBuilder::init(Foo::class)->build()instead of manualnew 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
| 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
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 valuesYou 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|stringenum MyEnumeration: string
{
case OK = 'OK';
case WARNING = 'WARNING';
case ERROR = 'ERROR';
}$result = ObjectBuilder::init(MyEnumeration::class)->build();
// returns one of MyEnumeration casesYou 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::ERRORFor 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/...}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 (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 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)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());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 addressAvailable constraints:
min/max— value range for int and floatmin_length/max_length— string lengthformat— predefined formats:email,url,uuid
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 valuesCustom 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());The automatically selected builder can be overridden:
use MF1DD\Domain\ClassBuilderInterface;
$result = ObjectBuilder::init(MyClass::class)
->withBuilder($myCustomBuilder)
->build();