Passed
Push — master ( 6b35df...b5f91c )
by Andreas
10:55
created

midcom_baseclasses_components_viewer::handle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 0
dl 0
loc 21
ccs 11
cts 11
cp 1
crap 2
rs 9.9332
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A midcom_baseclasses_components_viewer::show() 0 7 2
1
<?php
2
/**
3
 * @package midcom.baseclasses
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use midcom\routing\loader;
10
use Symfony\Component\Routing\Router;
11
use midcom\routing\resolver;
12
use midcom\routing\plugin;
13
14
/**
15
 * Base class to encapsulate the component's routing, instantiated by the MidCOM
16
 * component interface.
17
 *
18
 * It provides an automatic mechanism for URL processing and validation, minimizing
19
 * the required work to get a new component running.
20
 *
21
 * <b>Request switch configuration</b>
22
 *
23
 * The class uses an array which aids in URL-to-function mapping. Handlers are distinguished
24
 * by the "URL-space" they handle. For each handler, one function is needed for the
25
 * request handling ("Handle Phase") and optionally one for output ("Output Phase"). These handlers refer to
26
 * another class which gets instantiated if necessary.
27
 *
28
 * All request handlers are contained in a single array, whose keys identify the various switch
29
 * configurations. These identifiers are only for informational purposes (they appear in the
30
 * debug log), so you could just resort to automatic array index numbering using the []
31
 * operator.
32
 *
33
 * Each request handler definition in the switch must contain these key/value pairs:
34
 *
35
 * - <b>mixed fixed_args:</b> This is either a string or an array and defines the fixed
36
 *   arguments that have to be present at the beginning of the URL to be handled. A
37
 *   string denotes a single argument, an array is used if more than one fixed argument
38
 *   is needed. If you do not have any fixed arguments, set this parameter to null, which
39
 *   is the default.
40
 * - <b>int variable_args:</b> Usually, there are a number of variables in the URL, like
41
 *   article IDs, or article names. This can be 0, indicating that no variable arguments are
42
 *   required, which is the default. For an unlimited number of variable_args set it to -1.
43
 *
44
 * - <b>mixed handler:</b> This is a definition of what method should be invoked to
45
 *   handle the request using the callable array syntax. The first array member must contain
46
 *   the name of an existing class.
47
 *   This value has no default and must be set. The actual methods called will have either an
48
 *   _handler_ or _show_ prefix.
49
 *
50
 * Example:
51
 *
52
 * <code>
53
 * $this->_request_switch[] = [
54
 *     'fixed_args' => ['registrations', 'view'],
55
 *     'variable_args' => 1,
56
 *     'handler' => ['net_nemein_registrations_regadmin', 'view']
57
 * ];
58
 * </code>
59
 *
60
 * This definition is usually located in either in the routes.yml file (preferred)
61
 * or the _on_initialize event handler.
62
 *
63
 * The handlers are processed in the order which they have been added to the array. This has
64
 * several implications:
65
 *
66
 * First, if you have two handlers with similar signatures, the latter might be hidden by the
67
 * former, for example the handler 'view' with two variable arguments includes the urls that
68
 * could match 'view', 'registration' with a single variable argument if processed in this order.
69
 * In these cases you have to add the most specific handlers first.
70
 *
71
 * Second, for performance reasons, you should try to add the handler which will be accessed
72
 * most of the time first (unless it conflicts with the first rule above), as this will speed
73
 * up average request processing.
74
 *
75
 * It is recommended that you add string-based identifiers to your handlers. This makes
76
 * debugging of URL parsing much easier, as MidCOM logs which request handlers are checked
77
 * in debug mode. The above example could use something like
78
 * `$this->_request_switch['registrations-view']` to do so. Just never prefix one of your
79
 * handlers with one underscores, this namespace is reserved for MidCOM usage.
80
 *
81
 * <b>Callback method signatures</b>
82
 *
83
 * <code>
84
 * /**
85
 *  * Exec handler example, with Docblock:
86
 *  * @param string $handler_id The ID of the handler.
87
 *  * @param array $args The argument list.
88
 *  * @param array $data The local request data.
89
 *  {@*}
90
 * public function _handler_xxx (string $handler_id, array $args, array &$data) {}
91
 *
92
 * /**
93
 *  * Show handler example, with Docblock:
94
 *  * @param string $handler_id The ID of the handler.
95
 *  * @param array $data The local request data.
96
 *  {@*}
97
 * public function _show_xxx (string $handler_id, array &$data) {}
98
 * </code>
99
 *
100
 * The two callbacks match the regular processing sequence of MidCOM.
101
 *
102
 * The main callback _handle_xxx is mandatory, _show_xxx is optional since the handle method can
103
 * return a response directly.
104
 *
105
 * As you can see, the system provides you with an easy way to keep track of the data
106
 * of your request, without having dozens of members for trivial flags. This data array
107
 * is automatically registered in the custom component context under the name
108
 * 'request_data', making it easily available within style elements as $data
109
 *
110
 * The data array can also be accessed by using the $_request_data member of this class,
111
 * which is the original data storage location for the request data.
112
 *
113
 * Note that the request data, for ease of use, already contains the L10n
114
 * Databases of the Component and MidCOM itself located in this class. They are stored
115
 * as 'l10n' and 'l10n_midcom'. Also available as 'config' is the current component
116
 * configuration and 'topic' will hold the current content topic.
117
 *
118
 * <b>Automatic handler class instantiation</b>
119
 *
120
 * If you specify a class name instead of a class instance as an exec handler, MidCOM will
121
 * automatically create an instance of that class type and initialize it. These
122
 * so-called handler classes must be a subclass of midcom_baseclasses_components_handler.
123
 *
124
 * The subclasses you create should look about this:
125
 *
126
 * <code>
127
 * class my_handler extends midcom_baseclasses_components_handler
128
 * {
129
 *     public function _on_initialize()
130
 *     {
131
 *         // Add class initialization code here, all members have been prepared
132
 *     }
133
 * }
134
 * </code>
135
 *
136
 * The two methods for each handler have the same signature as if they were in the
137
 * same class.
138
 *
139
 * @package midcom.baseclasses
140
 */
