Passed
Push — feature/code_improvement ( 94564c...8399ab )
by Thierry
02:36
created

CallableRegistry::getCallableObject()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 6
nop 1
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CallableRegistry.php - Jaxon callable object registrar
5
 *
6
 * This class is the entry point for class, directory and namespace registration.
7
 *
8
 * @package jaxon-core
0 ignored issues
show
Coding Style introduced by
Package name "jaxon-core" is not valid; consider "Jaxoncore" instead
Loading history...
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
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
14
15
namespace Jaxon\Request\Support;
16
17
use RecursiveDirectoryIterator;
18
use RecursiveIteratorIterator;
19
20
class CallableRegistry
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class CallableRegistry
Loading history...
21
{
22
    /**
23
     * The callable repository
24
     *
25
     * @var CallableRepository
26
     */
27
    public $xRepository;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
28
29
    /**
30
     * The registered directories
31
     *
32
     * These are directories registered without a namespace.
33
     *
34
     * @var array
35
     */
36
    protected $aDirectories = [];
37
38
    /**
39
     * Indicate if the registered directories are already parsed
40
     *
41
     * @var boolean
42
     */
43
    protected $bParsedDirectories = false;
44
45
    /**
46
     * The registered namespaces
47
     *
48
     * These are the namespaces specified when registering directories.
49
     *
50
     * @var array
51
     */
52
    protected $aNamespaces = [];
53
54
    /**
55
     * Indicate if the registered namespaces are already parsed
56
     *
57
     * @var boolean
58
     */
59
    protected $bParsedNamespaces = false;
60
61
    /**
62
     * If the underscore is used as separator in js class names.
63
     *
64
     * @var boolean
65
     */
66
    protected $bUsingUnderscore = false;
67
68
    /**
69
     * The Composer autoloader
70
     *
71
     * @var Autoloader
72
     */
73
    private $xAutoloader = null;
74
75
    /**
76
     * The class constructor
77
     *
78
     * @param CallableRepository        $xRepository
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
79
     */
80
    public function __construct(CallableRepository $xRepository)
81
    {
82
        $this->xRepository = $xRepository;
83
84
        // Set the composer autoloader
85
        $sAutoloadFile = __DIR__ . '/../../../../../autoload.php';
86
        if(file_exists($sAutoloadFile))
87
        {
88
            $this->xAutoloader = require($sAutoloadFile);
89
        }
90
    }
91
92
    /**
93
     *
94
     * @param string        $sDirectory     The directory being registered
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
95
     * @param array         $aOptions       The associated options
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 9 found
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter name; 7 found
Loading history...
96
     *
97
     * @return void
98
     */
99
    public function addDirectory($sDirectory, array $aOptions)
100
    {
101
        // Set the autoload option default value
102
        if(!key_exists('autoload', $aOptions))
103
        {
104
            $aOptions['autoload'] = true;
105
        }
106
107
        $this->aDirectories[$sDirectory] = $aOptions;
108
    }
109
110
    /**
111
     *
112
     * @param string        $sNamespace     The namespace of the directory being registered
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 5 found
Loading history...
113
     * @param array         $aOptions       The associated options
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 9 found
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter name; 7 found
Loading history...
114
     *
115
     * @return void
116
     */
117
    public function addNamespace($sNamespace, array $aOptions)
118
    {
119
        // Separator default value
120
        if(!key_exists('separator', $aOptions))
121
        {
122
            $aOptions['separator'] = '.';
123
        }
124
        $aOptions['separator'] = trim($aOptions['separator']);
125
        if(!in_array($aOptions['separator'], ['.', '_']))
126
        {
127
            $aOptions['separator'] = '.';
128
        }
129
        if($aOptions['separator'] == '_')
130
        {
131
            $this->bUsingUnderscore = true;
132
        }
133
        // Set the autoload option default value
134
        if(!key_exists('autoload', $aOptions))
135
        {
136
            $aOptions['autoload'] = true;
137
        }
138
        // Register the dir with PSR4 autoloading
139
        if(($aOptions['autoload']) && $this->xAutoloader != null)
140
        {
141
            $this->xAutoloader->setPsr4($sNamespace . '\\', $aOptions['directory']);
142
        }
143
144
        $this->aNamespaces[$sNamespace] = $aOptions;
145
    }
146
147
    /**
148
     * Read classes from registered directories (without namespaces)
149
     *
150
     * @return void
151
     */
152
    public function parseDirectories()
153
    {
154
        // Browse directories without namespaces and read all the files.
155
        // This is to be done only once.
156
        if($this->bParsedDirectories)
157
        {
158
            return;
159
        }
160
        $this->bParsedDirectories = true;
161
162
        foreach($this->aDirectories as $sDirectory => $aOptions)
163
        {
164
            $itFile = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sDirectory));
165
            // Iterate on dir content
166
            foreach($itFile as $xFile)
167
            {
168
                // skip everything except PHP files
169
                if(!$xFile->isFile() || $xFile->getExtension() != 'php')
170
                {
171
                    continue;
172
                }
173
174
                $sClassName = $xFile->getBasename('.php');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
175
                $aClassOptions = [];
176
                // No more classmap autoloading. The file will be included when needed.
177
                if(($aOptions['autoload']))
178
                {
179
                    $aClassOptions['include'] = $xFile->getPathname();
180
                }
181
                $this->xRepository->addClass($sClassName, $aClassOptions, $aOptions);
182
            }
183
        }
