1.3.2. Class Definitions

Define the construction logic, including pre- and post-construction logic, for classes. There is also the ability to inherit (or not inherit) elements of the parent class definition.

All of the ClassDefinition methods are fluent, and can be called in any order.

1.3.2.1. Construction

These will be applied when the __construct() method is called.

1.3.2.1.1. Constructor Arguments

1.3.2.1.1.1. By Position or Name

Given this class ...

class Foo
{
    public function __construct(
        protected string $param0,
        protected string $param1
    ) {
    }
}

... you can set the constructor arguments all at once using arguments(), overriding all previous arguments:

$def->{Foo::CLASS}
    ->arguments([
        0 => 'arg0',
        1 => 'arg1',
    ]);

Alternatively, you can set them one at a time (or overriding an individual argument) using argument():

$def->{Foo::CLASS}
    ->argument(0, 'arg0')
    ->argument(1, 'arg1');

You can specify arguments by position or name in any combination you like. Given the above class, specifying the arguments by name would look like this:

$def->{Foo::CLASS}
    ->arguments([
        'param1' => 'arg1',
        'param0' => 'arg0',
    ]);

Among named and positional arguments referring to the same parameter, a later argument replaces an earlier one. For example:

$def->{Foo::CLASS}
    ->argument(0, 'positional'); // $param0 is now 'positional'
    ->argument('param0', 'named'); // $param0 is now 'named'
    ->argument(0, 'positional again'); // $param0 is now 'positional again'

1.3.2.1.1.2. By Typehint

You can also specify arguments by typehint. Given a class like this ...

class Bar
{
    public function __construct(
        protected stdClass $param0,
        protected string $param1
    } {
    }
}

... you might specify the the arguments like so:

$def->{Bar::CLASS}
    ->arguments([
        'param1' => 'arg1',
        stdClass::CLASS => new stdClass(),
    ]);

Specifying arguments by typehint is best combined with Lazy resolution, described elsewhere.

Arguments specified by name or position take precedence over arguments specified by typehint.

1.3.2.1.1.3. Variadic Arguments

If a class has a variadic constructor argument ...

class Baz
{
    protected array $items;

    public function __construct(
        string ...$items
    } {
        $this->items = $items;
    }
}

... it must be set using an array, like so:

$def->{Baz::CLASS}
    ->argument('items', ['a', 'b', 'c']);

1.3.2.1.1.4. Argument Examination and References

You can see if an argument value has been defined using hasArgument(), and retrieve the value of the argument using getArgument().

if (! $def->{Foo::CLASS}->hasArgument('bar')) {
    $def->{Foo::CLASS}->argument('bar', 'barval');
}

assert($def->{Foo::CLASS}->get('bar') === 'barval');

You can also get a reference to the argument value using refArgument(), so you can modify that value in place without having to get it and re-set it.

$def->{Foo::CLASS}->argument('bar', 'barval');

// ...

$bar =& $def->{Foo::CLASS}->refArgument('bar');
$bar .= 'suffixed';

assert($def->{Foo::CLASS}->get('bar') === 'barvalsuffixed');

1.3.2.2. Pre-Construction

These will be applied to the object instance before the __construct() method is called.

1.3.2.2.1. Class Overrides

If you like, you can specify an alternative class to use for instantiation instead of the using the definition ID as the class name. This means you can use a class that is different from the typehint ...

$def->{AbstractFoo::CLASS}
    ->class(Foo::CLASS)

... in which case you should be careful that the replacement class will actually work for the typehint.

Setting an alternative class() will cause the Container to use the definition for that other class. In the above example, that means any AbstractFoo pre- and post-construction logic will be ignored in favor of the Foo object definition, although the Foo definition will still inherit any defined AbstractFoo properties and arguments.

1.3.2.2.2. Factory Instantiation

Instead of relying on automatic instantiation via class(), properties (), and arguments(), you can set a callable factory on the class definition. This lets you create the object yourself, instead of letting the Container instantiate it for you.

The factory() takes precedence over the class(), properties(), and arguments() settings.

