PackageManager   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
dl 0
loc 276
rs 9.44
c 4
b 0
f 0
eloc 75
wmc 37

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A updateContainer() 0 31 6
A registerExceptionHandlers() 0 5 3
B registerCallables() 0 15 7
A registerItemsFromConfig() 0 24 1
A getPackage() 0 4 2
A registerFromConfig() 0 23 6
A registerPackage() 0 18 2
A getPackageLibConfig() 0 18 3
A registerCallablesFromConfig() 0 27 6
1
<?php
2
3
/**
4
 * PackageManager.php - Jaxon package manager
5
 *
6
 * Register Jaxon plugins, packages and callables from a config file.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2022 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Plugin\Manager;
16
17
use Jaxon\Jaxon;
18
use Jaxon\App\Config\ConfigManager;
19
use Jaxon\App\I18n\Translator;
20
use Jaxon\App\View\ViewRenderer;
21
use Jaxon\Config\Config;
22
use Jaxon\Di\Container;
23
use Jaxon\Exception\SetupException;
24
use Jaxon\Plugin\AbstractPackage;
25
use Jaxon\Plugin\Request\CallableClass\ComponentRegistry;
26
use Jaxon\Request\Handler\CallbackManager;
27
28
use function is_array;
29
use function is_integer;
30
use function is_string;
31
use function is_subclass_of;
32
use function trim;
33
34
class PackageManager
35
{
36
    /**
37
     * The constructor
38
     *
39
     * @param Container $di
40
     * @param Translator $xTranslator
41
     * @param PluginManager $xPluginManager
42
     * @param ConfigManager $xConfigManager
43
     * @param ViewRenderer $xViewRenderer
44
     * @param CallbackManager $xCallbackManager
45
     * @param ComponentRegistry $xRegistry
46
     */
47
    public function __construct(private Container $di, private Translator $xTranslator,
48
        private PluginManager $xPluginManager, private ConfigManager $xConfigManager,
49
        private ViewRenderer $xViewRenderer, private CallbackManager $xCallbackManager,
50
        private ComponentRegistry $xRegistry)
51
    {}
52
53
    /**
54
     * Save items in the DI container
55
     *
56
     * @param Config $xConfig
57
     *
58
     * @return void
59
     */
60
    public function updateContainer(Config $xConfig): void
61
    {
62
        $aOptions = $xConfig->getOption('container.set', []);
63
        foreach($aOptions as $xKey => $xValue)
64
        {
65
            // The key is the class name. It must be a string.
66
            $this->di->set((string)$xKey, $xValue);
67
        }
68
        $aOptions = $xConfig->getOption('container.val', []);
69
        foreach($aOptions as $xKey => $xValue)
70
        {
71
            // The key is the class name. It must be a string.
72
            $this->di->val((string)$xKey, $xValue);
73
        }
74
        $aOptions = $xConfig->getOption('container.auto', []);
75
        foreach($aOptions as $xValue)
76
        {
77
            // The key is the class name. It must be a string.
78
            $this->di->auto((string)$xValue);
79
        }
80
        $aOptions = $xConfig->getOption('container.alias', []);
81
        foreach($aOptions as $xKey => $xValue)
82
        {
83
            // The key is the class name. It must be a string.
84
            $this->di->alias((string)$xKey, (string)$xValue);
85
        }
86
        $aOptions = $xConfig->getOption('container.extend', []);
87
        foreach($aOptions as $xKey => $xValue)
88
        {
89
            // The key is the class name. It must be a string.
90
            $this->di->extend((string)$xKey, $xValue);
91
        }
92
    }
93
94
    /**
95
     * Register callables from a section of the config
96
     *
97
     * @param array $aOptions    The content of the config section
98
     * @param string $sCallableType    The type of callable to register
99
     *
100
     * @return void
101
     * @throws SetupException
102
     */
103
    private function registerCallables(array $aOptions, string $sCallableType): void
104
    {
105
        foreach($aOptions as $xKey => $xValue)
106
        {
107
            if(is_integer($xKey) && is_string($xValue))
108
            {
109
                // Register a function without options
110
                $this->xPluginManager->registerCallable($sCallableType, $xValue);
111
                continue;
112
            }
113
114
            if(is_string($xKey) && (is_array($xValue) || is_string($xValue)))
115
            {
116
                // Register a function with options
117
                $this->xPluginManager->registerCallable($sCallableType, $xKey, $xValue);
118
            }
119
        }
120
    }
121
122
    /**
123
     * Register exceptions handlers
124
     *
125
     * @param Config $xConfig
126
     *
127
     * @return void
128
     */
129
    private function registerExceptionHandlers(Config $xConfig): void
130
    {
131
        foreach($xConfig->getOption('exceptions', []) as $sExClass => $xExHandler)
132
        {
133
            $this->xCallbackManager->error($xExHandler, is_string($sExClass) ? $sExClass : '');
134
        }
135
    }
136
137
    /**
138
     * Get a callable list from config
139
     *
140
     * @param Config $xConfig
141
     * @param string $sOptionName
142
     * @param string $sOptionKey
143
     * @param string $sCallableType
144
     *
145
     * @return void
146
     */
147
    private function registerCallablesFromConfig(Config $xConfig,
148
        string $sOptionName, string $sOptionKey, string $sCallableType): void
