PackageManager   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Importance

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

10 Methods

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