PackageManager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
nc 1
nop 8
dl 0
loc 5
rs 10
c 1
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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