Completed
Push — master ( 1cd1d8...dbc4d9 )
by Thierry
02:53
created

CallableClass::processRequest()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CallableClass.php - Jaxon callable class plugin
5
 *
6
 * This class registers user defined callable classes, generates client side javascript code,
7
 * and calls their methods on user request
8
 *
9
 * @package jaxon-core
10
 * @author Jared White
11
 * @author J. Max Wilson
12
 * @author Joseph Woolley
13
 * @author Steffen Konerow
14
 * @author Thierry Feuzeu <[email protected]>
15
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
16
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
17
 * @copyright 2016 Thierry Feuzeu <[email protected]>
18
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
19
 * @link https://github.com/jaxon-php/jaxon-core
20
 */
21
22
namespace Jaxon\Request\Plugin;
23
24
use Jaxon\Jaxon;
25
use Jaxon\Plugin\Request as RequestPlugin;
26
27
class CallableClass extends RequestPlugin
28
{
29
    use \Jaxon\Utils\Traits\Config;
30
    use \Jaxon\Utils\Traits\Manager;
31
    use \Jaxon\Utils\Traits\Validator;
32
    use \Jaxon\Utils\Traits\Translator;
33
34
    /**
35
     * The registered callable objects
36
     *
37
     * @var array
38
     */
39
    protected $aCallableClasses = [];
40
41
    /**
42
     * The classpaths of the registered callable objects
43
     *
44
     * @var array
45
     */
46
    protected $aClassPaths = [];
47
48
    /**
49
     * The value of the class parameter of the incoming Jaxon request
50
     *
51
     * @var string
52
     */
53
    protected $sRequestedClass = null;
54
55
    /**
56
     * The value of the method parameter of the incoming Jaxon request
57
     *
58
     * @var string
59
     */
60
    protected $sRequestedMethod = null;
61
62
    public function __construct()
63
    {
64
        if(!empty($_GET['jxncls']))
65
        {
66
            $this->sRequestedClass = $_GET['jxncls'];
67
        }
68
        if(!empty($_GET['jxnmthd']))
69
        {
70
            $this->sRequestedMethod = $_GET['jxnmthd'];
71
        }
72
        if(!empty($_POST['jxncls']))
73
        {
74
            $this->sRequestedClass = $_POST['jxncls'];
75
        }
76
        if(!empty($_POST['jxnmthd']))
77
        {
78
            $this->sRequestedMethod = $_POST['jxnmthd'];
79
        }
80
    }
81
82
    /**
83
     * Return the name of this plugin
84
     *
85
     * @return string
86
     */
87
    public function getName()
88
    {
89
        return Jaxon::CALLABLE_CLASS;
90
    }
91
92
    /**
93
     * Register a callable class
94
     *
95
     * @param string        $sType          The type of request handler being registered
96
     * @param string        $sClassName     The name of the class being registered
97
     * @param array|string  $aOptions       The associated options
98
     *
99
     * @return boolean
100
     */
101
    public function register($sType, $sClassName, $aOptions)
102
    {
103
        if($sType != $this->getName())
104
        {
105
            return false;
106
        }
107
108
        if(!is_string($sClassName) || !class_exists($sClassName))
109
        {
110
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid-declaration'));
111
        }
112
        $sClassName = trim($sClassName, '\\');
113
        $this->aCallableClasses[] = $sClassName;
114
115
        if(!is_array($aOptions))
116
        {
117
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid-declaration'));
118
        }
119
120
        // Save the classpath and the separator in this class
121
        if(key_exists('*', $aOptions) && is_array($aOptions['*']))
122
        {
123
            $_aOptions = $aOptions['*'];
124
            $sSeparator = '.';
125
            if(key_exists('separator', $_aOptions))
126
            {
127
                $sSeparator = trim($_aOptions['separator']);
128
            }
129
            if(!in_array($sSeparator, ['.', '_']))
130
            {
131
                $sSeparator = '.';
132
            }
133
            $_aOptions['separator'] = $sSeparator;
134
135
            if(array_key_exists('classpath', $_aOptions))
136
            {
137
                $_aOptions['classpath'] = trim($_aOptions['classpath'], ' \\._');
138
                // Save classpath with "\" in the parameters
139
                $_aOptions['classpath'] = str_replace(['.', '_'], ['\\', '\\'], $_aOptions['classpath']);
140
                // Save classpath with separator locally
141
                $this->aClassPaths[] = str_replace('\\', $sSeparator, $_aOptions['classpath']);
142
            }
143
        }
144
145
        // Register the callable object
146
        jaxon()->di()->set($sClassName, function () use ($sClassName, $aOptions) {
147
            $xCallableObject = new \Jaxon\Request\Support\CallableObject($sClassName);
148
149
            foreach($aOptions as $sMethod => $aValue)
150
            {
151
                foreach($aValue as $sName => $sValue)
152
                {
153
                    $xCallableObject->configure($sMethod, $sName, $sValue);
154
                }
155
            }
156
157
            return $xCallableObject;
158
        });
159
160
        // Register the request factory for this callable object
161
        jaxon()->di()->set($sClassName . '\Factory\Rq', function ($di) use ($sClassName) {
162
            $xCallableObject = $di->get($sClassName);
163
            return new \Jaxon\Factory\Request\Portable($xCallableObject);
164
        });
165
166
        // Register the paginator factory for this callable object
167
        jaxon()->di()->set($sClassName . '\Factory\Pg', function ($di) use ($sClassName) {
168
            $xCallableObject = $di->get($sClassName);
169
            return new \Jaxon\Factory\Request\Paginator($xCallableObject);
170
        });
171
172
        return true;
173
    }
174
175
    /**
176
     * Generate a hash for the registered callable objects
177
     *
178
     * @return string
179
     */
180
    public function generateHash()
181
    {
182
        $di = jaxon()->di();
183
        $sHash = '';
184
        foreach($this->aCallableClasses as $sName)
185
        {
186
            $xCallableObject = $di->get($sName);
187
            $sHash .= $sName . implode('|', $xCallableObject->getMethods());
188
        }
189
        return md5($sHash);
190
    }
191
192
    /**
193
     * Generate client side javascript code for the registered callable objects
194
     *
195
     * @return string
196
     */
197
    public function getScript()
198
    {
199
        $code = '';
200
201
        // Generate code for javascript objects declaration
202
        $sJaxonPrefix = $this->getOption('core.prefix.class');
203
        $classes = [];
204
        foreach($this->aClassPaths as $sClassPath)
205
        {
206
            $offset = 0;
207
            $sClassPath .= '.Null'; // This is a sentinel. The last token is not processed in the while loop.
208
            while(($dotPosition = strpos($sClassPath, '.', $offset)) !== false)
209
            {
210
                $class = substr($sClassPath, 0, $dotPosition);
211
                // Generate code for this object
212
                if(!key_exists($class, $classes))
213
                {
214
                    $code .= "$sJaxonPrefix$class = {};\n";
215
                    $classes[$class] = $class;
216
                }
217
                $offset = $dotPosition + 1;
218
            }
219
        }
220
221
        // Generate code for javascript methods
222
        $di = jaxon()->di();
223
        foreach($this->aCallableClasses as $sName)
224
        {
225
            $xCallableObject = $di->get($sName);
226
            $code .= $xCallableObject->getScript();
227
        }
228
        return $code;
229
    }
230
231
    /**
232
     * Check if this plugin can process the incoming Jaxon request
233
     *
234
     * @return boolean
235
     */
236
    public function canProcessRequest()
237
    {
238
        // Check the validity of the class name
239
        if(($this->sRequestedClass) && !$this->validateClass($this->sRequestedClass))
240
        {
241
            $this->sRequestedClass = null;
242
            $this->sRequestedMethod = null;
243
        }
244
        // Check the validity of the method name
245
        if(($this->sRequestedMethod) && !$this->validateMethod($this->sRequestedMethod))
246
        {
247
            $this->sRequestedClass = null;
248
            $this->sRequestedMethod = null;
249
        }
250
        return ($this->sRequestedClass != null && $this->sRequestedMethod != null);
251
    }
252
253
    /**
254
     * Process the incoming Jaxon request
255
     *
256
     * @return boolean
257
     */
258
    public function processRequest()
259
    {
260
        if(!$this->canProcessRequest())
261
        {
262
            return false;
263
        }
264
265
        $aArgs = $this->getRequestManager()->process();
266
267
        // Find the requested method
268
        $xCallableObject = $this->getCallableObject($this->sRequestedClass);
269
        if(!$xCallableObject || !$xCallableObject->hasMethod($this->sRequestedMethod))
270
        {
271
            // Unable to find the requested object or method
272
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid',
273
                ['class' => $this->sRequestedClass, 'method' => $this->sRequestedMethod]));
274
        }
