Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
MethodFactory
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __invoke
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of php-fast-forward/container.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2025-2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/container
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Container\Factory;
20
21use ReflectionMethod;
22use Throwable;
23use ReflectionException;
24use FastForward\Container\Exception\RuntimeException;
25use Psr\Container\ContainerInterface;
26
27/**
28 * A factory that invokes a specified method on a class using reflection and the PSR-11 container.
29 *
30 * This factory MUST be used when service creation requires calling a non-constructor method,
31 * and supports both static and instance methods.
32 *
33 * If the method is not public, a RuntimeException SHALL be thrown.
34 *
35 * Arguments MAY be resolved from the container if passed as service identifiers.
36 */
37  class MethodFactory implements FactoryInterface
38{
39    /**
40     * @var array<int, mixed> arguments to be passed to the method during invocation
41     */
42    private array $arguments;
43
44    /**
45     * Constructs the MethodFactory.
46     *
47     * @param string $class the class name or container service ID on which the method is called
48     * @param string $method the name of the method to invoke
49     * @param mixed ...$arguments Optional arguments to pass to the method.
50     */
51    public function __construct(
52        private string $class,
53        private string $method,
54        mixed ...$arguments,
55    ) {
56        $this->arguments = $arguments;
57    }
58
59    /**
60     * Resolves the class and invokes the configured method with arguments.
61     *
62     * Arguments MAY be resolved from the container if passed as string identifiers and found.
63     * Static methods are invoked without instantiating the class. If the method is not public,
64     * this method MUST throw a RuntimeException.
65     *
66     * @param ContainerInterface $container The container used to resolve the class and arguments
67     *
68     * @return mixed The result of invoking the method
69     *
70     * @throws ReflectionException If the method does not exist
71     * @throws RuntimeException If the method is not public
72     */
73    public function __invoke(ContainerInterface $container): mixed
74    {
75        $arguments = array_map(
76            static fn($argument) => \is_string($argument) && $container->has($argument)
77                ? $container->get($argument)
78                : $argument,
79            $this->arguments
80        );
81
82        $reflectionMethod = new ReflectionMethod($this->class, $this->method);
83
84        if (! $reflectionMethod->isPublic()) {
85            throw RuntimeException::forNonPublicMethod($this->class, $this->method);
86        }
87
88        if ($reflectionMethod->isStatic()) {
89            return $reflectionMethod->invokeArgs(null, $arguments);
90        }
91
92        try {
93            $object = $container->get($this->class);
94        } catch (Throwable) {
95            $object = new ($this->class)();
96        }
97
98        return $reflectionMethod->invokeArgs($object, $arguments);
99    }
100}