Completed
Push — master ( 68c045...6d06de )
by Michael
02:34
created

class/ExtcalPersistableObjectHandler.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * You may not change or alter any portion of this comment or credits
4
 * of supporting developers from this source code or any supporting source code
5
 * which is considered copyrighted (c) material of the original comment or credit authors.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 */
11
12
/**
13
 * @copyright    {@link https://xoops.org/ XOOPS Project}
14
 * @license      {@link http://www.gnu.org/licenses/gpl-2.0.html GNU GPL 2 or later}
15
 * @package      extcal
16
 * @since
17
 * @author       XOOPS Development Team,
18
 */
19
20
/**
21
 * Persistable Object Handler class.
22
 * This class is responsible for providing data access mechanisms to the data source
23
 * of derived class objects.
24
 *
25
 * @author    Jan Keller Pedersen <[email protected]> - IDG Danmark A/S <www.idg.dk>
26
 * @copyright copyright (c) 2000-2004 XOOPS.org
27
 */
28
class ExtcalPersistableObjectHandler extends XoopsPersistableObjectHandler //XoopsObjectHandler
29
{
30
    /**#@+
31
     * Information about the class, the handler is managing
32
     *
33
     * @var string
34
     */
35
    //    public $table;
36
    //    public $keyName;
37
    //    public $className;
38
    //    public $identifierName;
39
40
    /**#@-*/
41
42
    /**
43
     * Constructor - called from child classes.
44
     *
45
     * @param XoopsDatabase $db        {@link XoopsDatabase} object
46
     * @param string        $tablename Name of database table
47
     * @param string        $classname Name of Class, this handler is managing
48
     * @param string        $keyname   Name of the property, holding the key
49
     * @param bool          $idenfierName
50
     */
51
    public function __construct(XoopsDatabase $db, $tablename, $classname, $keyname, $idenfierName = false)
52
    {
53
        parent::__construct($db);
54
        $this->table     = $db->prefix($tablename);
55
        $this->keyName   = $keyname;
56
        $this->className = $classname;
57
        if (false !== $idenfierName) {
58
            $this->identifierName = $idenfierName;
59
        }
60
    }
61
62
    /**
63
     * Constructor.
64
     */
65
    //    public function ExtcalPersistableObjectHandler($db, $tablename, $classname, $keyname, $idenfierName = false)
66
    //    {
67
    //        $this->__construct($db, $tablename, $classname, $keyname, $idenfierName);
68
    //    }
69
70
    /**
71
     * create a new user.
72
     *
73
     * @param bool $isNew Flag the new objects as "new"?
74
     *
75
     * @return XoopsObject
76
     */
77
    public function create($isNew = true)
78
    {
79
        $obj = new $this->className();
80
        if (true === $isNew) {
81
            $obj->setNew();
82
        }
83
84
        return $obj;
85
    }
86
87
    /**
88
     * retrieve an object.
89
     *
90
     * @param mixed $id ID of the object - or array of ids for joint keys. Joint keys MUST be given in the same order as in the constructor
91
     * @param null  $fields
92
     * @param bool  $as_object
93
     *
94
     * @return mixed reference to the object, FALSE if failed
95
     *
96
     * @internal param bool $asObject whether to return an object or an array
97
     */
98
    public function get($id = null, $fields = null, $as_object = true) //get($id, $as_object = true)
99
    {
100
        if (is_array($this->keyName)) {
101
            $criteria = new CriteriaCompo();
102
            for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
103
                $criteria->add(new Criteria($this->keyName[$i], (int)$id[$i]));
104
            }
105
        } else {
106
            $criteria = new Criteria($this->keyName, (int)$id);
107
        }
108
        $criteria->setLimit(1);
109
        $objectArray = $this->getObjects($criteria, false, true);
110
        if (1 != count($objectArray)) {
111
            return $this->create();
112
        }
113
114
        return $objectArray[0];
115
    }
116
117
    /**
118
     * retrieve objects from the database.
119
     *
120
     * @param CriteriaElement $criteria {@link CriteriaElement} conditions to be met
121
     * @param bool            $idAsKey  use the ID as key for the array?
122
     * @param bool            $asObject return an array of objects?
123
     *
124
     * @return array
125
     */
