The Object Creator
Wire's object Creator
class helps you create instances of classes for
which you don't have their constructor arguments at hand. It attempts to obtain
or gather all the required values by analyzing the types of the constructor
parameters of the class.
Basic usage of the object creator
use Conia\Wire\Wire;
class Value
{
public function get(): string
{
return 'Autowired Value';
}
}
class Model
{
public function __construct(protected Value $value)
{
}
public function value(): string
{
return $this->value->get();
}
}
$creator = Wire::creator(); // creates a `Creator` instance
$model = $creator->create(Model::class);
assert($model instanceof Model);
assert($model->value() === 'Autowired Value');
Behind the scenes, Wire will create both the Value
and the Model
objects.
Since a Value
object is required to initialize Model
it is created
beforehand and then passed to the constructor of Model
. Value
does not have
constructor parameters and can therefore be instantiated safely.
Technically, the creator uses reflection to determine the types of Model
's
constructor parameters. If a parameter type is a class the creator will try to
instantiate it by analyzing its constructor parameters as well. This process
works recursively, continuously resolving arguments until they are all
resolved, or until it encounters an unresolvable parameter.
PSR-11 Containers
When combined with a PSR-11 compatible dependency injection container, the
creator can be assisted in resolving parameters that would otherwise be
unresolvable.
See PSR-11 Containers.
Factory methods
If a class uses a static factory method to create an instance, you can pass the
name of the method to Creator::create
:
use Conia\Wire\Wire;
class Value
{
public function get(): string
{
return 'Autowired Value';
}
}
class Model
{
public function __construct(
public readonly Value $value,
public readonly string $str
) {
}
public static function createModel(
Value $value,
string $str = 'default string'
): self {
return new self($value, $str);
}
}
$creator = Wire::creator();
$model = $creator->create(Model::class, constructor: 'createModel');
assert($model instanceof Model);
assert($model->value->get() === 'Autowired Value');
assert($model->str === 'default string');
Parameters with default values
If a parameter has a default value and is otherwise unresolvable, the default value is used:
use Conia\Wire\Wire;
class Model
{
public function __construct(public string $str = 'default value')
{
}
}
$creator = Wire::creator();
$model = $creator->create(Model::class);
assert($model instanceof Model);
assert($model->str === 'default value');
Assist the creator with arguments that are already available
Predefined arguments
If you already have some or all of the arguments available to you, you can pass them to the create method by using an associative array. Predefined arguments are matched by name. When the name of the parameter to be resolved is the same as a key in the associative array, the value of that key is passed as the argument.
use Conia\Wire\Wire;
class Value
{
public function get(): string
{
return 'Autowired Value';
}
}
class Model
{
public function __construct(
protected string $str,
protected Value $value
) {
}
public function value(): string
{
return $this->value->get() . ' ' . $this->str;
}
}
$creator = Wire::creator();
$model = $creator->create(Model::class, predefinedArgs: ['str' => 'and str']);
assert($model instanceof Model);
assert($model->value() === 'Autowired Value and str');
Predefined types
These are very similar to predefined arguments. But instead of using a parameter's name to find a match in the associative array, it uses its type. Additionally, they are also used deeper down the object tree:
use Conia\Wire\Wire;
class DeepValue
{
public function __construct(public readonly string $value)
{
}
}
class Value
{
public function __construct(public readonly DeepValue $deep)
{
}
public function get(): string
{
return 'Autowired Value';
}
}
class AnotherValue
{
public function __construct(public readonly string $str)
{
}
}
class Model
{
public function __construct(
public readonly Value $value,
public readonly AnotherValue $another,
) {
}
}
$creator = Wire::creator();
$model = $creator->create(
Model::class,
predefinedTypes: [
AnotherValue::class => new AnotherValue('predefined value'),
DeepValue::class => new DeepValue('deep value'),
]
);
assert($model instanceof Model);
assert($model->value->get() === 'Autowired Value');
assert($model->value->deep->value === 'deep value');
assert($model->another->str === 'predefined value');
You can also combine predefined types with the Inject
attribute:
use Conia\Wire\Inject;
use Conia\Wire\Wire;
class Value
{
public function get(): string
{
return 'Autowired Value';
}
}
class AnotherValue
{
public function __construct(public readonly string $str)
{
}
}
class Model
{
public function __construct(
public readonly Value $value,
#[Inject('use-this-id')]
public readonly AnotherValue $another,
) {
}
}
$creator = Wire::creator();
$model = $creator->create(
Model::class,
predefinedTypes: ['use-this-id' => new AnotherValue('predefined value')]
);
assert($model instanceof Model);
assert($model->value->get() === 'Autowired Value');
assert($model->another->str === 'predefined value');
Creating the creator without the Wire
factory
Internally the Wire
factory initializes the creator like shown here:
use Conia\Wire\Creator;
// A PSR-11 container implementation like
// https://conia.dev/registry or https://php-di.org
use Conia\Wire\Tests\Fixtures\Container;
$container = new Container();
$creator = new Creator($container);
// Or without container
$creator = new Creator();