275
276
        // Call the requested method
277
        $xCallableObject->call($this->sRequestedMethod, $aArgs);
278
        return true;
279
    }
280
281
    /**
282
     * Find a callable object by class name
283
     *
284
     * @param string        $sClassName            The class name of the callable object
285
     *
286
     * @return object
287
     */
288
    public function getCallableObject($sClassName)
289
    {
290
        // Replace all separators ('.' and '_') with antislashes, and remove the antislashes
291
        // at the beginning and the end of the class name.
292
        $sClassName = trim(str_replace(['.', '_'], ['\\', '\\'], (string)$sClassName), '\\');
293
        // Register an instance of the requested class, if it isn't yet
294
        if(!key_exists($sClassName, $this->aCallableClasses))
295
        {
296
            $this->getPluginManager()->registerClass($sClassName);
297
        }
298
        return key_exists($sClassName, $this->aCallableClasses) ? jaxon()->di()->get($sClassName) : null;
299
    }
300
301
    /**
302
     * Find a user registered callable object by class name
303
     *
304
     * @param string        $sClassName            The class name of the callable object
305
     *
306
     * @return object
307
     */
308
    public function getRegisteredObject($sClassName)
309
    {
310
        // Get the corresponding callable object
311
        $xCallableObject = $this->getCallableObject($sClassName);
312
        return ($xCallableObject) ? $xCallableObject->getRegisteredObject() : null;
313
    }
314
}
315