126
    public function &getObjects(CriteriaElement $criteria = null, $idAsKey = false, $asObject = true)
127
    {
128
        $ret   = [];
129
        $limit = $start = 0;
130
        $sql   = 'SELECT * FROM ' . $this->table;
131 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
132
            $sql .= ' ' . $criteria->renderWhere();
133
            if ('' != $criteria->getSort()) {
134
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
135
            }
136
            $limit = $criteria->getLimit();
137
            $start = $criteria->getStart();
138
        }
139
        $result = $this->db->query($sql, $limit, $start);
140
        if (!$result) {
141
            return $ret;
142
        }
143
144
        $ret = $this->convertResultSet($result, $idAsKey, $asObject);
145
146
        return $ret;
147
    }
148
149
    /**
150
     * Convert a database resultset to a returnable array.
151
     *
152
     * @param XoopsObject $result  database resultset
153
     * @param bool        $idAsKey - should NOT be used with joint keys
154
     * @param bool        $asObject
155
     *
156
     * @return array
157
     */
158
    public function convertResultSet($result, $idAsKey = false, $asObject = true)
159
    {
160
        $ret = [];
161
        while ($myrow = $this->db->fetchArray($result)) {
162
            $obj = $this->create(false);
163
            $obj->assignVars($myrow);
164
            if (!$idAsKey) {
165 View Code Duplication
                if ($asObject) {
166
                    $ret[] = $obj;
167
                } else {
168
                    $row  = [];
169
                    $vars =& $obj->getVars();
170
                    foreach (array_keys($vars) as $i) {
171
                        $row[$i] = $obj->getVar($i);
172
                    }
173
                    $ret[] = $row;
174
                }
175
            } else {
176
                if ($asObject) {
177
                    $ret[$myrow[$this->keyName]] = $obj;
178 View Code Duplication
                } else {
179
                    $row  = [];
180
                    $vars =& $obj->getVars();
181
                    foreach (array_keys($vars) as $i) {
182
                        $row[$i] = $obj->getVar($i);
183
                    }
184
                    $ret[$myrow[$this->keyName]] = $row;
185
                }
186
            }
187
            unset($obj);
188
        }
189
190
        return $ret;
191
    }
192
193
    /**
194
     * Retrieve a list of objects as arrays - DON'T USE WITH JOINT KEYS.
195
     *
196
     * @param CriteriaElement $criteria {@link CriteriaElement} conditions to be met
197
     * @param int             $limit    Max number of objects to fetch
198
     * @param int             $start    Which record to start at
199
     *
200
     * @return array
201
     */
202
    public function getList(CriteriaElement $criteria = null, $limit = 0, $start = 0)
203
    {
204
        $ret = [];
205
        if (null === $criteria) {
206
            $criteria = new CriteriaCompo();
207
        }
208
209
        if ('' == $criteria->getSort()) {
210
            $criteria->setSort($this->identifierName);
211
        }
212
213
        $sql = 'SELECT ' . $this->keyName;
214
        if (!empty($this->identifierName)) {
215
            $sql .= ', ' . $this->identifierName;
216
        }
217
        $sql .= ' FROM ' . $this->table;
218 View Code Duplication
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
219
            $sql .= ' ' . $criteria->renderWhere();
220
            if ('' != $criteria->getSort()) {
221
                $sql .= ' ORDER BY ' . $criteria->getSort() . ' ' . $criteria->getOrder();
222
            }
223
            $limit = $criteria->getLimit();
224
            $start = $criteria->getStart();
225
        }
226
        $result = $this->db->query($sql, $limit, $start);
227
        if (!$result) {
228
            return $ret;
229
        }
230
231
        $myts = MyTextSanitizer::getInstance();
232
        while ($myrow = $this->db->fetchArray($result)) {
233
            //identifiers should be textboxes, so sanitize them like that
234
            $ret[$myrow[$this->keyName]] = empty($this->identifierName) ? 1 : $myts->htmlSpecialChars($myrow[$this->identifierName]);
235
        }
236
237
        return $ret;
238
    }
239
240
    /**
241
     * count objects matching a condition.
242
     *
243
     * @param CriteriaElement $criteria {@link CriteriaElement} to match
244
     *
245
     * @return int|array count of objects
246
     */
