Passed
Pull Request — master (#99)
by Sergei
02:37
created

WidgetFactory::initialize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 6
dl 0
loc 20
ccs 9
cts 9
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Widget;
6
7
use Psr\Container\ContainerInterface;
8
use Yiisoft\Definitions\ArrayDefinition;
9
use Yiisoft\Definitions\Exception\CircularReferenceException;
10
use Yiisoft\Definitions\Exception\InvalidConfigException;
11
use Yiisoft\Definitions\Exception\NotInstantiableException;
12
use Yiisoft\Definitions\Helpers\ArrayDefinitionHelper;
13
use Yiisoft\Definitions\Helpers\DefinitionValidator;
14
use Yiisoft\Factory\NotFoundException;
15
use Yiisoft\Factory\Factory;
16
17
/**
18
 * WidgetFactory creates an instance of the widget based on the specified configuration
19
 * {@see WidgetFactory::createWidget()}. Before creating a widget, you need to initialize
20
 * the WidgetFactory with {@see WidgetFactory::initialize()}.
21
 */
22
final class WidgetFactory
23
{
24
    private static bool $initialized = false;
25
    private static ?Factory $factory = null;
26
27
    /**
28
     * @psalm-var array<string, array<string, array>>
29
     */
30
    private static array $themes = [];
31
32
    private static ?string $defaultTheme = null;
33
34
    /**
35
     * @psalm-var array<string, string>
36
     */
37
    private static array $widgetDefaultThemes = [];
38
39
    /**
40
     * @codeCoverageIgnore
41
     */
42
    private function __construct()
43
    {
44
    }
45
46
    /**
47
     * @psalm-param array<string, mixed> $definitions
48
     * @psalm-param array<string, array<string, array>> $themes
49
     * @psalm-param array<string, string> $widgetDefaultThemes
50
     *
51
     * @throws InvalidConfigException
52
     *
53
     * @see Factory::__construct()
54
     */
55 42
    public static function initialize(
56
        ?ContainerInterface $container = null,
57
        array $definitions = [],
58
        bool $validate = true,
59
        array $themes = [],
60
        ?string $defaultTheme = null,
61
        array $widgetDefaultThemes = [],
62
    ): void {
63 42
        self::$factory = new Factory($container, $definitions, $validate);
64
65 42
        if ($validate) {
66 42
            self::assertThemesStructure($themes);
67 37
            self::assertWidgetDefaultThemesStructure($widgetDefaultThemes);
68
        }
69
70 35
        self::$themes = $themes;
71 35
        self::$defaultTheme = $defaultTheme;
72 35
        self::$widgetDefaultThemes = $widgetDefaultThemes;
73
74 35
        self::$initialized = true;
75
    }
76
77 1
    public static function setDefaultTheme(?string $theme): void
78
    {
79 1
        self::$defaultTheme = $theme;
80
    }
81
82
    /**
83
     * Creates a widget defined by config passed.
84
     *
85
     * @param array $config The parameters for creating a widget.
86
     * @param string|null $theme The widget theme.
87
     *
88
     * @throws CircularReferenceException
89
     * @throws InvalidConfigException
90
     * @throws NotFoundException
91
     * @throws NotInstantiableException
92
     *
93
     * @see Factory::create()
94
     *
95
     * @psalm-suppress MixedInferredReturnType
96
     * @psalm-suppress MixedReturnStatement
97
     */
98 33
    public static function createWidget(array $config, ?string $theme = null): Widget
99
    {
100 33
        if (self::$factory === null) {
101 2
            self::$factory = new Factory();
102
        }
103
104 33
        $className = $config[ArrayDefinition::CLASS_NAME] ?? null;
105 33
        if (is_string($className)) {
106 33
            $theme ??= self::$widgetDefaultThemes[$className] ?? self::$defaultTheme;
107 33
            if ($theme !== null && isset(self::$themes[$theme][$className])) {
108 9
                $config = ArrayDefinitionHelper::merge(
109 9
                    self::$themes[$theme][$className],
110 9
                    $config
111 9
                );
112
            }
113
        }
114
115
        try {
116 33
            return self::$factory->create($config);
0 ignored issues
show
Bug introduced by
The method create() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
            return self::$factory->/** @scrutinizer ignore-call */ create($config);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
117 2
        } catch (NotInstantiableException $exception) {
118 2
            if (self::$initialized) {
119 1
                throw $exception;
120
            }
121
122
            /**
123
             * @var string $className When `$className` is not string `$factory->create()`
124
             * throws not `NotInstantiableException` exception.
125
             */
126 1
            throw new NotInstantiableWithoutWidgetFactoryInitializationException($className, $exception);
127
        }
128
    }
129
130
    /**
131
     * @throws InvalidConfigException
132
     */
133 42
    private static function assertThemesStructure(array $themes): void
134
    {
135 42
        foreach ($themes as $theme => $definitions) {
136 18
            if (!is_string($theme)) {
137 1
                throw new InvalidConfigException(
138 1
                    sprintf('Theme name must be a string. Integer value "%s" given.', $theme)
139 1
                );
140
            }
141 17
            if (!is_array($definitions)) {
142 1
                throw new InvalidConfigException(
143 1
                    sprintf(
144 1
                        'Theme configuration must be an array. "%s" given for theme "%s".',
145 1
                        get_debug_type($definitions),
146 1
                        $theme,
147 1
                    )
148 1
                );
149
            }
150 16
            foreach ($definitions as $id => $definition) {
151 16
                if (!is_string($id)) {
152 1
                    throw new InvalidConfigException(
153 1
                        sprintf('Widget name must be a string. Integer value "%s" given in theme "%s".', $id, $theme)
154 1
                    );
155
                }
156 15
                if (!is_array($definition)) {
157 1
                    throw new InvalidConfigException(
158 1
                        sprintf(
159 1
                            'Widget themes supports array definitions only. "%s" given for "%s" definition in "%s" theme.',
160 1
                            get_debug_type($definition),
161 1
                            $id,
162 1
                            $theme,
163 1
                        )
164 1
                    );
165
                }
166 14
                DefinitionValidator::validateArrayDefinition($definition, $id);
167
            }
168
        }
169
    }
170
171
    /**
172
     * @throws InvalidConfigException
173
     */
174 37
    private static function assertWidgetDefaultThemesStructure(array $value): void
175
    {
176 37
        foreach ($value as $widget => $theme) {
177 5
            if (!is_string($widget)) {
178 1
                throw new InvalidConfigException(
179 1
                    sprintf('Widget class must be a string. Integer value "%s" given.', $widget)
180 1
                );
181
            }
182 4
            if (!is_string($theme)) {
183 1
                throw new InvalidConfigException(
184 1
                    sprintf(
185 1
                        'Theme name must be a string. "%s" given for widget "%s".',
186 1
                        get_debug_type($theme),
187 1
                        $widget,
188 1
                    )
189 1
                );
190
            }
191
        }
192
    }
193
}
194