Passed
Push — dev ( 4ef148...eba0dc )
by Dispositif
07:17
created

AbstractWikiTemplate   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 476
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 157
c 5
b 0
f 0
dl 0
loc 476
rs 3.04
wmc 67

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 3
A setParamOrderByUser() 0 18 4
A toArray() 0 5 1
A setParam() 0 17 4
A isParamOrAlias() 0 3 1
A getParamsAndAlias() 0 3 1
A detectUserSeparator() 0 3 1
B paramsByRenderOrder() 0 28 7
B mergeWrongParametersFromUser() 0 31 7
A hydrateFromText() 0 8 2
B serialize() 0 41 8
A checkParamName() 0 17 4
A hydrate() 0 11 3
A filterEmptyNotRequired() 0 11 4
A getAliasParam() 0 7 2
A unsetParam() 0 5 1
A hasParamValue() 0 11 3
A __get() 0 10 2
B hydrateTemplateParameter() 0 46 7
A getParam() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractWikiTemplate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractWikiTemplate, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of dispositif/wikibot application
4
 * 2019 : Philippe M. <[email protected]>
5
 * For the full copyright and MIT license information, please view the LICENSE file.
6
 */
7
8
declare(strict_types=1);
9
10
namespace App\Domain\Models\Wiki;
11
12
use App\Domain\Utils\ArrayProcessTrait;
13
use App\Domain\Utils\TemplateParser;
14
use App\Domain\Utils\WikiTextUtil;
15
use DomainException;
16
use Exception;
17
use Throwable;
18
19
/**
20
 * TODO detect userPreferences (inlineStyle, spaceStyle...)
21
 * Class AbstractWikiTemplate.
22
 */