247 View Code Duplication
    public function getCount(CriteriaElement $criteria = null)
248
    {
249
        $field   = '';
250
        $groupby = false;
251
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
252
            if ('' != $criteria->groupby) {
253
                $groupby = true;
254
                $field   = $criteria->groupby . ', '; //Not entirely secure unless you KNOW that no criteria's groupby clause is going to be mis-used
255
            }
256
        }
257
        $sql = 'SELECT ' . $field . 'COUNT(*) FROM ' . $this->table;
258
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
259
            $sql .= ' ' . $criteria->renderWhere();
260
            if ('' != $criteria->groupby) {
261
                $sql .= $criteria->getGroupby();
262
            }
263
        }
264
        $result = $this->db->query($sql);
265
        if (!$result) {
266
            return 0;
267
        }
268
        if (false === $groupby) {
269
            list($count) = $this->db->fetchRow($result);
270
271
            return $count;
272
        } else {
273
            $ret = [];
274
            while (list($id, $count) = $this->db->fetchRow($result)) {
275
                $ret[$id] = $count;
276
            }
277
278
            return $ret;
279
        }
280
    }
281
282
    /**
283
     * delete an object from the database by id.
284
     *
285
     * @param mixed $id id of the object to delete
286
     * @param bool  $force
287
     *
288
     * @return bool FALSE if failed.
289
     */
290
    public function deleteById($id, $force = false) //delete(XoopsObject $object, $force = false)
291
    {
292
        if (is_array($this->keyName)) {
293
            $clause = [];
294
            for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
295
                $clause[] = $this->keyName[$i] . ' = ' . $id[$i];
296
            }
297
            $whereclause = implode(' AND ', $clause);
298
        } else {
299
            $whereclause = $this->keyName . ' = ' . $id;
300
        }
301
        $sql = 'DELETE FROM ' . $this->table . ' WHERE ' . $whereclause;
302 View Code Duplication
        if (false !== $force) {
303
            $result = $this->db->queryF($sql);
304
        } else {
305
            $result = $this->db->query($sql);
306
        }
307
        if (!$result) {
308
            return false;
309
        }
310
311
        return true;
312
    }
313
314
    /**
315
     * insert a new object in the database.
316
     *
317
     * @param XoopsObject $obj         reference to the object
318
     * @param bool        $force       whether to force the query execution despite security settings
319
     * @param bool        $checkObject check if the object is dirty and clean the attributes
320
     *
321
     * @return bool FALSE if failed, TRUE if already present and unchanged or successful
322
     */
323
    public function insert(XoopsObject $obj, $force = false, $checkObject = true)
324
    {
325
        if (false !== $checkObject) {
326
            if (!is_object($obj)) {
327
                //                var_dump($obj);
328
                return false;
329
            }
330
331
            if (!($obj instanceof $this->className && class_exists($this->className))) {
332
                $obj->setErrors(get_class($obj) . ' Differs from ' . $this->className);
333
334
                return false;
335
            }
336
        }
337
        if (!$obj->cleanVars()) {
338
            return false;
339
        }
340
341
        foreach ($obj->cleanVars as $k => $v) {
342
            if (XOBJ_DTYPE_INT == $obj->vars[$k]['data_type']) {
343
                $cleanvars[$k] = (int)$v;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$cleanvars was never initialized. Although not strictly required by PHP, it is generally a good practice to add $cleanvars = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
344
            } elseif (is_array($v)) {
345
                $cleanvars[$k] = $this->db->quoteString(implode(',', $v));
346
            } else {
347
                $cleanvars[$k] = $this->db->quoteString($v);
348
            }
349
        }
350
        if ($obj->isNew()) {
351
            if (!is_array($this->keyName)) {
352
                if ($cleanvars[$this->keyName] < 1) {
353
                    $cleanvars[$this->keyName] = $this->db->genId($this->table . '_' . $this->keyName . '_seq');
354
                }
355
            }
356
            $sql = 'INSERT INTO ' . $this->table . ' (' . implode(',', array_keys($cleanvars)) . ') VALUES (' . implode(',', array_values($cleanvars)) . ')';
357
        } else {
358
            $sql = 'UPDATE ' . $this->table . ' SET';
359
            foreach ($cleanvars as $key => $value) {
360
                if ((!is_array($this->keyName) && $key == $this->keyName)
361
                    || (is_array($this->keyName)
362
                        && in_array($key, $this->keyName))) {
363
                    continue;
364
                }
365
                if (isset($notfirst)) {
366
                    $sql .= ',';
367
                }
368
                $sql      .= ' ' . $key . ' = ' . $value;
369
                $notfirst = true;
370
            }
371
            if (is_array($this->keyName)) {
372
                $whereclause = '';
373
                for ($i = 0, $iMax = count($this->keyName); $i < $iMax; ++$i) {
374
                    if ($i > 0) {
375
                        $whereclause .= ' AND ';
376
                    }
377
                    $whereclause .= $this->keyName[$i] . ' = ' . $obj->getVar($this->keyName[$i]);
378
                }
379
            } else {
380
                $whereclause = $this->keyName . ' = ' . $obj->getVar($this->keyName);
381
            }
382
            $sql .= ' WHERE ' . $whereclause;
383
        }
384 View Code Duplication
        if (false !== $force) {
385
            $result = $this->db->queryF($sql);
386
        } else {
387
            $result = $this->db->query($sql);
388
        }
389
        if (!$result) {
390
            return false;
391
        }
392
        if (!is_array($this->keyName) && $obj->isNew()) {
393
            $obj->assignVar($this->keyName, $this->db->getInsertId());
394
        }
395
396
        return true;
397
    }
