Container::setContainer()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * Container.php
5
 *
6
 * Jaxon DI container. Provide container service for Jaxon classes.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2016 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\Di;
16
17
use Jaxon\App\Ajax\Lib;
18
use Jaxon\App\I18n\Translator;
19
use Jaxon\App\Session\SessionInterface;
20
use Jaxon\Exception\SetupException;
21
use Pimple\Container as PimpleContainer;
22
use Psr\Container\ContainerInterface;
23
use Psr\Log\LoggerInterface;
24
use Psr\Log\NullLogger;
25
26
use Closure;
27
use Exception;
28
use ReflectionClass;
29
use ReflectionException;
30
use ReflectionNamedType;
31
use ReflectionParameter;
32
use Throwable;
33
34
use function array_map;
35
use function realpath;
36
37
class Container
38
{
39
    use Traits\AppTrait;
40
    use Traits\PsrTrait;
41
    use Traits\RequestTrait;
42
    use Traits\ResponseTrait;
43
    use Traits\PluginTrait;
44
    use Traits\CallableTrait;
45
    use Traits\ViewTrait;
46
    use Traits\UtilTrait;
47
    use Traits\MetadataTrait;
48
49
    /**
50
     * The library Dependency Injection Container
51
     *
52
     * @var PimpleContainer
53
     */
54
    private $xLibContainer;
55
56
    /**
57
     * The application or framework Dependency Injection Container
58
     *
59
     * @var ContainerInterface
60
     */
61
    private $xAppContainer = null;
62
63
    /**
64
     * The class constructor
65
     */
66
    public function __construct(Lib $jaxon)
67
    {
68
        $this->xLibContainer = new PimpleContainer();
69
70
        // Save the Lib and Container instances
71
        $this->val(Lib::class, $jaxon);
72
        $this->val(Container::class, $this);
73
74
        // Register the null logger by default
75
        $this->setLogger(new NullLogger());
76
77
        // Template directory
78
        $sTemplateDir = realpath(__DIR__ . '/../../templates');
79
        $this->val('jaxon.core.dir.template', $sTemplateDir);
80
81
        // Translation directory
82
        $sTranslationDir = realpath(__DIR__ . '/../../translations');
83
        $this->val('jaxon.core.dir.translation', $sTranslationDir);
84
85
        $this->registerAll();
86
    }
87
88
    /**
89
     * Register the values into the container
90
     *
91
     * @return void
92
     */
93
    private function registerAll()
94
    {
95
        $this->registerApp();
96
        $this->registerPsr();
97
        $this->registerRequests();
98
        $this->registerResponses();
99
        $this->registerPlugins();
100
        $this->registerCallables();
101
        $this->registerViews();
102
        $this->registerUtils();
103
        $this->registerMetadataReader();
104
    }
105
106
    /**
107
     * Set the logger
108
     *
109
     * @param LoggerInterface $xLogger
110
     *
111
     * @return void
112
     */
113
    public function setLogger(LoggerInterface $xLogger)
114
    {
115
        $this->val(LoggerInterface::class, $xLogger);
116
    }
117
118
    /**
119
     * Get the logger
120
     *
121
     * @return LoggerInterface
122
     */
123
    public function getLogger(): LoggerInterface
124
    {
125
        return $this->get(LoggerInterface::class);
126
    }
127
128
    /**
129
     * Set the container provided by the integrated framework
130
     *
131
     * @param ContainerInterface $xContainer    The container implementation
132
     *
133
     * @return void
134
     */
135
    public function setContainer(ContainerInterface $xContainer)
136
    {
137
        $this->xAppContainer = $xContainer;
138
    }
139
140
    /**
141
     * Check if a class is defined in the container
142
     *
143
     * @param string $sClass    The full class name
144
     *
145
     * @return bool
146
     */
147
    public function h(string $sClass): bool
148
    {
149
        return $this->xLibContainer->offsetExists($sClass);
150
    }
151
152
    /**
153
     * Check if a class is defined in the container
154
     *
155
     * @param string $sClass    The full class name
156
     *
157
     * @return bool
158
     */
159
    public function has(string $sClass): bool
160
    {
161
        if($this->xAppContainer != null && $this->xAppContainer->has($sClass))
162
        {
163
            return true;
164
        }
165
        return $this->xLibContainer->offsetExists($sClass);
166
    }
167
168
    /**
169
     * Get a class instance
170
     *
171
     * @param string $sClass    The full class name
172
     *
173
     * @return mixed
174
     */
175
    public function g(string $sClass)
176
    {
177
        return $this->xLibContainer->offsetGet($sClass);
178
    }
179
180
    /**
181
     * Get a class instance
182
     *
183
     * @param string $sClass The full class name
184
     *
185
     * @return mixed
186
     * @throws SetupException
187
     */
188
    public function get(string $sClass)
189
    {
190
        try
191
        {
192
            if($this->xAppContainer != null && $this->xAppContainer->has($sClass))
193
            {
194
                return $this->xAppContainer->get($sClass);
195
            }
196
            return $this->xLibContainer->offsetGet($sClass);
197
        }
198
        catch(Exception|Throwable $e)
199
        {
200
            $xLogger = $this->g(LoggerInterface::class);
201
            $xTranslator = $this->g(Translator::class);
202
            $sMessage = $e->getMessage() . ': ' . $xTranslator->trans('errors.class.container', ['name' => $sClass]);
203
            $xLogger->error($e->getMessage(), ['message' => $sMessage]);
204
            throw new SetupException($sMessage);
205
        }
206
    }
207
208
    /**
209
     * Save a closure in the container
210
     *
211
     * @param string $sClass    The full class name
212
     * @param Closure $xClosure    The closure
213
     * @param bool $bIsSingleton
214
     *
215
     * @return void
216
     */
217
    public function set(string $sClass, Closure $xClosure, bool $bIsSingleton = true)
218
    {
219
        // Wrap the user closure into a new closure, so it can take this container as a parameter.
220
        $xClosure = function() use($xClosure) {
221
            return $xClosure($this);
222
        };
223
        $this->xLibContainer->offsetSet($sClass,
224
            $bIsSingleton ? $xClosure : $this->xLibContainer->factory($xClosure));
225
    }
226
227
    /**
228
     * Save a value in the container
229
     *
230
     * @param string $sKey    The key
231
     * @param mixed $xValue    The value
232
     *
233
     * @return void
234
     */
235
    public function val(string $sKey, $xValue)
236
    {
237
       $this->xLibContainer->offsetSet($sKey, $xValue);
238
    }
239
240
    /**
241
     * Set an alias in the container
242
     *
243
     * @param string $sAlias    The alias name
244
     * @param string $sClass    The class name
245
     *
246
     * @return void
247
     */
248
    public function alias(string $sAlias, string $sClass)
249
    {
250
        $this->set($sAlias, function($di) use ($sClass) {
251
            return $di->get($sClass);
252
        });
253
    }
254
255
    /**
256
     * @param ReflectionClass $xClass
257
     * @param ReflectionParameter $xParameter
258
     *
259
     * @return mixed
260
     * @throws SetupException
261
     */
262
    protected function getParameter(ReflectionClass $xClass, ReflectionParameter $xParameter)
0 ignored issues
show
Unused Code introduced by
The parameter $xClass is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

262
    protected function getParameter(/** @scrutinizer ignore-unused */ ReflectionClass $xClass, ReflectionParameter $xParameter)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
263
    {
264
        $xType = $xParameter->getType();
265
        // Check the parameter class first.
266
        if($xType instanceof ReflectionNamedType)
267
        {
268
            // Check the class + the name
269
            if($this->has($xType->getName() . ' $' . $xParameter->getName()))
270
            {
271
                return $this->get($xType->getName() . ' $' . $xParameter->getName());
272
            }
273
            // Check the class only
274
            if($this->has($xType->getName()))
275
            {
276
                return $this->get($xType->getName());
277
            }
278
        }
279
        // Check the name only
280
        return $this->get('$' . $xParameter->getName());
281
    }
282
283
    /**
284
     * Create an instance of a class, getting the constructor parameters from the DI container
285
     *
286
     * @param string|ReflectionClass $xClass The class name or the reflection class
287
     *
288
     * @return object|null
289
     * @throws ReflectionException
290
     * @throws SetupException
291
     */
292
    public function make($xClass)
293
    {
294
        if(is_string($xClass))
295
        {
296
            $xClass = new ReflectionClass($xClass); // Create the reflection class instance
297
        }
298
        if(!($xClass instanceof ReflectionClass))
0 ignored issues
show
introduced by
$xClass is always a sub-type of ReflectionClass.
Loading history...
299
        {
300
            return null;
301
        }
302
        // Use the Reflection class to get the parameters of the constructor
303
        if(($constructor = $xClass->getConstructor()) === null)
304
        {
305
            return $xClass->newInstance();
306
        }
307
        $aParameterInstances = array_map(function($xParameter) use($xClass) {
308
            return $this->getParameter($xClass, $xParameter);
309
        }, $constructor->getParameters());
310
311
        return $xClass->newInstanceArgs($aParameterInstances);
312
    }
313
314
    /**
315
     * Create an instance of a class by automatically fetching the dependencies in the constructor.
316
     *
317
     * @param string $sClass    The class name
318
     *
319
     * @return void
320
     */
321
    public function auto(string $sClass)
322
    {
323
        $this->set($sClass, function() use ($sClass) {
324
            return $this->make($sClass);
325
        });
326
    }
327
328
    /**
329
     * Get the session manager
330
     *
331
     * @return SessionInterface|null
332
     */
333
    public function getSessionManager(): ?SessionInterface
334
    {
335
        return $this->h(SessionInterface::class) ? $this->g(SessionInterface::class) : null;
336
    }
337
338
    /**
339
     * Set the session manager
340
     *
341
     * @param Closure $xClosure    A closure to create the session manager instance
342
     *
343
     * @return void
344
     */
345
    public function setSessionManager(Closure $xClosure)
346
    {
347
        $this->set(SessionInterface::class, $xClosure);
348
    }
349
}
350