23
abstract class AbstractWikiTemplate extends AbstractParametersObject
24
{
25
    use ArrayProcessTrait;
26
27
    const MODEL_NAME = '';
28
29
    // commented to allow inherit from Interface in OuvrageTemplate
30
    //const PARAM_ALIAS = [];
31
32
    /**
33
     * todo : modify to [a,b,c] ?
34
     */
35
    const REQUIRED_PARAMETERS = [];
36
37
    public $log = [];
38
39
    public $parametersErrorFromHydrate;
40
41
    public $userSeparator; // todo move to WikiRef
42
43
    /**
44
     * optional
45
     * Not a constant so it can be modified in constructor.
46
     * Commented so it can be inherit from trait in OuvrageTemplate
47
     */
48
    //protected $parametersByOrder = [];
49
50
    protected $paramOrderByUser = [];
51
52
    /**
53
     * AbstractWikiTemplate constructor.
54
     *
55
     * @throws Exception
56
     */
57
    public function __construct()
58
    {
59
        if (empty(static::REQUIRED_PARAMETERS)) {
1 ignored issue
show
introduced by
The condition empty(static::REQUIRED_PARAMETERS) is always true.
Loading history...
60
            throw new Exception(sprintf('REQUIRED_PARAMETERS not configured in "%s"', get_called_class()));
61
        }
62
        $this->parametersValues = static::REQUIRED_PARAMETERS;
63
64
        if (empty($this->parametersByOrder)) {
65
            $this->parametersByOrder = static::REQUIRED_PARAMETERS;
66
        }
67
    }
68
69
    /**
70
     * Get data from wiki-template. Also invalid param/values.
71
     *
72
     * @return array
73
     */
74
    public function toArray(): array
75
    {
76
        $allValue = array_merge($this->parametersValues, $this->parametersErrorFromHydrate ?? []);
77
78
        return $this->deleteEmptyValueArray($allValue);
79
    }
80
81
    /**
82
     * Is the parameter's name valid ?
83
     *
84
     * @param string $paramName
85
     *
86
     * @return bool
87
     */
88
    public function isParamOrAlias(string $paramName): bool
89
    {
90
        return in_array($paramName, $this->getParamsAndAlias());
91
    }
92
93
    public function getParamsAndAlias(): array
94
    {
95
        return array_merge($this->parametersByOrder, array_keys($this::PARAM_ALIAS));
1 ignored issue
show
Bug introduced by
The constant App\Domain\Models\Wiki\A...kiTemplate::PARAM_ALIAS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
96
    }
97
98
    /**
99
     * TODO check if method set{ParamName} exists.
100
     *
101
     * @param string $name
102
     * @param string $value
103
     *
104
     * @return AbstractParametersObject
105
     * @throws Exception
106
     */
107
    public function setParam(string $name, string $value): AbstractParametersObject
108
    {
109
        try {
110
            $this->checkParamName($name);
111
        } catch (Throwable $e) {
112
            $this->log[] = sprintf('no parameter "%s" in AbstractParametersObject "%s"', $name, get_called_class());
113
114
            return $this;
115
        }
116
117
        $name = $this->getAliasParam($name);
118
        $value = trim($value);
119
        if (!empty($value) || $this->parametersValues[$name]) {
120
            $this->parametersValues[$name] = $value;
121
        }
122
123
        return $this;
124
    }
125
126
    /**
127
     * TODO return bool + log() ?
128
     * todo check keyNum <= count($parametersByOrder).
129
     *
130
     * @param $name string|int
131
     *
132
     * @throws Exception
133
     */
134
    protected function checkParamName($name): void
135
    {
136
        // todo verify/useless ?
137
        if (is_int($name)) {
138
            $name = (string)$name;
139
        }
140
141
        // that parameter exists in template ?
142
        if (in_array($name, $this->parametersByOrder)
143
            || array_key_exists($name, static::PARAM_ALIAS)
1 ignored issue
show
Bug introduced by
The constant App\Domain\Models\Wiki\A...kiTemplate::PARAM_ALIAS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
144
        ) {
145
            return;
146
        }
147
148
        // keyNum parameter ?
149
        //        if (!in_array($name, ['1', '2', '3', '4'])) {
150
        throw new Exception(sprintf('no parameter "%s" in template "%s"', $name, get_called_class()));
151
    }
152
153
    /**
154
     * @param string $name
155
     *
156
     * @return string
157
     */
158
    public function getAliasParam(string $name): string
159
    {
160
        if (array_key_exists($name, static::PARAM_ALIAS)) {
1 ignored issue
show
Bug introduced by
The constant App\Domain\Models\Wiki\A...kiTemplate::PARAM_ALIAS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
161
            $name = static::PARAM_ALIAS[$name];
162
        }
163
164
        return $name;
165
    }
166
167
    /**
168
     * @param $param
169
     *
170
     * @return string|null
171
     * @throws Exception
172
     */
173
    public function __get($param): ?string
174
    {
175
        $this->checkParamName($param);
176
177
        if (!empty($this->parametersValues[$param])) {
178
            return $this->parametersValues[$param];
179
        }
180
181
        // todo param_alias ?
182
        return null;
183
    }
184
185
    public function unsetParam(string $name): void
186
    {
187
        $this->checkParamName($name);
188
        $name = $this->getAliasParam($name);
189
        unset($this->parametersValues[$name]);
190
    }
191
192
    /**
193
     * TODO move/refac.
194
     *
195
     * @param string $tplText
196
     *
197
     * @throws Exception
198
     */
199
    public function hydrateFromText(string $tplText)
200
    {
201
        if (WikiTextUtil::isCommented($tplText)) {
202
            throw new DomainException('HTML comment tag detected');
203
        }
204
        $data = TemplateParser::parseDataFromTemplate($this::MODEL_NAME, $tplText);
205
        $this->detectUserSeparator($tplText);
206
        $this->hydrate($data);
207
    }
208
209
    public function detectUserSeparator($text): void
210
    {
211
        $this->userSeparator = TemplateParser::findUserStyleSeparator($text);
212
    }
213
214
    /**
215
     * @param array $data
216
     *
217
     * @return AbstractWikiTemplate
218
     * @throws Exception
219
     */
220
    public function hydrate(array $data): self
221
    {
222
        foreach ($data as $name => $value) {
223
            if (is_string($value)) {
224
                $this->hydrateTemplateParameter($name, $value);
225
            }
226
        }
227
228
        $this->setParamOrderByUser($data);
229
230
        return $this;
231
    }
232
233
    /**
234
     * @param        $name  string|int
235
     * @param string $value
236
     *
237
     * @throws Exception
238
     */
239
    protected function hydrateTemplateParameter($name, string $value): void
240
    {
241
        // Gestion alias
242
        try {
243
            $this->checkParamName($name);
244
            $name = $this->getAliasParam($name); // main parameter name
245
246
            // Gestion des doublons de paramètres
247
            if ($this->hasParamValue($name)) {
248
                if (!empty($value)) {
249
                    $this->log[] = "parameter $name en doublon";
250
                    $this->parametersErrorFromHydrate[$name.'-doublon'] = $value;
251
                }
252
253
                return;
254
            }
255
        } catch (Throwable $e) {
256
            unset($e);
257
            // hack : 1 => "ouvrage collectif"
258
            $name = (string)$name;
259
            $this->log[] = "parameter $name not found";
260
            $this->parametersErrorFromHydrate[$name] = $value;
261
262
            return;
263
        }
264
265
266
        if (empty($value)) {
267
            // optional parameter
268
            if (!isset(static::REQUIRED_PARAMETERS[$name])) {
269
                unset($this->parametersValues[$name]);
270
271
                return;
272
            }
273
            // required parameter
274
            $this->parametersValues[$name] = '';
275
        }
276
277
        $method = $this->setterMethodName($name);
278
        if (method_exists($this, $method)) {
279
            $this->$method($value);
280
281
            return;
282
        }
283
284
        $this->parametersValues[$name] = $value;
285
    }
286
287
    /**
288
     * @param string $name
289
     *
290
     * @return string|null
291
     * @throws Exception
292
     */
293
    public function getParam(string $name): ?string
294
    {
295
        try {
296
            $this->checkParamName($name);
297
        } catch (Exception $e) {
298
            return null;
299
        }
300
        $name = $this->getAliasParam($name);
301
302
        return ($this->parametersValues[$name]) ?? null;
303
    }
304
305
    /**
306
     * Define the serialize order of parameters (from user initial choice).
307
     * default : $params = ['param1'=>'', 'param2' => '', ...]
308
     * OK with $params = ['a','b','c'].
309
     *
310
     * @param array
311
     *
312
     * @throws Exception
313
     */
314
    public function setParamOrderByUser(array $params = []): void
315
    {
316
        $validParams = [];
317
        foreach ($params as $key => $value) {
318
            $name = (is_int($key)) ? $value : $key;
319
320
            try {
321
                $this->checkParamName($name);
322
                $name = $this->getAliasParam($name);
323
                $validParams[] = $name;
324
            } catch (Throwable $e) {
325
                unset($e);
326
                $this->log[] = "Parameter $name do not exists";
327
328
                continue;
329
            }
330
        }
331
        $this->paramOrderByUser = $validParams;
332
    }
333
334
    /**
335
     * For a parameter, check is the value exists (not empty).
336
     *
337
     * @param string $name
338
     *
339
     * @return bool
340
     */
341
    public function hasParamValue(string $name): bool
342
    {
343
        try {
344
            if (!empty(trim($this->getParam($name)))) {
345
                return true;
346
            }
347
        } catch (\Throwable $e) {
348
            unset($e);
349
        }
350
351
        return false;
352
    }
353
354
    /**
355
     * TODO : data transfer object (DTO) to mix userErrorParam data ?
356
     * TODO : refac $inlineStyle as $userPreferences[] and bool flag on serialize().
357
     *
358
     * @param bool|null $cleanOrder
359
     *
360
     * @return string
361
     */
362
    public function serialize(?bool $cleanOrder = false): string
363
    {
364
        $paramsByRenderOrder = $this->paramsByRenderOrder($cleanOrder);
365
        $paramsByRenderOrder = $this->filterEmptyNotRequired($paramsByRenderOrder);
366
367
        // max caractères des paramètres (valides)
368
        $maxChars = 0;
369
        foreach (array_keys($paramsByRenderOrder) as $paramName) {
370
            $maxChars = max($maxChars, mb_strlen($paramName));
371
        }
372
373
        // TODO : $option to add or not the wrong parameters ?
374
        // Using the wrong parameters+value from user input ?
375
        $paramsByRenderOrder = $this->mergeWrongParametersFromUser($paramsByRenderOrder);
376
377
        $string = '{{'.static::MODEL_NAME;
378
        foreach ($paramsByRenderOrder as $paramName => $paramValue) {
379
            $string .= ($this->userSeparator) ?? '|';
380
381
            if (!in_array($paramName, ['0', '1', '2', '3', '4', '5'])) {
382
                $string .= $paramName;
383
                // espacements multiples pour style étendu : "auteur    = Bla"
384
                if ($this->userSeparator && false !== strpos($this->userSeparator, "\n")) {
385
                    $spaceNb = max(0, $maxChars - mb_strlen($paramName));
386
                    $string .= str_repeat(' ', $spaceNb);
387
                    $string .= ' = ';
388
                } else {
389
                    // style condensé "auteur=Bla"
390
                    $string .= '=';
391
                }
392
            }
393
            // {{template|1=blabla}} -> {{template|blabla}}
394
            $string .= $paramValue;
395
        }
396
        // expanded model -> "\n}}"
397
        if ($this->userSeparator && false !== strpos($this->userSeparator, "\n")) {
398
            $string .= "\n";
399
        }
400
        $string .= '}}';
401
402
        return $string;
403
    }
404
405
    /**
406
     * @param bool|null $cleanOrder
407
     *
408
     * @return array
409
     */
410
    protected function paramsByRenderOrder(?bool $cleanOrder = false): array
411
    {
412
        $renderParams = [];
413
414
        // By user order
415
        if (!empty($this->paramOrderByUser) && !$cleanOrder) {
416
            $completeFantasyOrder = $this->completeFantasyOrder(
417
                $this->paramOrderByUser,
418
                $this->parametersByOrder
419
            );
420
421
            foreach ($completeFantasyOrder as $paramName) {
422
                if (isset($this->parametersValues[$paramName])) {
423
                    $renderParams[$paramName] = $this->parametersValues[$paramName];
424
                }
425
            }
426
427
            return $renderParams;
428
        }
429
430
        // default order
431
        foreach ($this->parametersByOrder as $order => $paramName) {
432
            if (isset($this->parametersValues[$paramName])) {
433
                $renderParams[$paramName] = $this->parametersValues[$paramName];
434
            }
435
        }
436
437
        return $renderParams;
438
    }
439
440
    /**
441
     * Delete key if empty value and the key not required.
442
     *
443
     * @param array $params
444
     *
445
     * @return array
446
     */
447
    protected function filterEmptyNotRequired(array $params): array
448
    {
449
        $render = [];
450
        foreach ($params as $name => $value) {
451
            if (empty($value) && !isset(static::REQUIRED_PARAMETERS[$name])) {
452
                continue;
453
            }
454
            $render[$name] = $params[$name];
455
        }
456
457
        return $render;
458
    }
459
460
    /**
461
     * Merge Render data with wrong parameters+value from user input.
462
     * The wrong ones already corrected are not added.
463
     *
464
     * @param array $paramsByRenderOrder
465
     *
466
     * @return array
467
     */
468
    protected function mergeWrongParametersFromUser(array $paramsByRenderOrder): array
469
    {
470
        if (!empty($this->parametersErrorFromHydrate)) {
471
            // FIXED? : si y'a de l'info dans un paramètre erreur et sans value...
472
            //$errorUserData = $this->deleteEmptyValueArray($this->parametersErrorFromHydrate);
473
            $errorUserData = $this->parametersErrorFromHydrate;
474
475
            // Add a note in HTML commentary
476
            foreach ($errorUserData as $param => $value) {
477
                if ('string' === gettype($param) && empty(trim($param))) {
478
                    continue;
479
                }
480
                if (is_int($param)) {
481
                    // erreur "|lire en ligne|"
482
                    if (in_array($value, $this->getParamsAndAlias())) {
483
                        unset($errorUserData[$param]);
484
485
                        continue;
486
                    }
487
488
                    // ou 1= 2= 3=
489
                    $errorUserData[$param] = $value.' <!--VALEUR SANS NOM DE PARAMETRE -->';
490
491
                    continue;
492
                }
493
                $errorUserData[$param] = $value." <!--PARAMETRE '$param' N'EXISTE PAS -->";
494
            }
495
            $paramsByRenderOrder = array_merge($paramsByRenderOrder, $errorUserData);
496
        }
497
498
        return $paramsByRenderOrder;
499
    }
500
}
501