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    protected ?LoggerInterface $logger = null;
31
32    /**
33     * Cache of created singleton objects.
34     *
35     * @var array<string, object>
36     */
37    protected array $singletons = [];
38
39    /**
40     * Return the spec map (keyed by class name).
41     *
42     * @return array<class-string, array<string, mixed>>
43     */
44    abstract protected function getSpecMap(): array;
45
46    /**
47     * Return the constructor arguments for the given spec.
48     *
49     * Each subclass can decide how to build arguments based on flags like "langDependent", "needsLogger", etc.
50     *
51     * @param array<string,mixed> $spec
52     * @param LoggerInterface $logger
53     *
54     * @return array<int,mixed>
55     */
56    abstract protected function getSpecArgs( array $spec, LoggerInterface $logger ): array;
57
58    /**
59     * Create and cache an object instance.
60     *
61     * @template T of object
62     *
63     * @param class-string<T> $class The class name.
64     *
65     * @return T An instance of the specified class.
66     *
67     * @since 1.45
68     */
69    protected function createFromSpec( string $class ) {
70        if ( isset( $this->singletons[$class] ) ) {
71            /** @var T $cached */
72            $cached = $this->singletons[$class];
73
74            return $cached;
75        }
76
77        $specs = $this->getSpecMap();
78        if ( !isset( $specs[$class] ) ) {
79            throw new UnexpectedValueException( "Class not registered: $class" );
80        }
81
82        $spec = $specs[$class];
83        $logger = $this->logger ?? new NullLogger();
84
85        $args = $this->getSpecArgs( $spec, $logger );
86        if ( !empty( $spec['args'] ) && is_array( $spec['args'] ) ) {
87            $args = array_merge( $args, $spec['args'] );
88        }
89
90        $objectSpec = [
91            'class' => $class,
92            'args' => $args,
93        ];
94
95        $instance = ObjectFactory::getObjectFromSpec( $objectSpec );
96
97        if ( !$instance instanceof $class ) {
98            throw new UnexpectedValueException(
99                "Expected instance of $class, got " . get_class( $instance )
100            );
101        }
102
103        $this->singletons[$class] = $instance;
104
105        return $instance;
106    }
107}