398
399
    /**
400
     * Change a value for objects with a certain criteria.
401
     *
402
     * @param string          $fieldname  Name of the field
403
     * @param string|array    $fieldvalue Value to write
404
     * @param CriteriaElement $criteria   {@link CriteriaElement}
405
     * @param bool            $force
406
     *
407
     * @return bool
408
     */
409
    public function updateAll($fieldname, $fieldvalue, CriteriaElement $criteria = null, $force = false)
410
    {
411
        $setClause = $fieldname . ' = ';
412
        if (is_numeric($fieldvalue)) {
413
            $setClause .= $fieldvalue;
414
        } elseif (is_array($fieldvalue)) {
415
            $setClause .= $this->db->quoteString(implode(',', $fieldvalue));
416
        } else {
417
            $setClause .= $this->db->quoteString($fieldvalue);
418
        }
419
        $sql = 'UPDATE ' . $this->table . ' SET ' . $setClause;
420
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
421
            $sql .= ' ' . $criteria->renderWhere();
422
        }
423 View Code Duplication
        if (false !== $force) {
424
            $result = $this->db->queryF($sql);
425
        } else {
426
            $result = $this->db->query($sql);
427
        }
428
        if (!$result) {
429
            return false;
430
        }
431
432
        return true;
433
    }
434
435
    /**
436
     * @param      $fieldname
437
     * @param      $fieldvalue
438
     * @param null $criteria
439
     * @param bool $force
440
     *
441
     * @return bool
442
     */
443
    public function updateFieldValue($fieldname, $fieldvalue, $criteria = null, $force = true)
444
    {
445
        $sql = 'UPDATE ' . $this->table . ' SET ' . $fieldname . ' = ' . $fieldvalue;
446
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
447
            $sql .= ' ' . $criteria->renderWhere();
448
        }
449 View Code Duplication
        if (false !== $force) {
450
            $result = $this->db->queryF($sql);
451
        } else {
452
            $result = $this->db->query($sql);
453
        }
454
        if (!$result) {
455
            return false;
456
        }
457
458
        return true;
459
    }
460
461
    /**
462
     * delete all objects meeting the conditions.
463
     *
464
     * @param CriteriaElement $criteria        {@link CriteriaElement}
465
     *                                         with conditions to meet
466
     * @param bool            $force
467
     * @param bool            $asObject
468
     *
469
     * @return bool
470
     */
471
    public function deleteAll(CriteriaElement $criteria = null, $force = true, $asObject = false)
472
    {
473
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
474
            $sql = 'DELETE FROM ' . $this->table;
475
            $sql .= ' ' . $criteria->renderWhere();
476
            if (!$this->db->query($sql)) {
477
                return false;
478
            }
479
            $rows = $this->db->getAffectedRows();
480
481
            return $rows > 0 ? $rows : true;
482
        }