149
    {
150
        // The callable (directory path, class or function name) can be used as the
151
        // key of the array item, a string as the value of an entry without a key,
152
        // or set with the key $sOptionKey in an array entry without a key.
153
        $aCallables = [];
154
        foreach($xConfig->getOption($sOptionName, []) as $xKey => $xValue)
155
        {
156
            if(is_string($xKey))
157
            {
158
                $aCallables[$xKey] = $xValue;
159
                continue;
160
            }
161
            if(is_string($xValue))
162
            {
163
                $aCallables[] = $xValue;
164
                continue;
165
            }
166
167
            if(is_array($xValue) && isset($xValue[$sOptionKey]))
168
            {
169
                $aCallables[$xValue[$sOptionKey]] = $xValue;
170
            }
171
            // Invalid values are ignored.
172
        }
173
        $this->registerCallables($aCallables, $sCallableType);
174
    }
175
176
    /**
177
     * Read and set Jaxon options from a JSON config file
178
     *
179
     * @param Config $xConfig The config options
180
     *
181
     * @return void
182
     * @throws SetupException
183
     */
184
    private function registerItemsFromConfig(Config $xConfig): void
185
    {
186
        // Set the config for the registered callables.
187
        $this->xRegistry->setPackageConfig($xConfig);
188
189
        // Register functions, classes and directories
190
        $this->registerCallablesFromConfig($xConfig,
191
            'functions', 'name', Jaxon::CALLABLE_FUNCTION);
192
        $this->registerCallablesFromConfig($xConfig,
193
            'classes', 'name', Jaxon::CALLABLE_CLASS);
194
        $this->registerCallablesFromConfig($xConfig,
195
            'directories', 'path', Jaxon::CALLABLE_DIR);
196
197
        // Unset the current config.
198
        $this->xRegistry->unsetPackageConfig();
199
200
        // Register the view namespaces
201
        // Note: the $xUserConfig can provide a "template" option, which is used to customize
202
        // the user defined view namespaces. That's why it is needed here.
203
        $this->xViewRenderer->addNamespaces($xConfig);
204
        // Save items in the DI container
205
        $this->updateContainer($xConfig);
206
        // Register the exception handlers
207
        $this->registerExceptionHandlers($xConfig);
208
    }
209
210
    /**
211
     * Get the options provided by the package library
212
     *
213
     * @param class-string $sClassName    The package class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
214
     *
215
     * @return Config
216
     * @throws SetupException
217
     */
218
    private function getPackageLibConfig(string $sClassName): Config
219
    {
220
        // $this->aPackages contains packages config file paths.
221
        $aLibOptions = $sClassName::config();
222
        if(is_string($aLibOptions))
223
        {
224
            // A string is supposed to be the path to a config file.
225
            $aLibOptions = $this->xConfigManager->read($aLibOptions);
226
        }
227
        elseif(!is_array($aLibOptions))
228
        {
229
            // Otherwise, anything else than an array is not accepted.
230
            $sMessage = $this->xTranslator->trans('errors.register.invalid', ['name' => $sClassName]);
231
            throw new SetupException($sMessage);
232
        }
233
        // Add the package name to the config
234
        $aLibOptions['package'] = $sClassName;
235
        return $this->xConfigManager->newConfig($aLibOptions);
236
    }
237
238
    /**
239
     * Register a package
240
     *
241
     * @param class-string $sClassName    The package class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
242
     * @param array $aUserOptions    The user provided package options
243
     *
244
     * @return void
245
     * @throws SetupException
246
     */
247
    public function registerPackage(string $sClassName, array $aUserOptions = []): void
248
    {
249
        $sClassName = trim($sClassName, '\\ ');
250
        if(!is_subclass_of($sClassName, AbstractPackage::class))
251
        {
252
            $sMessage = $this->xTranslator->trans('errors.register.invalid', ['name' => $sClassName]);
253
            throw new SetupException($sMessage);
254
        }
255
256
        // Register the declarations in the package config.
257
        $xAppConfig = $this->getPackageLibConfig($sClassName);
258
        $this->registerItemsFromConfig($xAppConfig);
259
260
        // Register the package and its options in the DI
261
        $this->di->registerPackage($sClassName, $aUserOptions);
262
263
        // Register the package as a code generator.
264
        $this->xPluginManager->registerCodeGenerator($sClassName, 500);
265
    }
266
267
    /**
268
     * Get a package instance
269
     *
270
     * @template T of AbstractPackage
271
     * @param class-string<T> $sClassName    The package class name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
272
     *
273
     * @return T|null
274
     */
275
    public function getPackage(string $sClassName): ?AbstractPackage
276
    {
277
        $sClassName = trim($sClassName, '\\ ');
278
        return $this->di->h($sClassName) ? $this->di->g($sClassName) : null;
279
    }
280
281
    /**
282
     * Read and set Jaxon options from the config
283
     *
284
     * @return void
285
     * @throws SetupException
286
     */
287
    public function registerFromConfig(): void
288
    {
289
        $xAppConfig = $this->xConfigManager->getAppConfig();
290
        $this->registerItemsFromConfig($xAppConfig);
291
292
        // Register packages
293
        $aPackageConfig = $xAppConfig->getOption('packages', []);
294
        foreach($aPackageConfig as $xKey => $xValue)
295
        {
296
            if(is_integer($xKey) && is_string($xValue))
297
            {
298
                // Register a package without options
299
                $sClassName = $xValue;
300
                $this->registerPackage($sClassName);
301
                continue;
302
            }
303
304
            if(is_string($xKey) && is_array($xValue))
305
            {
306
                // Register a package with options
307
                $sClassName = $xKey;
308
                $aPkgOptions = $xValue;
309
                $this->registerPackage($sClassName, $aPkgOptions);
310
            }
311
        }
312
    }
313
}
314