184
    }
185
186
    /**
187
     * Read classes from registered directories (with namespaces)
188
     *
189
     * @return void
190
     */
191
    public function parseNamespaces()
192
    {
193
        // Browse directories with namespaces and read all the files.
194
        // This is to be done only once.
195
        if($this->bParsedNamespaces)
196
        {
197
            return;
198
        }
199
        $this->bParsedNamespaces = true;
200
201
        $sDS = DIRECTORY_SEPARATOR;
202
        foreach($this->aNamespaces as $sNamespace => $aOptions)
203
        {
204
            $this->xRepository->addNamespace($sNamespace, ['separator' => $aOptions['separator']]);
205
206
            // Iterate on dir content
207
            $sDirectory = $aOptions['directory'];
208
            $itFile = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($sDirectory));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
209
            foreach($itFile as $xFile)
210
            {
211
                // skip everything except PHP files
212
                if(!$xFile->isFile() || $xFile->getExtension() != 'php')
213
                {
214
                    continue;
215
                }
216
217
                // Find the class path (the same as the class namespace)
218
                $sClassPath = $sNamespace;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
219
                $sRelativePath = substr($xFile->getPath(), strlen($sDirectory));
220
                $sRelativePath = trim(str_replace($sDS, '\\', $sRelativePath), '\\');
221
                if($sRelativePath != '')
222
                {
223
                    $sClassPath .= '\\' . $sRelativePath;
224
                }
225
226
                $this->xRepository->addNamespace($sClassPath, ['separator' => $aOptions['separator']]);
227
228
                $sClassName = $sClassPath . '\\' . $xFile->getBasename('.php');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
229
                $aClassOptions = ['namespace' => $sNamespace];
230
                $this->xRepository->addClass($sClassName, $aClassOptions, $aOptions);
231
            }
232
        }
233
    }
234
235
    /**
236
     * Find options for a class which is registered with namespace
237
     *
238
     * @param string        $sClassName            The class name
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 12 found
Loading history...
239
     *
240
     * @return array|null
241
     */
242
    protected function getClassOptionsFromNamespaces($sClassName)
243
    {
244
        // Find the corresponding namespace
245
        $sNamespace = null;
246
        foreach(array_keys($this->aNamespaces) as $_sNamespace)
247
        {
248
            if(substr($sClassName, 0, strlen($_sNamespace) + 1) == $_sNamespace . '\\')
249
            {
250
                $sNamespace = $_sNamespace;
251
                break;
252
            }
253
        }
254
        if($sNamespace === null)
255
        {
256
            return null; // Class not registered
257
        }
258
259
        // Get the class options
260
        $aOptions = $this->aNamespaces[$sNamespace];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
261
        $aClassOptions = ['namespace' => $sNamespace];
262
        return $this->xRepository->makeClassOptions($sClassName, $aClassOptions, $aOptions);
263
    }
264
265
    /**
266
     * Find the options associated with a registered class name
267
     *
268
     * @param string        $sClassName            The class name
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 12 found
Loading history...
269
     *
270
     * @return array|null
271
     */
272
    protected function getClassOptions($sClassName)
273
    {
274
        // Find options for a class registered with namespace.
275
        $aOptions = $this->getClassOptionsFromNamespaces($sClassName);
276
        if($aOptions !== null)
277
        {
278
            return $aOptions;
279
        }
280
281
        // Without a namespace, we need to parse all classes to be able to find one.
282
        $this->parseDirectories();
283
284
        // Find options for a class registered without namespace.
285
        return $this->xRepository->getClassOptions($sClassName);
286
    }
287
288
    /**
289
     * Find a callable object by class name
290
     *
291
     * @param string        $sClassName            The class name of the callable object
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter type; 8 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 12 found
Loading history...
292
     *
293
     * @return object
294
     */
295
    public function getCallableObject($sClassName)
296
    {
297
        // Replace all separators ('.' and '_') with antislashes, and remove the antislashes
298
        // at the beginning and the end of the class name.
299
        $sClassName = (string)$sClassName;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $sClassName. This often makes code more readable.
Loading history...
300
        $sClassName = trim(str_replace('.', '\\', $sClassName), '\\');
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $sClassName. This often makes code more readable.
Loading history...
301
        if($this->bUsingUnderscore)
302
        {
303
            $sClassName = trim(str_replace('_', '\\', $sClassName), '\\');
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $sClassName. This often makes code more readable.
Loading history...
304
        }
305
306
        // Check if the callable object was already created.
307
        $aCallableObjects = $this->xRepository->getCallableObjects();
308
        if(key_exists($sClassName, $aCallableObjects))
309
        {
310
            return $aCallableObjects[$sClassName];
311
        }
312
313
        $aOptions = $this->getClassOptions($sClassName);
314
        if($aOptions === null)
315
        {
316
            return null;
317
        }
318
319
        return $this->xRepository->createCallableObject($sClassName, $aOptions);
320
    }
321
}
322