483
484
        return false;
485
    }
486
487
    /**
488
     * @param $data
489
     *
490
     * @return array
491
     */
492
    public function _toObject($data)
493
    {
494
        if (is_array($data)) {
495
            $ret = [];
496
            foreach ($data as $v) {
497
                $object = new $this->className();
498
                $object->assignVars($v);
499
                $ret[] = $object;
500
            }
501
502
            return $ret;
503
        } else {
504
            $object = new $this->className();
505
            $object->assignVars($v);
506
507
            return $object;
508
        }
509
    }
510
511
    /**
512
     * @param        $objects
513
     * @param array  $externalKeys
514
     * @param string $format
515
     *
516
     * @return array
517
     */
518
    public function objectToArray($objects, $externalKeys = [], $format = 's')
519
    {
520
        static $cache;
521
        if (!is_array($externalKeys)) {
522
            $externalKeys = [$externalKeys];
523
        } //JJD
524
525
        $ret = [];
526
        if (is_array($objects)) {
527
            $i = 0;
528
            foreach ($objects as $object) {
529
                $vars = $object->getVars();
530
                foreach ($vars as $k => $v) {
531
                    $ret[$i][$k] = $object->getVar($k, $format);
532
                }
533
                foreach ($externalKeys as $key) {
534
                    // Replace external key by corresponding object
535
                    $externalKey = $object->getExternalKey($key);
536
                    if (0 != $ret[$i][$key]) {
537
                        // Retrieving data if isn't cached
538
                        if (!isset($cached[$externalKey['keyName']][$ret[$i][$key]])) {
539 View Code Duplication
                            if ($externalKey['core']) {
540
                                $handler = xoops_getHandler($externalKey['className']);
541
                            } else {
542
                                $handler = xoops_getModuleHandler($externalKey['className'], 'extcal');
543
                            }
544
                            $getMethod                                       = $externalKey['getMethodeName'];
545
                            $cached[$externalKey['keyName']][$ret[$i][$key]] = $this->objectToArrayWithoutExternalKey($handler->$getMethod($ret[$i][$key], true), $format);
546
                        }
547
                        $ret[$i][$externalKey['keyName']] = $cached[$externalKey['keyName']][$ret[$i][$key]];
548
                    }
549
                    unset($ret[$i][$key]);
550
                }
551
                ++$i;
552
            }
553
        } else {
554
            $vars = $objects->getVars();
555
            foreach ($vars as $k => $v) {
556
                $ret[$k] = $objects->getVar($k, $format);
557
            }
558
            foreach ($externalKeys as $key) {
559
                // Replace external key by corresponding object
560
                $externalKey = $objects->getExternalKey($key);
561
                if (0 != $ret[$key]) {
562
                    // Retriving data if isn't cached
563
                    if (!isset($cached[$externalKey['keyName']][$ret[$key]])) {
564 View Code Duplication
                        if ($externalKey['core']) {
565
                            $handler = xoops_getHandler($externalKey['className']);
566
                        } else {
567
                            $handler = xoops_getModuleHandler($externalKey['className'], 'extcal');
568
                        }
569
                        $getMethod                                   = $externalKey['getMethodeName'];
570
                        $cached[$externalKey['keyName']][$ret[$key]] = $this->objectToArrayWithoutExternalKey($handler->$getMethod($ret[$key], true), $format);
571
                    }
572
                    $ret[$externalKey['keyName']] = $cached[$externalKey['keyName']][$ret[$key]];
573
                }
574
                unset($ret[$key]);
575
            }
576
        }
577
578
        return $ret;
579
    }
580
581
    /**
582
     * @param        $object
583
     * @param string $format
584
     *
585
     * @return array
586
     */
587
    public function objectToArrayWithoutExternalKey($object, $format = 's')
588
    {
589
        $ret = [];
590
        if (null !== $object) {
591
            $vars = $object->getVars();
592
            foreach ($vars as $k => $v) {
593
                $ret[$k] = $object->getVar($k, $format);
594
            }
595
        }
596
597
        return $ret;
598
    }
