CallableClassPlugin::__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
 * CallableClassPlugin.php - Jaxon callable class plugin
5
 *
6
 * This class registers user defined callable classes, and calls their methods on user request.
7
 *
8
 * @package jaxon-core
9
 * @author Jared White
10
 * @author J. Max Wilson
11
 * @author Joseph Woolley
12
 * @author Steffen Konerow
13
 * @author Thierry Feuzeu <[email protected]>
14
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
15
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
16
 * @copyright 2016 Thierry Feuzeu <[email protected]>
17
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
18
 * @link https://github.com/jaxon-php/jaxon-core
19
 */
20
21
namespace Jaxon\Plugin\Request\CallableClass;
22
23
use Jaxon\Jaxon;
24
use Jaxon\App\I18n\Translator;
25
use Jaxon\Di\ClassContainer;
26
use Jaxon\Di\Container;
27
use Jaxon\Exception\RequestException;
28
use Jaxon\Exception\SetupException;
29
use Jaxon\Plugin\AbstractRequestPlugin;
30
use Jaxon\Request\Target;
31
use Jaxon\Request\Validator;
32
use Jaxon\Response\AbstractResponse;
33
use Jaxon\Utils\Template\TemplateEngine;
34
use Psr\Http\Message\ServerRequestInterface;
35
use Exception;
36
use ReflectionException;
37
38
use function array_reduce;
39
use function is_array;
40
use function is_string;
41
use function md5;
42
use function str_replace;
43
use function strpos;
44
use function strlen;
45
use function substr;
46
use function trim;
47
use function uksort;
48
49
class CallableClassPlugin extends AbstractRequestPlugin
50
{
51
    /**
52
     * The class constructor
53
     *
54
     * @param string $sPrefix
55
     * @param bool $bDebug
56
     * @param Container $di
57
     * @param ClassContainer $cls
58
     * @param CallableRegistry $xRegistry
59
     * @param TemplateEngine $xTemplateEngine
60
     * @param Translator $xTranslator
61
     * @param Validator $xValidator
62
     */
63
    public function __construct(private string $sPrefix, private bool $bDebug,
64
        private Container $di, private ClassContainer $cls,
65
        private CallableRegistry $xRegistry, private TemplateEngine $xTemplateEngine,
66
        private Translator $xTranslator, private Validator $xValidator)
67
    {}
68
69
    /**
70
     * @inheritDoc
71
     */
72
    public function getName(): string
73
    {
74
        return Jaxon::CALLABLE_CLASS;
75
    }
76
77
    /**
78
     * @inheritDoc
79
     * @throws SetupException
80
     */
81
    public function checkOptions(string $sCallable, $xOptions): array
82
    {
83
        if(!$this->xValidator->validateClass(trim($sCallable)))
84
        {
85
            throw new SetupException($this->xTranslator->trans('errors.objects.invalid-declaration'));
86
        }
87
        if(is_string($xOptions))
88
        {
89
            $xOptions = ['include' => $xOptions];
90
        }
91
        elseif(!is_array($xOptions))
92
        {
93
            throw new SetupException($this->xTranslator->trans('errors.objects.invalid-declaration'));
94
        }
95
        return $xOptions;
96
    }
97
98
    /**
99
     * @inheritDoc
100
     */
101
    public function register(string $sType, string $sCallable, array $aOptions): bool
102
    {
103
        $sClassName = trim($sCallable);
104
        $this->xRegistry->registerClass($sClassName, $aOptions);
105
        return true;
106
    }
107
108
    /**
109
     * @inheritDoc
110
     * @throws SetupException
111
     */
112
    public function getCallable(string $sCallable)
113
    {
114
        return $this->cls->makeCallableObject($sCallable);
115
    }
116
117
    /**
118
     * @inheritDoc
119
     */
120
    public function getHash(): string
121
    {
122
        $this->xRegistry->parseCallableClasses();
123
        return md5($this->xRegistry->getHash());
124
    }
125
126
    /**
127
     * Generate client side javascript code for namespaces
128
     *
129
     * @return string
130
     */
131
    private function getNamespacesScript(): string
132
    {
133
        $sCode = '';
134
        $aJsClasses = [];
135
        foreach($this->xRegistry->getNamespaces() as $sNamespace)
136
        {
137
            $offset = 0;
138
            $sJsNamespace = str_replace('\\', '.', $sNamespace);
139
            $sJsNamespace .= '.Null'; // This is a sentinel. The last token is not processed in the while loop.
140
            while(($dotPosition = strpos($sJsNamespace, '.', $offset)) !== false)
141
            {
142
                $sJsClass = substr($sJsNamespace, 0, $dotPosition);
143
                // Generate code for this object
144
                if(!isset($aJsClasses[$sJsClass]))
145
                {
146
                    $sCode .= $this->sPrefix . "$sJsClass = {};\n";
147
                    $aJsClasses[$sJsClass] = $sJsClass;
148
                }
149
                $offset = $dotPosition + 1;
150
            }
151
        }
152
        return $sCode;
153
    }
154
155
    /**
156
     * Generate client side javascript code for a callable class
157
     *
158
     * @param CallableObject $xCallableObject The corresponding callable object
159
     *
160
     * @return string
161
     */
162
    private function getCallableScript(CallableObject $xCallableObject): string
163
    {
164
        if($xCallableObject->excluded())
165
        {
166
            return '';
167
        }
168
169
        return $this->xTemplateEngine->render('jaxon::callables/object.js', [
170
            'sPrefix' => $this->sPrefix,
171
            'sClass' => $xCallableObject->getJsName(),
172
            'aMethods' => $xCallableObject->getCallableMethods(),
173
        ]);
174
    }
175
176
    /**
177
     * Generate client side javascript code for the registered callable objects
178
     *
179
     * @return string
180
     * @throws SetupException
181
     */
182
    public function getScript(): string
183
    {
184
        $this->xRegistry->parseCallableClasses();
185
        $aCallableObjects = $this->cls->getCallableObjects();
186
        // Sort the options by key length asc
187
        uksort($aCallableObjects, function($name1, $name2) {
188
            return strlen($name1) - strlen($name2);
189
        });
190
191
        return array_reduce($aCallableObjects, function($sCode, $xCallableObject) {
192
            return $sCode . $this->getCallableScript($xCallableObject);
193
        }, $this->getNamespacesScript());
194
    }
195
196
    /**
197
     * @inheritDoc
198
     */
199
    public static function canProcessRequest(ServerRequestInterface $xRequest): bool
200
    {
201
        $aCall = $xRequest->getAttribute('jxncall');
202
        return $aCall !== null && ($aCall['type'] ?? '') === 'class' &&
203
            isset($aCall['name']) && isset($aCall['method']);
204
    }
205
206
    /**
207
     * @inheritDoc
208
     */
209
    public function setTarget(ServerRequestInterface $xRequest): Target
210
    {
211
        $aCall = $xRequest->getAttribute('jxncall');
212
        $this->xTarget = Target::makeClass(trim($aCall['name']), trim($aCall['method']));
213
        return $this->xTarget;
214
    }
215
216
    /**
217
     * @param Exception $xException
218
     * @param string $sErrorMessage
219
     *
220
     * @throws RequestException
221
     * @return void
222
     */
223
    private function throwException(Exception $xException, string $sErrorMessage): void
224
    {
225
        $this->di->getLogger()->error($xException->getMessage());
226
        throw new RequestException($sErrorMessage . (!$this->bDebug ? '' :
227
            "\n" . $xException->getMessage()));
228
    }
229
230
    /**
231
     * @inheritDoc
232
     * @throws RequestException
233
     */
234
    public function processRequest(): ?AbstractResponse
235
    {
236
        $sClassName = $this->xTarget->getClassName();
237
        $sMethodName = $this->xTarget->getMethodName();
238
239
        if(!$this->xValidator->validateClass($sClassName) ||
240
            !$this->xValidator->validateMethod($sMethodName))
241
        {
242
            // Unable to find the requested object or method
243
            throw new RequestException($this->xTranslator->trans('errors.objects.invalid',
244
                ['class' => $sClassName, 'method' => $sMethodName]));
245
        }
246
247
        // Call the requested method
248
        try
249
        {
250
            /** @var CallableObject */
251
            $xCallableObject = $this->getCallable($sClassName);
252
        }
253
        catch(ReflectionException|SetupException $e)
254
        {
255
            // Unable to find the requested class or method
256
            $this->throwException($e, $this->xTranslator->trans('errors.objects.invalid',
257
                ['class' => $sClassName, 'method' => $sMethodName]));
258
        }
259
        try
260
        {
261
            return $xCallableObject->call($this->xTarget);
262
        }
263
        catch(ReflectionException|SetupException $e)
264
        {
265
            // Unable to execute the requested class or method
266
            $this->throwException($e, $this->xTranslator->trans('errors.objects.call',
267
                ['class' => $sClassName, 'method' => $sMethodName]));
268
        }
269
    }
270
}
271