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
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 5
rs 10

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