Completed
Push — master ( 96636c...782a93 )
by Edgard
22:00 queued 11:00
created

Schema::createColumnSchemaBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php
2
3
/**
4
 * @link http://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license http://www.yiiframework.com/license/
7
 */
8
9
namespace edgardmessias\db\firebird;
10
11
use yii\base\InvalidCallException;
12
use yii\db\Exception;
13
use yii\db\TableSchema;
14
15
/**
16
 * Schema represents the Firebird schema information.
17
 *
18
 * @property string[] $indexNames All index names in the Firebird. This property is read-only.
19
 * @property IndexSchema[] $indexSchemas The metadata for all indexes in the Firebird. Each array element is an
20
 * instance of [[IndexSchema]] or its child class. This property is read-only.
21
 * @property array $indexTypes All index types in the Firebird in format: index name => index type. This
22
 * property is read-only.
23
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
24
 *
25
 * @author Edgard Lorraine Messias <[email protected]>
26
 * @since 2.0
27
 */
28
class Schema extends \yii\db\Schema
29
{
30
31
    private $_lastInsertID = null;
32
33
    /**
34
     * @var array map of DB errors and corresponding exceptions
35
     * If left part is found in DB error message exception class from the right part is used.
36
     */
37
    public $exceptionMap = [
38
        'SQLSTATE[23'                                               => 'yii\db\IntegrityException',
39
        'SQLSTATE[HY000]: General error: -803 violation of PRIMARY' => 'yii\db\IntegrityException',
40
    ];
41
    public $reservedWords = [
42
        'ORDER',
43
        'POSITION',
44
        'TIME',
45
        'VALUE',
46
    ];
47
48
    /**
49
     * @var array mapping from physical column types (keys) to abstract column types (values)
50
     */
51
    public $typeMap = [
52
        'bigint'             => self::TYPE_BIGINT,
53
        'char'               => self::TYPE_CHAR,
54
        'varchar'            => self::TYPE_STRING,
55
        'timestamp'          => self::TYPE_TIMESTAMP,
56
        'decimal'            => self::TYPE_DECIMAL,
57
        'float'              => self::TYPE_FLOAT,
58
        'blob'               => self::TYPE_BINARY,
59
        'integer'            => self::TYPE_INTEGER,
60
        'blob sub_type text' => self::TYPE_TEXT,
61
        'numeric'            => self::TYPE_DECIMAL,
62
        'double precision'   => self::TYPE_DOUBLE,
63
        'smallint'           => self::TYPE_SMALLINT,
64
    ];
65
66
    /**
67
     * Creates a query builder for the database.
68
     * This method may be overridden by child classes to create a DBMS-specific query builder.
69
     * @return QueryBuilder query builder instance
70
     */
71 2
    public function createQueryBuilder()
72
    {
73 2
        return new QueryBuilder($this->db);
74
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79 2
    public function createColumnSchemaBuilder($type, $length = null)
80
    {
81 2
        return new ColumnSchemaBuilder($type, $length);
82
    }
83
84 25
    public function quoteSimpleTableName($name)
85
    {
86 25
        if ($this->db->tablePrefix !== '') {
87
            return $name;
88
        }
89
90 25
        $word = strtoupper(str_replace('%', '', $name));
91 25
        if (in_array($word, $this->reservedWords)) {
92 1
            return strpos($name, '"') !== false ? $name : '"' . $name . '"';
93
        }
94
95 25
        return $name;
96
    }
97
98 66
    public function quoteSimpleColumnName($name)
99
    {
100 66
        if (in_array(strtoupper($name), $this->reservedWords)) {
101 1
            return parent::quoteSimpleColumnName($name);
102
        }
103 66
        return $name;
104
    }
105
106 3
    protected function loadTableSchema($name)
107
    {
108 3
        $table = new TableSchema;
109 3
        $this->resolveTableNames($table, $name);
110 3
        if ($this->findColumns($table)) {
111 2
            $this->findConstraints($table);
112 2
            return $table;
113
        }
114 2
        return null;
115
    }
116
117 1
    public function getPdoType($data)
118
    {
119
        static $typeMap = [
120
            // php type => PDO type
121
            'boolean'  => \PDO::PARAM_INT,
122
            'integer'  => \PDO::PARAM_INT,
123
            'string'   => \PDO::PARAM_STR,
124
            'resource' => \PDO::PARAM_LOB,
125
            'NULL'     => \PDO::PARAM_NULL,
126 1
        ];
127 1
        $type = gettype($data);
128
129 1
        return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
130
    }
131
132
    /**
133
     *
134
     * @param TableSchema $table
135
     * @param string $name
136
     */
137 3
    protected function resolveTableNames($table, $name)
138
    {
139 3
        $parts = explode('.', str_replace('"', '', $name));
140 3
        if (isset($parts[1])) {
141
            $table->schemaName = $parts[0];
142
            $table->name = strtolower($parts[1]);
143
            $table->fullName = $this->quoteTableName($table->schemaName) . '.' . $this->quoteTableName($table->name);
144
        } else {
145 3
            $table->name = strtolower($parts[0]);
146 3
            $table->fullName = $this->quoteTableName($table->name);
147
        }
148 3
    }
149
150
    /**
151
     * Collects the table column metadata.
152
     *
153
     * @param TableSchema $table the table metadata
154
     * @return boolean whether the table exists in the database
155
     */
156 3
    protected function findColumns($table)
157
    {
158
        // Zoggo - Converted sql to use join syntax
159
        // robregonm - Added isAutoInc
160
        $sql = 'SELECT
161
                    rel.rdb$field_name AS fname,
162
                    rel.rdb$default_source AS fdefault,
163
                    fld.rdb$field_type AS fcodtype,
164
                    fld.rdb$field_sub_type AS fcodsubtype,
165
                    fld.rdb$field_length AS flength,
166
                    fld.rdb$character_length AS fcharlength,
167
                    fld.rdb$field_scale AS fscale,
168
                    fld.rdb$field_precision AS fprecision,
169
                    rel.rdb$null_flag AS fnull,
170
                    rel.rdb$description AS fcomment,
171
                    fld.rdb$default_value AS fdefault_value,
172
                    (SELECT RDB$TRIGGER_SOURCE FROM RDB$TRIGGERS
173
                        WHERE RDB$SYSTEM_FLAG = 0
174 3
                        AND UPPER(RDB$RELATION_NAME)=UPPER(\'' . $table->name . '\')
175
                        AND RDB$TRIGGER_TYPE = 1
176
                        AND RDB$TRIGGER_INACTIVE = 0
177
                        AND (UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=GEN_ID%\'
178
                            OR UPPER(REPLACE(RDB$TRIGGER_SOURCE,\' \',\'\')) LIKE \'%NEW.\'||TRIM(rel.rdb$field_name)||\'=NEXTVALUEFOR%\'))
179
                    AS fautoinc
180
                FROM
181
                    rdb$relation_fields rel
182
                    JOIN rdb$fields fld ON rel.rdb$field_source=fld.rdb$field_name
183
                WHERE
184 3
                    UPPER(rel.rdb$relation_name)=UPPER(\'' . $table->name . '\')
185
                ORDER BY
186 3
                    rel.rdb$field_position;';
187
        try {
188 3
            $columns = $this->db->createCommand($sql)->queryAll();
189 3
            if (empty($columns)) {
190 2
                return false;
191
            }
192 2
        } catch (Exception $e) {
193
            return false;
194
        }
195
        $sql = 'SELECT
196
                    idx.rdb$field_name AS fname
197
                FROM
198
                    rdb$relation_constraints rc
199
                    JOIN rdb$index_segments idx ON idx.rdb$index_name=rc.rdb$index_name
200
                WHERE rc.rdb$constraint_type=\'PRIMARY KEY\'
201 2
					AND UPPER(rc.rdb$relation_name)=UPPER(\'' . $table->name . '\')';
202
        try {
203 2
            $pkeys = $this->db->createCommand($sql)->queryColumn();
204 2
        } catch (Exception $e) {
205
            return false;
206
        }
207 2
        $pkeys = array_map("rtrim", $pkeys);
208 2
        $pkeys = array_map("strtolower", $pkeys);
209 2
        foreach ($columns as $key => $column) {
210 2
            $column = array_map("strtolower", $column);
211 2
            $columns[$key]['fprimary'] = in_array(rtrim($column['fname']), $pkeys);
212 2
        }
213 2
        foreach ($columns as $column) {
214 2
            $c = $this->loadColumnSchema($column);
215 2
            if ($table->sequenceName === null && $c->autoIncrement) {
216 1
                $matches = [];
217 1
                if (preg_match("/NEW.{$c->name}\s*=\s*GEN_ID\((\w+)/i", $column['fautoinc'], $matches)) {
218
                    $table->sequenceName = $matches[1];
219 1
                } elseif (preg_match("/NEW.{$c->name}\s*=\s*NEXT\s+VALUE\s+FOR\s+(\w+)/i", $column['fautoinc'], $matches)) {
220 1
                    $table->sequenceName = $matches[1];
221 1
                }
222 1
            }
223 2
            $table->columns[$c->name] = $c;
224 2
            if ($c->isPrimaryKey) {
225 2
                $table->primaryKey[] = $c->name;
226 2
            }
227 2
        }
228 2
        return (count($table->columns) > 0);
229
    }
230
231
    /**
232
     * @return ColumnSchema
233
     * @throws \yii\base\InvalidConfigException
234
     */
235 2
    protected function createColumnSchema()
236
    {
237 2
        return \Yii::createObject('\edgardmessias\db\firebird\ColumnSchema');
238
    }
239
240
    /**
241
     * Creates a table column.
242
     *
243
     * @param array $column column metadata
244
     * @return ColumnSchema normalized column metadata
245
     */
246 2
    protected function loadColumnSchema($column)
247
    {
248 2
        $c = $this->createColumnSchema();
249 2
        $c->name = strtolower(rtrim($column['fname']));
250 2
        $c->allowNull = $column['fnull'] !== '1';
251 2
        $c->isPrimaryKey = $column['fprimary'];
252 2
        $c->autoIncrement = (boolean) $column['fautoinc'];
253 2
        $c->comment = $column['fcomment'] === null ? '' : $column['fcomment'];
254
255 2
        $c->type = self::TYPE_STRING;
256
257 2
        $defaultValue = null;
258 2
        if (!empty($column['fdefault'])) {
259
            // remove whitespace, 'DEFAULT ' prefix and surrounding single quotes; all optional
260
            if (preg_match("/\s*(DEFAULT\s+){0,1}('(.*)'|(.*))\s*/i", $column['fdefault'], $parts)) {
261
                $defaultValue = array_pop($parts);
262
            }
263
            // handle escaped single quotes like in "funny''quoted''string"
264
            $defaultValue = str_replace('\'\'', '\'', $defaultValue);
265
        }
266 2
        if ($defaultValue === null) {
267 2
            $defaultValue = $column['fdefault_value'];
268 2
        }
269 2
        $dbType = "";
270
        $baseTypes = [
271 2
            7   => 'SMALLINT',
272 2
            8   => 'INTEGER',
273 2
            16  => 'INT64',
274 2
            9   => 'QUAD',
275 2
            10  => 'FLOAT',
276 2
            11  => 'D_FLOAT',
277 2
            17  => 'BOOLEAN',
278 2
            27  => 'DOUBLE PRECISION',
279 2
            12  => 'DATE',
280 2
            13  => 'TIME',
281 2
            35  => 'TIMESTAMP',
282 2
            261 => 'BLOB',
283 2
            40  => 'CSTRING',
284 2
            45  => 'BLOB_ID',
285 2
        ];
286
        $baseCharTypes = [
287 2
            37 => 'VARCHAR',
288 2
            14 => 'CHAR',
289 2
        ];
290 2
        if (array_key_exists((int) $column['fcodtype'], $baseTypes)) {
291 2
            $dbType = $baseTypes[(int) $column['fcodtype']];
292 2
        } elseif (array_key_exists((int) $column['fcodtype'], $baseCharTypes)) {
293 2
            $c->size = (int) $column['fcharlength'];
294 2
            $c->precision = $c->size;
295 2
            $dbType = $baseCharTypes[(int) $column['fcodtype']] . "($c->size)";
296 2
        }
297 2
        switch ((int) $column['fcodtype']) {
298 2
            case 7:
299 2
            case 8:
300 2
                switch ((int) $column['fcodsubtype']) {
301 2
                    case 1:
302
                        $c->precision = (int) $column['fprecision'];
303
                        $c->size = $c->precision;
304
                        $c->scale = abs((int) $column['fscale']);
0 ignored issues
show
Documentation Bug introduced by
It seems like abs((int) $column['fscale']) can also be of type double. However, the property $scale is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
305
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
306
                        break;
307 2
                    case 2:
308
                        $c->precision = (int) $column['fprecision'];
309
                        $c->size = $c->precision;
310
                        $c->scale = abs((int) $column['fscale']);
311
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
312
                        break;
313
                }
314 2
                break;
315 2
            case 16:
316
                switch ((int) $column['fcodsubtype']) {
317
                    case 1:
318
                        $c->precision = (int) $column['fprecision'];
319
                        $c->size = $c->precision;
320
                        $c->scale = abs((int) $column['fscale']);
321
                        $dbType = "NUMERIC({$c->precision},{$c->scale})";
322
                        break;
323
                    case 2:
324
                        $c->precision = (int) $column['fprecision'];
325
                        $c->size = $c->precision;
326
                        $c->scale = abs((int) $column['fscale']);
327
                        $dbType = "DECIMAL({$c->precision},{$c->scale})";
328
                        break;
329
                    default:
330
                        $dbType = 'BIGINT';
331
                        break;
332
                }
333
                break;
334 2
            case 261:
335
                switch ((int) $column['fcodsubtype']) {
336
                    case 1:
337
                        $dbType = 'BLOB SUB_TYPE TEXT';
338
                        $c->size = null;
339
                        break;
340
                }
341
                break;
342
        }
343
344 2
        $c->dbType = strtolower($dbType);
345
346 2
        $c->type = self::TYPE_STRING;
347 2
        if (preg_match('/^([\w\ ]+)(?:\(([^\)]+)\))?/', $c->dbType, $matches)) {
348 2
            $type = strtolower($matches[1]);
349 2
            if (isset($this->typeMap[$type])) {
350 2
                $c->type = $this->typeMap[$type];
351 2
            }
352 2
        }
353
354
355 2
        $c->phpType = $this->getColumnPhpType($c);
356
357 2
        $c->defaultValue = null;
358 2
        if ($defaultValue !== null) {
359
            if (in_array($c->type, [self::TYPE_DATE, self::TYPE_DATETIME, self::TYPE_TIME, self::TYPE_TIMESTAMP])
360
                    && preg_match('/(CURRENT_|NOW|NULL|TODAY|TOMORROW|YESTERDAY)/i', $defaultValue)) {
361
                $c->defaultValue = new \yii\db\Expression(trim($defaultValue));
362
            } else {
363
                $c->defaultValue = $c->phpTypecast($defaultValue);
364
            }
365
        }
366
367 2
        return $c;
368
    }
369
370
    /**
371
     * Collects the foreign key column details for the given table.
372
     *
373
     * @param TableSchema $table the table metadata
374
     */
375 2
    protected function findConstraints($table)
376
    {
377
        // Zoggo - Converted sql to use join syntax
378
        $sql = 'SELECT
379
                    a.rdb$constraint_name as fconstraint,
380
                    c.rdb$relation_name AS ftable,
381
                    d.rdb$field_name AS pfield,
382
                    e.rdb$field_name AS ffield
383
                FROM
384
                    rdb$ref_constraints b
385
                    JOIN rdb$relation_constraints a ON a.rdb$constraint_name=b.rdb$constraint_name
386
                    JOIN rdb$relation_constraints c ON b.rdb$const_name_uq=c.rdb$constraint_name
387
                    JOIN rdb$index_segments d ON c.rdb$index_name=d.rdb$index_name
388
                    JOIN rdb$index_segments e ON a.rdb$index_name=e.rdb$index_name AND e.rdb$field_position = d.rdb$field_position
389
                WHERE
390
                    a.rdb$constraint_type=\'FOREIGN KEY\' AND
391 2
                    UPPER(a.rdb$relation_name)=UPPER(\'' . $table->name . '\') ';
392
        try {
393 2
            $fkeys = $this->db->createCommand($sql)->queryAll();
394 2
        } catch (Exception $e) {
395
            return false;
396
        }
397
398 2
        $constraints = [];
399 2
        foreach ($fkeys as $fkey) {
400
            // Zoggo - Added strtolower here to guarantee that values are
401
            // returned lower case. Otherwise gii generates wrong code.
402
            $fkey = array_map("rtrim", $fkey);
403
            $fkey = array_map("strtolower", $fkey);
404
405
            if (!isset($constraints[$fkey['fconstraint']])) {
406
                $constraints[$fkey['fconstraint']] = [
407
                    $fkey['ftable']
408
                ];
409
            }
410
            $constraints[$fkey['fconstraint']][$fkey['ffield']] = $fkey['pfield'];
411 2
        }
412 2
        $table->foreignKeys = array_values($constraints);
413 2
    }
414
415
    protected function findTableNames($schema = '')
416
    {
417
        $sql = 'SELECT
418
                    rdb$relation_name
419
                FROM
420
                    rdb$relations
421
                WHERE
422
                    (rdb$system_flag is null OR rdb$system_flag=0)';
423
        try {
424
            $tables = $this->db->createCommand($sql)->queryColumn();
425
        } catch (Exception $e) {
426
            return false;
427
        }
428
429
        $tables = array_map('rtrim', $tables);
430
        $tables = array_map('strtolower', $tables);
431
432
        return $tables;
433
    }
434
435
    /**
436
     * Returns all unique indexes for the given table.
437
     * Each array element is of the following structure:
438
     *
439
     * ~~~
440
     * [
441
     *  'IndexName1' => ['col1' [, ...]],
442
     *  'IndexName2' => ['col2' [, ...]],
443
     * ]
444
     * ~~~
445
     *
446
     * @param TableSchema $table the table metadata
447
     * @return array all unique indexes for the given table.
448
     * @since 2.0.4
449
     */
450
    public function findUniqueIndexes($table)
451
    {
452
        $query = '
453
SELECT id.RDB$INDEX_NAME as index_name, ids.RDB$FIELD_NAME as column_name
454
FROM RDB$INDICES id
455
INNER JOIN RDB$INDEX_SEGMENTS ids ON ids.RDB$INDEX_NAME = id.RDB$INDEX_NAME
456
WHERE id.RDB$UNIQUE_FLAG = 1
457
AND   id.RDB$SYSTEM_FLAG = 0
458
AND UPPER(id.RDB$RELATION_NAME) = UPPER(\'' . $table->name . '\')
459
ORDER BY id.RDB$RELATION_NAME, id.RDB$INDEX_NAME, ids.RDB$FIELD_POSITION';
460
        $result = [];
461
        $command = $this->db->createCommand($query);
462
        foreach ($command->queryAll() as $row) {
463
            $result[strtolower(rtrim($row['index_name']))][] = strtolower(rtrim($row['column_name']));
464
        }
465
        return $result;
466
    }
467
468
    /**
469
     * Sets the isolation level of the current transaction.
470
     * @param string $level The transaction isolation level to use for this transaction.
471
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
472
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
473
     * after `SET TRANSACTION ISOLATION LEVEL`.
474
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
475
     */
476
    public function setTransactionIsolationLevel($level)
477
    {
478
        if ($level == \yii\db\Transaction::READ_UNCOMMITTED) {
479
            parent::setTransactionIsolationLevel('READ COMMITTED RECORD_VERSION');
480
        } elseif ($level == \yii\db\Transaction::REPEATABLE_READ) {
481
            parent::setTransactionIsolationLevel('SNAPSHOT');
482
        } elseif ($level == \yii\db\Transaction::SERIALIZABLE) {
483
            parent::setTransactionIsolationLevel('SNAPSHOT TABLE STABILITY');
484
        } else {
485
            parent::setTransactionIsolationLevel($level);
486
        }
487
    }
488
489
    /**
490
     * @inheritdoc
491
     */
492 1
    public function insert($table, $columns)
493
    {
494 1
        $this->_lastInsertID = false;
495 1
        $params = [];
496 1
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
497 1
        $returnColumns = $this->getTableSchema($table)->primaryKey;
498 1
        if (!empty($returnColumns)) {
499 1
            $returning = [];
500 1
            foreach ((array) $returnColumns as $name) {
501 1
                $returning[] = $this->quoteColumnName($name);
502 1
            }
503 1
            $sql .= ' RETURNING ' . implode(', ', $returning);
504 1
        }
505
506 1
        $command = $this->db->createCommand($sql, $params);
507 1
        $command->prepare(false);
508 1
        $result = $command->queryOne();
509
510 1
        if (!$command->pdoStatement->rowCount()) {
511
            return false;
512
        } else {
513 1
            if (!empty($returnColumns)) {
514 1
                foreach ((array) $returnColumns as $name) {
515 1
                    if ($this->getTableSchema($table)->getColumn($name)->autoIncrement) {
516 1
                        $this->_lastInsertID = $result[$name];
517 1
                        break;
518
                    }
519 1
                }
520 1
            }
521 1
            return $result;
522
        }
523
    }
524
525
    /**
526
     * @inheritdoc
527
     */
528
    public function getLastInsertID($sequenceName = '')
529
    {
530
        if (!$this->db->isActive) {
531
            throw new InvalidCallException('DB Connection is not active.');
532
        }
533
        
534
        if ($sequenceName !== '') {
535
            return $this->db->createCommand('SELECT GEN_ID(' . $this->db->quoteTableName($sequenceName) . ', 0 ) FROM RDB$DATABASE;')->queryScalar();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->db->createCommand...BASE;')->queryScalar(); of type string|null|false adds false to the return on line 535 which is incompatible with the return type of the parent method yii\db\Schema::getLastInsertID of type string. It seems like you forgot to handle an error condition.
Loading history...
536
        }
537
538
        if ($this->_lastInsertID !== false) {
539
            return $this->_lastInsertID;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_lastInsertID; (false) is incompatible with the return type of the parent method yii\db\Schema::getLastInsertID of type 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...
540
        }
541
        return null;
542
    }
543
}
544