Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
18 / 22
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecBasedFactoryTrait
81.82% covered (warning)
81.82%
18 / 22
0.00% covered (danger)
0.00%
0 / 1
6.22
0.00% covered (danger)
0.00%
0 / 1
 getSpecMap
n/a
0 / 0
n/a
0 / 0
0
 getSpecArgs
n/a
0 / 0
n/a
0 / 0
0
 createFromSpec
81.82% covered (warning)
81.82%
18 / 22
0.00% covered (danger)
0.00%
0 / 1
6.22
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace Wikimedia\Leximorph\Traits;
8
9use Psr\Log\LoggerInterface;
10use Psr\Log\NullLogger;
11use UnexpectedValueException;
12use Wikimedia\ObjectFactory\ObjectFactory;
13
14/**
15 * SpecBasedFactoryTrait
16 *
17 * Trait for instantiating Leximorph objects from a spec map.
18 *
19 * @since     1.45
20 * @author    Doğu Abaris (abaris@null.net)
21 * @license   https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
22 */
23trait SpecBasedFactoryTrait {
24
25    /**
26     * The default language code.
27     */
28    protected string $langCode;
29
30    /**
31     * The logger instance.
32     */
33    protected ?LoggerInterface $logger = null;
34
35    /**
36     * Cache of created singleton objects.
37     *
38     * @var array<string, object>
39     */
40    protected array $singletons = [];
41
42    /**
43     * Return the spec map (keyed by class name).
44     *
45     * @return array<class-string, array<string, mixed>>
46     */
47    abstract protected function getSpecMap(): array;
48
49    /**
50     * Return the constructor arguments for the given spec.
51     *
52     * Each subclass can decide how to build arguments based on flags like "langDependent", "needsLogger", etc.
53     *
54     * @param array<string,mixed> $spec
55     * @param LoggerInterface $logger
56     *
57     * @return array<int,mixed>
58     */
59    abstract protected function getSpecArgs( array $spec, LoggerInterface $logger ): array;
60
61    /**
62     * Create and cache an object instance.
63     *
64     * @template T of object
65     *
66     * @param class-string<T> $class The class name.
67     *
68     * @return T An instance of the specified class.
69     *
70     * @since 1.45
71     */
72    protected function createFromSpec( string $class ) {
73        if ( isset( $this->singletons[$class] ) ) {
74            /** @var T $cached */
75            $cached = $this->singletons[$class];
76
77            return $cached;
78        }
79
80        $specs = $this->getSpecMap();
81        if ( !isset( $specs[$class] ) ) {
82            throw new UnexpectedValueException( "Class not registered: $class" );
83        }
84
85        $spec = $specs[$class];
86        $logger = $this->logger ?? new NullLogger();
87
88        $args = $this->getSpecArgs( $spec, $logger );
89        if ( !empty( $spec['args'] ) && is_array( $spec['args'] ) ) {
90            $args = array_merge( $args, $spec['args'] );
91        }
92
93        $objectSpec = [
94            'class' => $class,
95            'args' => $args,
96        ];
97
98        /** @phan-suppress-next-line PhanTypeInvalidCallableArrayKey */
99        $instance = ObjectFactory::getObjectFromSpec( $objectSpec );
100
101        if ( !$instance instanceof $class ) {
102            throw new UnexpectedValueException(
103                "Expected instance of $class, got " . get_class( $instance )
104            );
105        }
106
107        $this->singletons[$class] = $instance;
108
109        return $instance;
110    }
111}