PackageManager   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 72
c 3
b 0
f 0
dl 0
loc 270
rs 9.52
wmc 36

10 Methods

Rating   Name   Duplication   Size   Complexity  
A updateContainer() 0 25 5
A registerExceptionHandlers() 0 5 3
B registerCallables() 0 15 7
A __construct() 0 5 1
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
    private 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
    }
87
88
    /**
89
     * Register callables from a section of the config
90
     *
91
     * @param array $aOptions    The content of the config section
92
     * @param string $sCallableType    The type of callable to register
93
     *
94
     * @return void
95
     * @throws SetupException
96
     */
97
    private function registerCallables(array $aOptions, string $sCallableType): void
98
    {
99
        foreach($aOptions as $xKey => $xValue)
100
        {
101
            if(is_integer($xKey) && is_string($xValue))
102
            {
103
                // Register a function without options
104
                $this->xPluginManager->registerCallable($sCallableType, $xValue);
105
                continue;
106
            }
107
108
            if(is_string($xKey) && (is_array($xValue) || is_string($xValue)))
109
            {
110
                // Register a function with options
111
                $this->xPluginManager->registerCallable($sCallableType, $xKey, $xValue);
112
            }
113
        }
114
    }
115
116
    /**
117
     * Register exceptions handlers
118
     *
119
     * @param Config $xConfig
120
     *
121
     * @return void
122
     */
123
    private function registerExceptionHandlers(Config $xConfig): void
124
    {
125
        foreach($xConfig->getOption('exceptions', []) as $sExClass => $xExHandler)
126
        {
127
            $this->xCallbackManager->error($xExHandler, is_string($sExClass) ? $sExClass : '');
128
        }
129
    }
130
131
    /**
132
     * Get a callable list from config
133
     *
134
     * @param Config $xConfig
135
     * @param string $sOptionName
136
     * @param string $sOptionKey
137
     * @param string $sCallableType
138
     *
139
     * @return void
140
     */
141
    private function registerCallablesFromConfig(Config $xConfig,
142
        string $sOptionName, string $sOptionKey, string $sCallableType): void
143
    {
144
        // The callable (directory path, class or function name) can be used as the
145
        // key of the array item, a string as the value of an entry without a key,
146
        // or set with the key $sOptionKey in an array entry without a key.
147
        $aCallables = [];
148
        foreach($xConfig->getOption($sOptionName, []) as $xKey => $xValue)
149
        {
150
            if(is_string($xKey))
151
            {
152
                $aCallables[$xKey] = $xValue;
153
                continue;
154
            }
155
            if(is_string($xValue))
156
            {
157
                $aCallables[] = $xValue;
158
                continue;
159
            }
160
161
            if(is_array($xValue) && isset($xValue[$sOptionKey]))
162
            {
163
                $aCallables[$xValue[$sOptionKey]] = $xValue;
164
            }
165
            // Invalid values are ignored.
166
        }
167
        $this->registerCallables($aCallables, $sCallableType);
168
    }
169
170
    /**
171
     * Read and set Jaxon options from a JSON config file
172
     *
173
     * @param Config $xConfig The config options
174
     *
175
     * @return void
176
     * @throws SetupException
177
     */
178
    private function registerItemsFromConfig(Config $xConfig): void
179
    {
180
        // Set the config for the registered callables.
181
        $this->xRegistry->setPackageConfig($xConfig);
182
183
        // Register functions, classes and directories
184
        $this->registerCallablesFromConfig($xConfig,
185
            'functions', 'name', Jaxon::CALLABLE_FUNCTION);
186
        $this->registerCallablesFromConfig($xConfig,
187
            'classes', 'name', Jaxon::CALLABLE_CLASS);
188
        $this->registerCallablesFromConfig($xConfig,
189
            'directories', 'path', Jaxon::CALLABLE_DIR);
190
191
        // Unset the current config.
192
        $this->xRegistry->unsetPackageConfig();
193
194
        // Register the view namespaces
195
        // Note: the $xUserConfig can provide a "template" option, which is used to customize
196
        // the user defined view namespaces. That's why it is needed here.
197
        $this->xViewRenderer->addNamespaces($xConfig);
198
        // Save items in the DI container
199
        $this->updateContainer($xConfig);
200
        // Register the exception handlers
201
        $this->registerExceptionHandlers($xConfig);
202
    }
