Completed
Push — master ( f42a52...0a9499 )
by Thierry
02:50 queued 01:16
created

CallableRepository::addNamespace()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 32
nop 2
dl 0
loc 29
rs 8.5226
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CallableRepository.php - Jaxon callable object repository
5
 *
6
 * This class stores all the callable object already created.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2019 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\Request\Support;
16
17
use Jaxon\Request\Factory\CallableClass\Request as RequestFactory;
18
use Jaxon\Request\Factory\CallableClass\Paginator as PaginatorFactory;
19
20
use RecursiveDirectoryIterator;
21
use RecursiveIteratorIterator;
22
23
class CallableRepository
24
{
25
    /**
26
     * The registered namespaces
27
     *
28
     * These are the namespaces specified when registering directories.
29
     *
30
     * @var array
31
     */
32
    protected $aNamespaceOptions = [];
33
34
    /**
35
     * The registered classes
36
     *
37
     * These are registered classes, and classes in directories registered without a namespace.
38
     *
39
     * @var array
40
     */
41
    protected $aClassOptions = [];
42
43
    /**
44
     * The namespaces
45
     *
46
     * These are all the namespaces found in registered directories
47
     *
48
     * @var array
49
     */
50
    protected $aNamespaces = [];
51
52
    /**
53
     * The created callable objects
54
     *
55
     * @var array
56
     */
57
    protected $aCallableObjects = [];
58
59
    /**
60
     * The options to be applied to callable objects
61
     *
62
     * @var array
63
     */
64
    protected $aCallableOptions = [];
65
66
    /**
67
     * If the underscore is used as separator in js class names
68
     *
69
     * @var boolean
70
     */
71
    protected $bUsingUnderscore = false;
72
73
    /**
74
     * The Composer autoloader
75
     *
76
     * @var Autoloader
77
     */
78
    private $xAutoloader = null;
79
80
    /**
81
     * The class constructor
82
     */
83
    public function __construct()
84
    {
85
        // Set the composer autoloader
86
        $sAutoloadFile = __DIR__ . '/../../../../../autoload.php';
87
        if(file_exists($sAutoloadFile))
88
        {
89
            $this->xAutoloader = require($sAutoloadFile);
90
        }
91
    }
92
93
    /**
94
     *
95
     * @param string        $sClassName     The name of the class being registered
96
     * @param array|string  $aOptions       The associated options
97
     *
98
     * @return void
99
     */
100
    public function addClass($sClassName, $aOptions)
101
    {
102
        $sClassName = trim($sClassName, '\\');
103
        $this->aClassOptions[$sClassName] = $aOptions;
104
    }
105
106
    /**
107
     * Get a given class options from specified directory options
108
     *
109
     * @param string        $sClassName         The name of the class
110
     * @param array         $aDirectoryOptions  The directory options
111
     * @param array         $aDefaultOptions    The default options
112
     *
113
     * @return array
114
     */
115
    private function getClassOptions($sClassName, array $aDirectoryOptions, array $aDefaultOptions = [])
116
    {
117
        $aOptions = $aDefaultOptions;
118
        if(key_exists('separator', $aDirectoryOptions))
119
        {
120
            $aOptions['separator'] = $aDirectoryOptions['separator'];
121
        }
122
        if(key_exists('protected', $aDirectoryOptions))
123
        {
124
            $aOptions['protected'] = $aDirectoryOptions['protected'];
125
        }
126
        if(key_exists('*', $aDirectoryOptions))
127
        {
128
            $aOptions = array_merge($aOptions, $aDirectoryOptions['*']);
129
        }
130
        if(key_exists($sClassName, $aDirectoryOptions))
131
        {
132
            $aOptions = array_merge($aOptions, $aDirectoryOptions[$sClassName]);
133
        }
134
135
        return $aOptions;
136
    }
137
138
    /**
139
     *
140
     * @param string        $sDirectory     The directory being registered
141
     * @param array         $aOptions       The associated options
142
     *
143
     * @return void
144
     */
145
    public function addDirectory($sDirectory, $aOptions)
146
    {
147
        $itDir = new RecursiveDirectoryIterator($sDirectory);
148
        $itFile = new RecursiveIteratorIterator($itDir);
149
        // Iterate on dir content
150
        foreach($itFile as $xFile)
151
        {
152
            // skip everything except PHP files
153
            if(!$xFile->isFile() || $xFile->getExtension() != 'php')
154
            {
155
                continue;
156
            }
157
158
            $aClassOptions = [];
159
            // No more classmap autoloading. The file will be included when needed.
160
            if(($aOptions['autoload']))
161
            {
162
                $aClassOptions['include'] = $xFile->getPathname();
163
            }
164
165
            $sClassName = $xFile->getBasename('.php');
166
            $aClassOptions = $this->getClassOptions($sClassName, $aOptions, $aClassOptions);
167
            $this->addClass($sClassName, $aClassOptions);
168
        }
169
    }
170
171
    /**
172
     *
173
     * @param string        $sNamespace     The namespace of the directory being registered
174
     * @param array         $aOptions       The associated options
175
     *
176
     * @return void
177
     */
178
    public function addNamespace($sNamespace, array $aOptions)
179
    {
180
        // Separator default value
181
        if(!key_exists('separator', $aOptions))
182
        {
183
            $aOptions['separator'] = '.';
184
        }
185
        $aOptions['separator'] = trim($aOptions['separator']);
186
        if(!in_array($aOptions['separator'], ['.', '_']))
187
        {
188
            $aOptions['separator'] = '.';
189
        }
190
        if($aOptions['separator'] == '_')
191
        {
192
            $this->bUsingUnderscore = true;
193
        }
194
        // Set the autoload option default value
195
        if(!key_exists('autoload', $aOptions))
196
        {
197
            $aOptions['autoload'] = true;
198
        }
199
        // Register the dir with PSR4 autoloading
200
        if(($aOptions['autoload']) && $this->xAutoloader != null)
201
        {
202
            $this->xAutoloader->setPsr4($sNamespace . '\\', $aOptions['directory']);
203
        }
204
205
        $this->aNamespaceOptions[$sNamespace] = $aOptions;
206
    }
207
208
    /**
209
     * Find a class name is register with Jaxon::CALLABLE_CLASS type
210
     *
211
     * @param string        $sClassName            The class name of the callable object
212
     *
213
     * @return array|null
214
     */
215
    private function getOptionsFromClass($sClassName)
216
    {
217
        if(!key_exists($sClassName, $this->aClassOptions))
218
        {
219
            return null; // Class not registered
220
        }
221
        return $this->aClassOptions[$sClassName];
222
    }
223
224
    /**
225
     * Find a class name is register with Jaxon::CALLABLE_DIR type
226
     *
227
     * @param string        $sClassName            The class name of the callable object
228
     * @param string|null   $sNamespace            The namespace
229
     *
230
     * @return array|null
231
     */
232
    private function getOptionsFromNamespace($sClassName, $sNamespace = null)
233
    {
234
        // Find the corresponding namespace
235
        if($sNamespace === null)
236
        {
237
            foreach(array_keys($this->aNamespaceOptions) as $_sNamespace)
238
            {
239
                if(substr($sClassName, 0, strlen($_sNamespace) + 1) == $_sNamespace . '\\')
240
                {
241
                    $sNamespace = $_sNamespace;
242
                    break;
243
                }
244
            }
245
        }
246
        if($sNamespace === null)
247
        {
248
            return null; // Class not registered
249
        }
250
251
        // Get the class options
252
        $aOptions = $this->aNamespaceOptions[$sNamespace];
253
        $aDefaultOptions = ['namespace' => $sNamespace];
254
        if(key_exists('separator', $aOptions))
255
        {
256
            $aDefaultOptions['separator'] = $aOptions['separator'];
257
        }
258
        return $this->getClassOptions($sClassName, $aOptions, $aDefaultOptions);
259
    }
260
261
    /**
262
     * Find a callable object by class name
263
     *
264
     * @param string        $sClassName            The class name of the callable object
265
     * @param array         $aOptions              The callable object options
266
     *
267
     * @return object
268
     */
269
    protected function _getCallableObject($sClassName, array $aOptions)
270
    {
271
        // Make sure the registered class exists
272
        if(key_exists('include', $aOptions))
273
        {
274
            require_once($aOptions['include']);
275
        }
276
        if(!class_exists($sClassName))
277
        {
278
            return null;
279
        }
280
281
        // Create the callable object
282
        $xCallableObject = new CallableObject($sClassName);
283
        $this->aCallableOptions[$sClassName] = [];
284
        foreach($aOptions as $sName => $xValue)
285
        {
286
            if(in_array($sName, ['separator', 'namespace', 'protected']))
287
            {
288
                $xCallableObject->configure($sName, $xValue);
289
            }
290
            elseif(is_array($xValue) && $sName != 'include')
291
            {
292
                // These options are to be included in javascript code.
293
                $this->aCallableOptions[$sClassName][$sName] = $xValue;
294
            }
295
        }
296
        $this->aCallableObjects[$sClassName] = $xCallableObject;
297
298
        // Register the request factory for this callable object
299
        jaxon()->di()->setCallableClassRequestFactory($sClassName, $xCallableObject);
300
        // jaxon()->di()->set($sClassName . '_RequestFactory', function () use ($sClassName) {
301
        //     $xCallableObject = $this->aCallableObjects[$sClassName];
302
        //     return new CallableClassRequestFactory($xCallableObject);
303
        // });
304
        // Register the paginator factory for this callable object
305
        jaxon()->di()->setCallableClassPaginatorFactory($sClassName, $xCallableObject);
306
        // jaxon()->di()->set($sClassName . '_PaginatorFactory', function () use ($sClassName) {
307
        //     $xCallableObject = $this->aCallableObjects[$sClassName];
308
        //     return new CallableClassPaginatorFactory($xCallableObject);
309
        // });
310
311
        return $xCallableObject;
312
    }
313
314
    /**
315
     * Find a callable object by class name
316
     *
317
     * @param string        $sClassName            The class name of the callable object
318
     *
319
     * @return object
320
     */
321
    public function getCallableObject($sClassName)
322
    {
323
        // Replace all separators ('.' and '_') with antislashes, and remove the antislashes
324
        // at the beginning and the end of the class name.
325
        $sClassName = (string)$sClassName;
326
        $sClassName = trim(str_replace('.', '\\', $sClassName), '\\');
327
        if($this->bUsingUnderscore)
328
        {
329
            $sClassName = trim(str_replace('_', '\\', $sClassName), '\\');
330
        }
331
332
        if(key_exists($sClassName, $this->aCallableObjects))
333
        {
334
            return $this->aCallableObjects[$sClassName];
335
        }
336
337
        $aOptions = $this->getOptionsFromClass($sClassName);
338
        if($aOptions === null)
339
        {
340
            $aOptions = $this->getOptionsFromNamespace($sClassName);
341
        }
342
        if($aOptions === null)
343
        {
344
            return null;
345
        }
346
347
        return $this->_getCallableObject($sClassName, $aOptions);
348
    }
349
350
    /**
351
     * Create callable objects for all registered namespaces
352
     *
353
     * @return void
354
     */
355
    public function createCallableObjects()
356
    {
357
        // Create callable objects for registered classes
358
        foreach($this->aClassOptions as $sClassName => $aClassOptions)
359
        {
360
            if(!key_exists($sClassName, $this->aCallableObjects))
361
            {
362
                $this->_getCallableObject($sClassName, $aClassOptions);
363
            }
364
        }
365
366
        // Create callable objects for registered namespaces
367
        $sDS = DIRECTORY_SEPARATOR;
368
        foreach($this->aNamespaceOptions as $sNamespace => $aOptions)
369
        {
370
            if(key_exists($sNamespace, $this->aNamespaces))
371
            {
372
                continue;
373
            }
374
375
            $this->aNamespaces[$sNamespace] = ['separator' => $aOptions['separator']];
376
377
            // Iterate on dir content
378
            $sDirectory = $aOptions['directory'];
379
            $itDir = new RecursiveDirectoryIterator($sDirectory);
380
            $itFile = new RecursiveIteratorIterator($itDir);
381
            foreach($itFile as $xFile)
382
            {
383
                // skip everything except PHP files
384
                if(!$xFile->isFile() || $xFile->getExtension() != 'php')
385
                {
386
                    continue;
387
                }
388
389
                // Find the class path (the same as the class namespace)
390
                $sClassPath = $sNamespace;
391
                $sRelativePath = substr($xFile->getPath(), strlen($sDirectory));
392
                $sRelativePath = trim(str_replace($sDS, '\\', $sRelativePath), '\\');
393
                if($sRelativePath != '')
394
                {
395
                    $sClassPath .= '\\' . $sRelativePath;
396
                }
397
398
                $this->aNamespaces[$sClassPath] = ['separator' => $aOptions['separator']];
399
                $sClassName = $sClassPath . '\\' . $xFile->getBasename('.php');
400
401
                if(!key_exists($sClassName, $this->aCallableObjects))
402
                {
403
                    $aClassOptions = $this->getOptionsFromNamespace($sClassName, $sNamespace);
404
                    if($aClassOptions !== null)
405
                    {
406
                        $this->_getCallableObject($sClassName, $aClassOptions);
407
                    }
408
                }
409
            }
410
        }
411
    }
412
413
    /**
414
     * Find a user registered callable object by class name
415
     *
416
     * @param string        $sClassName            The class name of the callable object
417
     *
418
     * @return object
419
     */
420
    protected function getRegisteredObject($sClassName)
421
    {
422
        // Get the corresponding callable object
423
        $xCallableObject = $this->getCallableObject($sClassName);
424
        return ($xCallableObject) ? $xCallableObject->getRegisteredObject() : null;
425
    }
426
427
    /**
428
     * Get all registered namespaces
429
     *
430
     * @return array
431
     */
432
    public function getNamespaces()
433
    {
434
        return $this->aNamespaces;
435
    }
436
437
    /**
438
     * Get all registered callable objects
439
     *
440
     * @return array
441
     */
442
    public function getCallableObjects()
443
    {
444
        return $this->aCallableObjects;
445
    }
446
447
    /**
448
     * Get all registered callable objects options
449
     *
450
     * @return array
451
     */
452
    public function getCallableOptions()
453
    {
454
        return $this->aCallableOptions;
455
    }
456
}
457