PackageManager::getPackage()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
rs 10
c 1
b 0
f 0
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