203
204
    /**
205
     * Get the options provided by the package library
206
     *
207
     * @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...
208
     *
209
     * @return Config
210
     * @throws SetupException
211
     */
212
    private function getPackageLibConfig(string $sClassName): Config
213
    {
214
        // $this->aPackages contains packages config file paths.
215
        $aLibOptions = $sClassName::config();
216
        if(is_string($aLibOptions))
217
        {
218
            // A string is supposed to be the path to a config file.
219
            $aLibOptions = $this->xConfigManager->read($aLibOptions);
220
        }
221
        elseif(!is_array($aLibOptions))
222
        {
223
            // Otherwise, anything else than an array is not accepted.
224
            $sMessage = $this->xTranslator->trans('errors.register.invalid', ['name' => $sClassName]);
225
            throw new SetupException($sMessage);
226
        }
227
        // Add the package name to the config
228
        $aLibOptions['package'] = $sClassName;
229
        return $this->xConfigManager->newConfig($aLibOptions);
230
    }
231
232
    /**
233
     * Register a package
234
     *
235
     * @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...
236
     * @param array $aUserOptions    The user provided package options
237
     *
238
     * @return void
239
     * @throws SetupException
240
     */
241
    public function registerPackage(string $sClassName, array $aUserOptions = []): void
242
    {
243
        $sClassName = trim($sClassName, '\\ ');
244
        if(!is_subclass_of($sClassName, AbstractPackage::class))
245
        {
246
            $sMessage = $this->xTranslator->trans('errors.register.invalid', ['name' => $sClassName]);
247
            throw new SetupException($sMessage);
248
        }
249
250
        // Register the declarations in the package config.
251
        $xAppConfig = $this->getPackageLibConfig($sClassName);
252
        $this->registerItemsFromConfig($xAppConfig);
253
254
        // Register the package and its options in the DI
255
        $this->di->registerPackage($sClassName, $aUserOptions);
256
257
        // Register the package as a code generator.
258
        $this->xPluginManager->registerCodeGenerator($sClassName, 500);
259
    }
260
261
    /**
262
     * Get a package instance
263
     *
264
     * @template T of AbstractPackage
265
     * @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...
266
     *
267
     * @return T|null
268
     */
269
    public function getPackage(string $sClassName): ?AbstractPackage
270
    {
271
        $sClassName = trim($sClassName, '\\ ');
272
        return $this->di->h($sClassName) ? $this->di->g($sClassName) : null;
273
    }
274
275
    /**
276
     * Read and set Jaxon options from the config
277
     *
278
     * @return void
279
     * @throws SetupException
280
     */
281
    public function registerFromConfig(): void
282
    {
283
        $xAppConfig = $this->xConfigManager->getAppConfig();
284
        $this->registerItemsFromConfig($xAppConfig);
285
286
        // Register packages
287
        $aPackageConfig = $xAppConfig->getOption('packages', []);
288
        foreach($aPackageConfig as $xKey => $xValue)
289
        {
290
            if(is_integer($xKey) && is_string($xValue))
291
            {
292
                // Register a package without options
293
                $sClassName = $xValue;
294
                $this->registerPackage($sClassName);
295
                continue;
296
            }
297
298
            if(is_string($xKey) && is_array($xValue))
299
            {
300
                // Register a package with options
301
                $sClassName = $xKey;
302
                $aPkgOptions = $xValue;
303
                $this->registerPackage($sClassName, $aPkgOptions);
304
            }
305
        }
306
    }
307
}
308