The callable factory must have the following signature ...

function (Container $container) : object

... although the return typehint may be more specific if you like.

For example:

$def->{Foo::CLASS}
    ->factory(function (Container $container) : Foo {
        return new Foo(); // or perform any other complex creation logic
    });

The factory() may be Lazy:

$def->{Foo::CLASS}
    ->factory(
        $def->newCall(FooFactory::CLASS, 'newInstance')
    );

It can also be used to return a class that is entirely different from the typehint ...

$def->{Foo::CLASS}
    ->factory(function (Container $container) : Bar {
        return new Bar();
    });

... in which case you must be careful that the replacement class will work for the typehint.

1.3.2.2.3. Property Injection

To set any property before construction, call the property() method with a property name and value:

$def->{Foo::CLASS}
    ->property('prop1', 'prop1value')
    ->property('prop2', 'prop2value')
    ->property('prop3', 'prop3value');

The property value may be Lazy resolvable.

Alternatively, you can set multiple properties all at once using properties(), overriding all previous properties:

$def->{Foo::CLASS}
    ->properties([
        'prop1' => 'prop1value',
        'prop2' => 'prop2value',
        'prop3' => 'prop3value',
    ]);

Warning:

Property injection is to be avoided as much as possible, and is provided only as a final fallback when no other approach is available. You should default to constructor injection instead of property injection.

1.3.2.3. Post-Construction

These post-construction methods will be applied to the object after initial construction (even if that construction was by factory()). You can specify them as many times as you like, and they will be applied in that order.

1.3.2.3.1. Setter Injection

Each call to method() indicates a method to call on the object after it is instantiated. The typical case is for setter injection, but it can be used for any post-construction initializer logic using class methods.

Given this class ...

class Foo
{
    protected $string;

    public function wrap(string $prefix, string $suffix) : void
    {
        $this->string = $prefix . $this->string . $suffix;
    }
}

... you might direct these method() calls to occur after instantiation:

$def->{Foo::CLASS}
    ->method('wrap', 'foo', 'bar')
    ->method('wrap', 'baz', 'dib');

Pass the method name as the first argument; the remaining arguments will be passed to that method call. These arguments may be Lazy.

1.3.2.3.2. General Modification

Sometimes method() calls will not be enough; you may need more complex modification logic. In these cases, add a modify() call to the definition. The typical use is for modifying the obejct itself, but it can be used for any other kind of initializer logic.

Pass a callable with this signature ...

function (Container $container, object $object) : void

... although the object typehint may be more specific if you like.

For example:

$def->{ComplexSetup::CLASS}
    ->modify(function (Container $container, ComplexSetup $object) : void {
        // complicated setup logic, then
        $object->finalize();
    });

1.3.2.3.3. Decorators

Whereas method() and modify() work on the object in place, the decorate() method allows you to return a completely different object if you like. To use it, pass a callable with following signature ...

function (Container $container, object $object) : object

... although the object typehints may be more specific if you like. Be sure to return the new object at the end of the callable.

For example:

$def->{Foo::CLASS}
    ->decorate(function (Container $container, Foo $foo) : DecoratedFoo {
        return new DecoratedFoo($foo);
    });

1.3.2.4. Inheritance

By default, the Definition for a class will "inherit" the defined arguments and properties of its parent classes. Inheritance of arguments and properties works all the way up to the highest parent.

For example, given these classes ...

class Foo
{
    public function __construct(string $arg0)
    {
    }
}

class Bar extends Foo
{
}

... and this Definition ...

$def->{Foo::CLASS}
    ->argument(0, 'value1')

... then the value of Bar $arg0 will be inherited from Foo $arg0 (in this case, it will be 'value1').

You can override any or all inherited arguments by using argument() or arguments() on the Definition for the child class.

Likewise, you can override any or all inherited properties by using property() or properties() on the Definition for the child class.

If you want to disable or interrupt inheritance, call inherit(null) on the child Definition:

$def->{Foo::CLASS}
    ->argument(0, 'value1')

$def->{Bar::CLASS}
    ->inherit(null);