141
class midcom_baseclasses_components_viewer
142
{
143
    use midcom_baseclasses_components_base;
0 ignored issues
show
introduced by
The trait midcom_baseclasses_components_base requires some properties which are not provided by midcom_baseclasses_components_viewer: $i18n, $head
Loading history...
144
145
    /**
146
     * @var midcom_db_topic
147
     */
148
    public $_topic;
149
150
    /**
151
     * @var midcom_helper_configuration
152
     */
153
    public $_config;
154
155
    /**
156
     * Request specific data storage area. Registered in the component context
157
     * as ''.
158
     *
159
     * @var array
160
     */
161
    public $_request_data = [];
162
163
    /**
164
     * The node toolbar for the current request context. Becomes available in the handle
165
     * phase.
166
     *
167
     * @var midcom_helper_toolbar
168
     * @see midcom_services_toolbars
169
     */
170
    public $_node_toolbar;
171
172
    /**
173
     * The view toolbar for the current request context. Becomes available in the handle
174
     * phase.
175
     *
176
     * @var midcom_helper_toolbar
177
     * @see midcom_services_toolbars
178
     */
179
    public $_view_toolbar;
180
181
    /**
182
     * @var midcom_baseclasses_components_plugin
183
     */
184
    private $active_plugin;
185
186
    /**
187
     * @var Router
188
     */
189
    protected $router;
190
191
    /**
192
     * Request execution switch configuration.
193
     *
194
     * The main request switch data. You need to set this during construction,
195
     * it will be post-processed afterwards during initialize to provide a unified
196
     * set of data. Therefore you must not modify this switch after construction.
197
     *
198
     * @var array
199
     */
200
    protected $_request_switch = [];
201
202
    /**
203
     * The handler which has been declared to be able to handle the
204
     * request. The array will contain the original index of the handler in the
205
     * '_route' member for backtracking purposes. The variable argument list will be
206
     * placed into 'args' for performance reasons.
207
     *
208
     * @var array
209
     */
210
    private $parameters;
211
212
    /**
213
     * Initializes the class, only basic variable assignment.
214
     *
215
     * Put all further initialization work into the _on_initialize event handler.
216
     */
217 351
    final public function __construct(midcom_db_topic $topic, midcom_helper_configuration $config, string $component)
218
    {
219 351
        $this->_topic = $topic;
220 351
        $this->_config = $config;
221 351
        $this->_component = $component;
222
223 351
        $this->_request_data['config'] = $this->_config;
224 351
        $this->_request_data['topic'] = null;
225 351
        $this->_request_data['l10n'] = $this->_l10n;
226 351
        $this->_request_data['l10n_midcom'] = $this->_l10n_midcom;
227
228 351
        $loader = new loader;
229 351
        $this->_request_switch = $loader->get_legacy_routes($component);
230
231 351
        $this->_on_initialize();
232 351
        $this->router = resolver::get_router($component, $this->_request_switch);
233
    }
234
235 351
    public function get_router() : Router
236
    {
237 351
        return $this->router;
238
    }
239
240
    /**
241
     * Prepares the handler callback for execution.
242
     * Set up handler fields and call the _on_handle event handler to allow for
243
     * generic request preparation.
244
     *
245
     * @see _on_handle()
246
     */
247 348
    public function prepare_handler(midcom_baseclasses_components_handler $handler, array &$parameters)
248
    {
249 348
        $this->parameters =& $parameters;
250
251 348
        $parameters['handler'] = explode('::', $parameters['_controller'], 2);
252
253 348
        midcom_core_context::get()->set_custom_key('request_data', $this->_request_data);
254
255
        //For plugins, set the component name explicitly so that L10n and config can be found
256 348
        if (!empty($this->active_plugin)) {
257 87
            $handler->_component = $this->active_plugin->_component;
258
        }
259
260 348
        $handler->initialize($this, $this->router);
261 348
        $this->parameters['handler'][0] = $handler;
262
263
        // Update the request data
264 348
        $this->_request_data['topic'] = $this->_topic;
265 348
        $this->_request_data['router'] = $this->router;
266
267
        // Get the toolbars for both the main request object and the handler object.
268 348
        $this->_node_toolbar = midcom::get()->toolbars->get_node_toolbar();
269 348
        $this->_view_toolbar = midcom::get()->toolbars->get_view_toolbar();
270 348
        $handler->_node_toolbar = $this->_node_toolbar;
271 348
        $handler->_view_toolbar = $this->_view_toolbar;
272
273
        // Add the handler ID to request data
274 348
        $this->_request_data['handler_id'] = $this->parameters['_route'];
275
276 348
        if (!array_key_exists('plugin_name', $this->_request_data)) {
277
            // We're not using a plugin handler, so call the general handle event handler
278 261
            $this->_on_handle($this->parameters['_route'], $this->parameters['args']);
279
        }
280
    }
281
282
    /**
283
     * Display the content using the matched handler.
284
     */
285 77
    public function show()
286
    {
287 77
        if (!empty($this->parameters['handler'])) {
288 77
            $handler = $this->parameters['handler'][0];
289 77
            $method = str_replace('_handler_', '_show_', $this->parameters['handler'][1]);
290
291 77
            $handler->$method($this->parameters['_route'], $this->_request_data);
292
        }
293
    }
294
295
    /**
296
     * Initialization event handler, called at the end of the initialization process
297
     *
298
     * Use this function instead of the constructor for all initialization work. You
299
     * can safely populate the request switch from here.
300
     *
301
     * You should not do anything other than general startup work, as this callback
302
     * executes <i>before</i> determining whether the component will handle the request.
303
     * Thus, anything that is specific to your request (like HTML HEAD tag adds)
304
     * must not be done here. Use _on_handle instead.
305
     */
306
    public function _on_initialize()
307
    {
308
    }
309
310
    /**
311
     * Component specific initialization code for the handle phase. The name of the request
312
     * handler is passed as an argument to the event handler.
313
     *
314
     * Note, that while you have the complete information around the request (handler id,
315
     * args and request data) available, it is strongly discouraged to handle everything
316
     * here. Instead, stay with the specific request handler methods as far as sensible.
317
     *
318
     * @param mixed $handler The ID (array key) of the handler that is responsible to handle
319
     *   the request.
320
     * @param array $args The argument list.
321
     */
322
    public function _on_handle($handler, array $args)
323
    {
324
    }
325
326
    /**
327
     * Create a new plugin namespace and map the configuration to it.
328
     * It allows flexible, user-configurable extension of components.
329
     *
330
     * Only very basic testing is done to keep runtime up, currently the system only
331
     * checks to prevent duplicate namespace registrations. In such a case,
332
     * midcom_error will be thrown.
333
     *
334
     * @param string $namespace The plugin namespace, checked against $args[0] during
335
     *     URL parsing.
336
     * @param array $config The configuration of the plugin namespace as outlined in
337
     *     the class introduction
338
     */
339 5
    public function register_plugin_namespace(string $namespace, array $config)
340
    {
341 5
        plugin::register_namespace($namespace, $config);
342
    }
343
344
    /**
345
     * Load the specified namespace/plugin combo.
346
     *
347
     * Any problem to load a plugin will be logged accordingly and false will be returned.
348
     * Critical errors will trigger midcom_error.
349
     *
350
     * @todo Allow for lazy plugin namespace configuration loading (using a callback)!
351
     *     This will make things more performant and integration with other components
352
     *     much easier.
353
     */
354 87
    public function load_plugin(string $name, midcom_baseclasses_components_plugin $plugin, array $config)
355
    {
356
        // Load the configuration into the request data, add the configured plugin name as
357
        // well so that URLs can be built.
358 87
        $this->_request_data['plugin_config'] = $config['config'] ?? null;
359 87
        $this->_request_data['plugin_name'] = $name;
360
361
        // Load remaining configuration, and prepare the plugin,
362
        // errors are logged by the callers.
363 87
        $this->router = resolver::get_router($plugin->_component);
364 87
        $plugin->initialize($this, $this->router);
365 87
        $this->active_plugin = $plugin;
366
    }
367
}
368