599
600
    /**
601
     * @param        $fieldname
602
     * @param        $criteria
603
     * @param string $op
604
     *
605
     * @return bool
606
     */
607
    public function updateCounter($fieldname, $criteria, $op = '+')
608
    {
609
        $sql    = 'UPDATE ' . $this->table . ' SET ' . $fieldname . ' = ' . $fieldname . $op . '1';
610
        $sql    .= ' ' . $criteria->renderWhere();
611
        $result = $this->db->queryF($sql);
612
        if (!$result) {
613
            return false;
614
        }
615
616
        return true;
617
    }
618
619
    /**
620
     * @param null|CriteriaElement $criteria
621
     * @param string               $sum
622
     *
623
     * @return array|string
624
     */
625 View Code Duplication
    public function getSum(CriteriaElement $criteria = null, $sum = '*')
626
    {
627
        $field   = '';
628
        $groupby = false;
629
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
630
            if ('' != $criteria->groupby) {
631
                $groupby = true;
632
                $field   = $criteria->groupby . ', '; //Not entirely secure unless you KNOW that no criteria's groupby clause is going to be mis-used
633
            }
634
        }
635
        $sql = 'SELECT ' . $field . "SUM($sum) FROM " . $this->table;
636
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
637
            $sql .= ' ' . $criteria->renderWhere();
638
            if ('' != $criteria->groupby) {
639
                $sql .= $criteria->getGroupby();
640
            }
641
        }
642
        $result = $this->db->query($sql);
643
        if (!$result) {
644
            return 0;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 0; (integer) is incompatible with the return type documented by ExtcalPersistableObjectHandler::getSum of type array|string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
645
        }
646
        if (false === $groupby) {
647
            list($sum) = $this->db->fetchRow($result);
648
649
            return $sum;
650
        } else {
651
            $ret = [];
652
            while (list($id, $sum) = $this->db->fetchRow($result)) {
653
                $ret[$id] = $sum;
654
            }
655
656
            return $ret;
657
        }
658
    }
659
660
    /**
661
     * @param null|CriteriaElement $criteria
662
     * @param string               $max
663
     *
664
     * @return array|string
665
     */
666 View Code Duplication
    public function getMax(CriteriaElement $criteria = null, $max = '*')
667
    {
668
        $field   = '';
669
        $groupby = false;
670
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
671
            if ('' != $criteria->groupby) {
672
                $groupby = true;
673
                $field   = $criteria->groupby . ', '; //Not entirely secure unless you KNOW that no criteria's groupby clause is going to be mis-used
674
            }
675
        }
676
        $sql = 'SELECT ' . $field . "MAX($max) FROM " . $this->table;
677
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
678
            $sql .= ' ' . $criteria->renderWhere();
679
            if ('' != $criteria->groupby) {
680
                $sql .= $criteria->getGroupby();
681
            }
682
        }
683
        $result = $this->db->query($sql);
684
        if (!$result) {
685
            return 0;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 0; (integer) is incompatible with the return type documented by ExtcalPersistableObjectHandler::getMax of type array|string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
686
        }
687
        if (false === $groupby) {
688
            list($max) = $this->db->fetchRow($result);
689
690
            return $max;
691
        } else {
692
            $ret = [];
693
            while (list($id, $max) = $this->db->fetchRow($result)) {
694
                $ret[$id] = $max;
695
            }
696
697
            return $ret;
698
        }
699
    }
700
701
    /**
702
     * @param null   $criteria
703
     * @param string $avg
704
     *
705
     * @return int
706
     */
707
    public function getAvg($criteria = null, $avg = '*')
708
    {
709
        $field = '';
710
711
        $sql = 'SELECT ' . $field . "AVG($avg) FROM " . $this->table;
712
        if (isset($criteria) && is_subclass_of($criteria, 'criteriaelement')) {
713
            $sql .= ' ' . $criteria->renderWhere();
714
        }
715
        $result = $this->db->query($sql);
716
        if (!$result) {
717
            return 0;
718
        }
719
        list($sum) = $this->db->fetchRow($result);
720
721
        return $sum;
722
    }
723
724
    /**
725
     * @return mixed
726
     */
727
    public function getInsertId()
728
    {
729
        return $this->db->getInsertId();
730
    }
731
}
732