数据库Schema

作为一个广泛适用的框架,Yii支持目前几乎所有的主流数据库,这也是一个优秀框架所应具有的基本 功能。说到独立于各种数据库,就不得不提到数据库Schema。Yii提供了各种主流的数据库Schema,你 甚至可以自己写一个Schema以适用自己独特的数据库管理系统(DBMS)。与Schema有关的类有这么几 个:

  • yii\db\Schema 抽象类,用于描述各种不同的DBMS的Schema。
  • yii\db\TableSchema 用于描述表结构。
  • yii\db\ColumnSchema 用于描述字段信息。
  • yii\db\pgsql, yii\db\mysql, yii\db\sqlite, yii\db\mssql, yii\db\oci, yii\db\cubird 下的各种schema,用于具体描述各种DBMS。

yii\db\Connection 中,有一个 $schemaMap 数组,用于建立PDO数据库驱动与具体的 schema 类间的映射关系:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public $schemaMap = [
    'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
    'mysqli' => 'yii\db\mysql\Schema', // MySQL
    'mysql' => 'yii\db\mysql\Schema', // MySQL
    'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
    'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
    'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
    'oci' => 'yii\db\oci\Schema', // Oracle driver
    'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
    'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
    'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
];

我们可以认为Yii默认情况下支持上述数组中的10种DBMS(6个Schema),这在绝大多数情况下, 是完全足够的。万一你使用了超出这一范围的DBMS,在确保兼容的情况下,你可以自己写一个Schema, 使Yii可以支持该DBMS。

数据类型

各DBMS间,最明显、最常见的差异就在于所支持、实现的数据类型不同。Yii的一个重要任务,就是消 除这些区别,提供一个统一的开发界面供开发者使用。所以,我们先来看看Yii是怎么克服这一拦路虎 ,实现天下的大一统的。

抽象数据类型

在使用Yii进行数据库开发时,涉及到2个方面的数据类型:PHP自身的数据类型,DBMS的数据类型。 其中,PHP数据类型比较好整,反正就那么几个,跟平台、环境的关系也比较清楚。 复杂的地方在于DBMS的数据类型,龙生九子,各有不同。

因此,Yii不但需要搞定各DBMS间数据类型的差异,还需要搞定PHP与DBMS间数据类 类型的差异。因此,Yii引入了一个逻辑层面的数据类型,来统一PHP与DBMS,以及各DBMS之间数据类型 的差异。这里我们把这个逻辑层面的数据类型称为抽象类型,在 yii\db\Schema 中进行定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Schema extends Object
{
    // 预定义16种抽象字段类型
    const TYPE_PK = 'pk';
    const TYPE_BIGPK = 'bigpk';
    const TYPE_STRING = 'string';
    const TYPE_TEXT = 'text';
    const TYPE_SMALLINT = 'smallint';
    const TYPE_INTEGER = 'integer';
    const TYPE_BIGINT = 'bigint';
    const TYPE_FLOAT = 'float';
    const TYPE_DECIMAL = 'decimal';
    const TYPE_DATETIME = 'datetime';
    const TYPE_TIMESTAMP = 'timestamp';
    const TYPE_TIME = 'time';
    const TYPE_DATE = 'date';
    const TYPE_BINARY = 'binary';
    const TYPE_BOOLEAN = 'boolean';
    const TYPE_MONEY = 'money';

    // ... ...
}

yii\db\Schema 一上来就先针对各DBMS间差异最明显的字段数据类型进行统一,提供了16种抽象的 字段类型。这16种类型与DBMS无关,在具体到特定的DBMS时,Yii会自动转换成合适的数据库字段类型 。我们在编程中,若需要指定字段类型,比如创建数据库之类,就使用这16种抽象类型。这样的话, 就不用考虑使用的类型具体的DBMS是否支持的问题了,可以使我们更加专注于开发。

这16种类型看着就知道是什么意思,我们就不展开讲了。只是有一点,这个数据类型是抽象的,也就是 说,只是一个逻辑意义上的数据类型,不涉及到具体实现。比如上面的 Schema::TYPE_BIGINT 所 要表示的是一个大数,但具体实现上,可能是用一个字符串来表示。

数据类型转换

既然Yii使用抽象数据类型来一统江山,那么无可避免的,涉及到PHP数据类型和DBMS数据类型与抽象类 型的转换问题。

抽象类型转数据库类型

前面提到过,在Yii开发中,我们使用16种抽象数据类型,而不使用具体的DBMS的数据类型。那么,在 我们要创建一个数据库时,Yii是怎么为我们所定义的字段指定合适的数据类型的呢?

首先来看看一个基类 yii\db\QueryBuilder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class QueryBuilder extends \yii\base\Object
{
    // $typeMap用于定义抽象数据类型到DBMS数据类型的映射关系,
    // 具体由各QueryBuilder子类实现。
    public $typeMap = [];

    // 将抽象数据类型转换成合适的DBMS数据类型
    public function getColumnType($type)
    {
        // 映射表中已经有的,直接使用映射的类型
        if (isset($this->typeMap[$type])) {
            return $this->typeMap[$type];

        // 映射表中没有的类型,看看是不是形如 "Schema::TYPE_INT(11) DEFAULT 0" 之类的
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
            if (isset($this->typeMap[$matches[1]])) {
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
            }

        // 看看是不是形如 "Schema::TYPE_INT NOT NULL" 之类的,
        // 注意这一分支在第二分支之后
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
            if (isset($this->typeMap[$matches[1]])) {
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
            }
        }

        // 实在匹配不上映射表中的类型,那就原封不动返回吧
        return $type;
    }

    // ... ...
}

上面的代码只列出了 yii\db\QueryBuilder 的部分内容。特别是其中的 $typeMap[] 是由子 类来具体实现的。比如,对于MySQL数据库, yii\db\mysql\QueryBuilder 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace yii\db\mysql;

class QueryBuilder extends \yii\db\QueryBuilder
{
    public $typeMap = [
        Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
        Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
        Schema::TYPE_STRING => 'varchar(255)',
        Schema::TYPE_TEXT => 'text',
        Schema::TYPE_SMALLINT => 'smallint(6)',
        Schema::TYPE_INTEGER => 'int(11)',
        Schema::TYPE_BIGINT => 'bigint(20)',
        Schema::TYPE_FLOAT => 'float',
        Schema::TYPE_DECIMAL => 'decimal(10,0)',
        Schema::TYPE_DATETIME => 'datetime',
        Schema::TYPE_TIMESTAMP => 'timestamp',
        Schema::TYPE_TIME => 'time',
        Schema::TYPE_DATE => 'date',
        Schema::TYPE_BINARY => 'blob',
        Schema::TYPE_BOOLEAN => 'tinyint(1)',
        Schema::TYPE_MONEY => 'decimal(19,4)',
    ];
    // ... ...
}

因此,对于16种抽象数据类型而言,都可以转换成MySQL的特定数据类型。这里我们看到, TYPE_MONEY 本来是MySQL所没有的数据类型,但通过将其映射成了 decimal(19,4) ,使得 MySQL也可以支持 TYPE_MONEY 类型了。

同样的,在 yii\db\pgsql\QueryBuilder yii\db\mssql\QueryBuilder 等QueryBuilder子类 中,也有相同的数据类型映射表 $typeMap[] ,以实现从抽象数据类型到具体数据库数据类型的映 射。

对于抽象数据类型的具体定义,我们选一个自己比较熟悉的DBMS的Schema进行记忆就可以了。如果实在 吃不准,比如 TYPE_STRING 是定长还是变长,保留不保留空格等,可以看看Schema中的定义。建 议读者朋友们还是熟记这16个基本类型,起码仔细过一遍,在具体开发时,能省时不少。

yii\db\QueryBuilder::getColumnType() 实现了抽象数据类型到具体DBMS数据类型的转换。 在具体的转换过程中,如果指定一个字段为 Schema::TYPE_STRING 之类的,那么就会被转换成 varchar(255) 。有的读者朋友可能会问,那要是想指定成 varchar(64) 该怎么做呢?

那就直接使用 Schema::TYPE_STRING(64)yii\db\QueryBuilder::getColumnType() 会识 别出来 Schema::TYPE_STRING ,并将原来的 varchar(255) 转换成 varchar(64) 。 这点适用于其他数据类型。当然,其本身要支持。指定一个 Schema::TYPE_BOOLEAN(2) 又有什么 意义呢?

我们还可以在16种抽象类型后面使用 NOT NULLDEFAULT 等。 yii\db\QueryBuilder::getColumnType() 也是能够识别出来并加以处理的。

数据库类型转抽象类型

所谓来而不往非礼也,说完了抽象类型转DBMS数据类型,就该说说反过来数据库的数据类型, 怎么转换成抽象数据类型了。仍然以MySQL数据库为例,具体代码在 yii\db\mysql\Schema 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Schema extends \yii\db\Schema
{
    // 定义从数据库数据类型到16个抽象数据类型间的映射关系
    public $typeMap = [
        'tinyint' => self::TYPE_SMALLINT,
        'bit' => self::TYPE_INTEGER,
        'smallint' => self::TYPE_SMALLINT,
        'mediumint' => self::TYPE_INTEGER,
        'int' => self::TYPE_INTEGER,
        'integer' => self::TYPE_INTEGER,
        'bigint' => self::TYPE_BIGINT,
        'float' => self::TYPE_FLOAT,
        'double' => self::TYPE_FLOAT,
        'real' => self::TYPE_FLOAT,
        'decimal' => self::TYPE_DECIMAL,
        'numeric' => self::TYPE_DECIMAL,
        'tinytext' => self::TYPE_TEXT,
        'mediumtext' => self::TYPE_TEXT,
        'longtext' => self::TYPE_TEXT,
        'longblob' => self::TYPE_BINARY,
        'blob' => self::TYPE_BINARY,
        'text' => self::TYPE_TEXT,
        'varchar' => self::TYPE_STRING,
        'string' => self::TYPE_STRING,
        'char' => self::TYPE_STRING,
        'datetime' => self::TYPE_DATETIME,
        'year' => self::TYPE_DATE,
        'date' => self::TYPE_DATE,
        'time' => self::TYPE_TIME,
        'timestamp' => self::TYPE_TIMESTAMP,
        'enum' => self::TYPE_STRING,
    ];

    // ... ...
}

yii\db\mysql\Schema::$typeMap 中,定义了MySQL数据类型与16种抽象数据类型的映射关系。 当然,由于抽象数据类型只有16种,所以,有一些MySQL数据类型被映射成同一种抽象类型。比如 year date 都被映射成了 self::TYPE_DATE 了。

同样的,你可以在 yii\db\mssql\Schemayii\db\pgsql\Schema 中找到类似的映射代码。 在各Schema子类中,Yii针对不同的DBMS,实现了数据库数据类型到抽象数据类型间的映射关系。

从数据库类型到抽象类型的映射关系,只要掌握前面16种抽象类型与数据库数据类型的映射关系, 那么,其逆向映射就几乎不用刻意去记忆了。额外的,凡是没有合适的抽象类型的,就用 TYPE_STRING 来表示。

上面只是一个映射表,具体转换过程在生成字段信息 yii\db\ColumnSchema 时进行。 yii\db\ColumnSchema 保存了一个字段的各种相关信息,包括字段类型等。

字段信息的获取和填充,又发生在 yii\db\Schema::loadTableSchema() 中。 该函数用于加载数据表信息 yii\db\TableSchema ,这是一个抽象函数,具体由各子类实现。

字段信息和表信息我们后面再讲,这里大致知道就是用于保存字段和数据表的各种信息就可以了。而且 在获取表信息时,必然会调用到获取字段信息的相关代码。字段信息和表信息的获取,都由Schema子类 来具体实现。

对于MySQL数据库而言,字段信息的获取在 yii\db\mysql\Schema::loadColumnSchema() 中,也是 他实现了从数据库类型到抽象类型的转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// $info数组 由 SQL 语句 "SHOW FULL COLUMNS FROM ..." 而来,形式如下:
//      Field: id
//       Type: int(11)
//  Collation: NULL
//       Null: NO
//        Key: PRI
//    Default: NULL
//      Extra: auto_increment
// Privileges: select,insert,update,references
//    Comment:
protected function loadColumnSchema($info)
{
    $column = $this->createColumnSchema();

    // 字段名
    $column->name = $info['Field'];
    // 是否允许为NULL
    $column->allowNull = $info['Null'] === 'YES';
    // 是否是主键
    $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
    // 是否 auto_increment
    $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
    // 获取字段注释
    $column->comment = $info['Comment'];

    // 重点是这里,获取数据库字段类型,如上面的 int(11)
    $column->dbType = $info['Type'];
    // 是否是 unsigned
    $column->unsigned = stripos($column->dbType, 'unsigned') !== false;

    // 以下将把数据库类型,转换成对应的抽象类型,默认为 TYPE_STRING
    $column->type = self::TYPE_STRING;
    if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {

        // 获取 int(11) 的 "int" 部分
        $type = strtolower($matches[1]);
        // 如果映射表里有,那就直接映射成抽象类型
        if (isset($this->typeMap[$type])) {
            $column->type = $this->typeMap[$type];
        }

        // 形如int(11) 的括号中的内容
        if (!empty($matches[2])) {
            // 枚举类型,还需要将所有枚举值写入 $column->enumValues
            if ($type === 'enum') {
                $values = explode(',', $matches[2]);
                foreach ($values as $i => $value) {
                    $values[$i] = trim($value, "'");
                }
                $column->enumValues = $values;

            // 如果不是枚举类型,那么括号中的内容就是精度了,如 decimal(19,4)
            } else {
                $values = explode(',', $matches[2]);
                $column->size = $column->precision = (int) $values[0];
                if (isset($values[1])) {
                    $column->scale = (int) $values[1];
                }

                // bit(1) 类型的,转换成 boolean
                if ($column->size === 1 && $type === 'bit') {
                    $column->type = 'boolean';
                } elseif ($type === 'bit') {
                    // 由于bit最多64位,如果超过 32 位,那么用一个 bigint 足以。
                    if ($column->size > 32) {
                        $column->type = 'bigint';
                    // 如果正好32位,那么用一个 interger 来表示。
                    } elseif ($column->size === 32) {
                        $column->type = 'integer';
                    }
                }
            }
        }
    }

    // 获取PHP数据类型
    $column->phpType = $this->getColumnPhpType($column);

    // 处理默认值
    if (!$column->isPrimaryKey) {
        // timestamp 的话,要实际获取当前时间戳,而不能是字符串 'CURRENT_TIMESTAMP'
        if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP') {
            $column->defaultValue = new Expression('CURRENT_TIMESTAMP');

        // bit 的话,要截取对应的内容,并进行进制转换
        } elseif (isset($type) && $type === 'bit') {
            $column->defaultValue = bindec(trim($info['Default'],'b\''));

        // 其余类型的,直接转换成PHP类型的值
        } else {
            $column->defaultValue = $column->phpTypecast($info['Default']);
        }
    }
    return $column;
}

上面的代码是完整获取字段信息的过程,这里重点要看的,是获取数据类型部分的代码。结合映射表和 上面的代码,我们能够得出:

  • Yii通过 SHOW FULL COLUMNS FROM SQL语句获取字段信息,并存储在 $info 数组中。
  • 根据 $info['Type'] 获取字段类型信息。
  • 如果映射表里已经有映射关系的,直接通过映射表,获取相应的抽象类型。
  • 如果映射表没有的,默认地视字段的抽象类型为 TYPE_STRING
  • 对于枚举类型,除了转换成 TYPE_STRING 外,还要获取其枚举值,否则,类型信息不完整 。
  • 对于bit类型,在32位及32位以下时,使用 TYPE_INTEGER 抽象类型,在32位以上(bit最大为64 位)时,使用 TYPE_BIGINT 类型。

至于其他字段信息的获取,如是否允许为 NULL 之类,上面的代码中注释已经交待清楚了,大家这么聪 明,相信难不倒你们。需要稍稍注意的,就是默认值的处理,特别是 timestamp 类型默认值的处 理。

抽象类型转PHP类型

前面讲数据库类型转抽象类型时,在 yii\db\mysql\Schema::loadColumnSchema() 中,有一个语 句 $column->phpType = $this->getColumnPhpType($column); 我们只是简单地说是转换成 PHP 类型,就一笔带来了。其实,他就是把抽像类型转换成PHP类型的关键,让我们来看看这个 yii\db\Schema::getColumnPhpType()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected function getColumnPhpType($column)
{
    // 定义从抽象类型到PHP类型的映射
    static $typeMap = [
        'smallint' => 'integer',
        'integer' => 'integer',
        'bigint' => 'integer',
        'boolean' => 'boolean',
        'float' => 'double',
        'binary' => 'resource',
    ];

    // 除了上面的映射关系外,还有几个特殊情况:
    // 1. bigint字段,在64位环境下,且为singed时,使用integer来表示,否则string
    // 2. integer字段,在32位环境下,且为unsinged时,使用string表示,否则integer
    // 3. 映射中不存在的字段类型均使用string
    if (isset($typeMap[$column->type])) {
        if ($column->type === 'bigint') {
            return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string';
        } elseif ($column->type === 'integer') {
            return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
        } else {
            return $typeMap[$column->type];
        }
    } else {
        return 'string';
    }
}

首先不要惊讶于为什么这个方法不像数据库类与抽象类型转换时,放在MySQL子类Schema中实现。这是 由于我们现在讨论的是抽象类型到PHP类型的转换。我们说过,抽象类型是与数据库无关的,PHP类型更 是与数据库没有半毛钱关系,所以,放在基类 yii\db\Schema 中是合理的。这也是我们在平时在 编程时经常采用的划分基类与子类的一个常用准则。

虽然,我们说抽象数据类型有16种,但是到了PHP范畴,有的类型是没有意义的。在PHP中,数据库的字 段类型,都可以归结了 integer boolean double resourece string 5种。

  • TYPE_PKTYPE_BIGPK 表示主键,Yii使用 yii\db\ColumnSchema::isPrimaryKey 属性来表示,不存在将其转换成何种数据类型的问题。
  • TYPE_SMALLINT TYPE_INTEGER TYPE_BIGINT 等字段一般都转换成PHP的 integer 类型。特别是 TYPE_SMALLINT ,PHP的 integer 完全可以满足要求。
  • TYPE_BIGINT 字段,如果是在64位环境下,且该字段并非为 unsigned 时,PHP的 integer 足够存储。但是,在32位环境下,PHP的 integer 只有4个字节,不够存储8个字节的 TYPE_BIGINT 。而如果字段是 unsigned 的,由于PHP并没有 unsigned 一说,就算是 64位环境,也少了1 bit。在不够存储时,就只能选用PHP的 string 类型了。
  • TYPE_INTEGER 字段,在32位环境下,如果是 unsigned 的,那也会少1 bit来存储。而如 果是64位环境,或者并非是 unsigned 的,PHP的 integer 都是够用的。同样,不够存储时 ,就使用字符串类型了。
  • TYPE_FLOAT 字段,注意是转换成PHP的 double 而非 floatfloat 精度不够。
  • TYPE_BOOLEAN 字段,顺理成章对应PHP的 boolean 类型。
  • TYPE_BINARY 字段,理所当然对应PHP的 resource 类型。
  • TYPE_STRINGTYPE_TEXT 字段,显而易见对应 PHP的 string 类型。
  • TYPE_DECIMALTYPE_MONEY 字段由于PHP的数值类型精度都不够,所以, 只能使用 string 类型来表示。
  • 对于日期、时间等字段,尽管PHP提供了丰富的日期、时间函数,但事实上,PHP中并没有专门的表示 日期、时间的数据类型。因此,对于这类字段,又只能求助于万能的 string 类型了。

字段内容转PHP变量

前面我们有了数据库类型转抽象类型,抽象类型转PHP类型,那么,我们就可以完成数据库类型到PHP类 型的转换,也就是说,能够将数据库中的内容读取到PHP中供我们编程时使用啦。

这个转换过程就在 yii\db\ColumnSchema::phpTypeCast() 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 该方法用于把 $value 转换成 php 变量,其中 $value 是PDO从数据库中读取的内容
// 主要参考的是字段的抽象类型 $type 和 PHP类型 $phpType
public function phpTypecast($value)
{
    // 内容为空时,若不是字符串或二进制抽象类型,则是NULL的意思。
    if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) {
        return null;
    }

    // 内容为null,或者 $value 的类型与PHP类型一致,或者 $value 是一个数据库表达式,
    // 那么可以直接返回
    if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
        return $value;
    }

    // 否则,需要根据PHP类型来完成类型转换
    switch ($this->phpType) {
        case 'resource':
        case 'string':
            return is_resource($value) ? $value : (string) $value;
        case 'integer':
            return (int) $value;
        case 'boolean':
            return (bool) $value;
        case 'double':
            return (double) $value;
    }
    return $value;
}

在上面的转换过程中,PHP类型信息已经在上一步的 yii\db\Schema::getColumnPhpType() 中获 取了。这里就是把从数据库中得到的数据,转换成 phpType 所指定的PHP变量类型。

前面我们讲过,16种抽象类型最终对应于PHP的5种数据类型。但是,从上面的 phpTypeCast() 来 看,还要多出2种类型来:

  • 一是 null ,对于既不是文本类型,又不是二进制类型的字段,如果其内容为空,那么他的意思 其实是 NULL
  • 二是 yii\db\Expression 数据库表达式类型。如前面我们提到的 timestamp 的默认值是 CURRENT_TIMESTAMP 时,就用到了一个 new Expression('CURRENT_TIMESTAMP')

所谓擒贼先擒王,读代码先读基类,我们就先来看看这个 yii\db\Schema 为数据类型的转换作了 什么吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Schema extends Object
{
    // 将一个PHP数据类型转换成PDO数据类型
    public function getPdoType($data)
    {
        // 定义一个PHP类型到PDO类型的映射表
        static $typeMap = [
            'boolean' => \PDO::PARAM_BOOL,
            'integer' => \PDO::PARAM_INT,
            'string' => \PDO::PARAM_STR,
            'resource' => \PDO::PARAM_LOB,
            'NULL' => \PDO::PARAM_NULL,
        ];
        $type = gettype($data);

        // 在匹配不上映射表时,采用字符串类型
        return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
    }

    // ... ...
}

从抽象类 yii\db\Schema 来看,

表信息(Table Schema)

yii\db\TableSchema 类用于描述数据表的信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class TableSchema extends Object
{
    public $schemaName;             // 所属的Schema
    public $name;                   // 表名,不包含Schema部分
    public $fullName;               // 表的完整名称,可能包含一个Schema前缀。
    public $primaryKey = [];        // 主键
    public $sequenceName;           // 主键若使用sequence,该属性表示序列名
    public $foreignKeys = [];       // 外键
    public $columns = [];           // 字段

    // ... ...
}

从上面的代码来看, yii\db\TableSchema 比较简单。上述的属性看一看就大致可以了解是干什么 用的。这里我们点一点,了解下就可以了。

列信息(Column Schema)

yii\db\ColumnSchema 类用于描述一个字段的信息,让我们来看一看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class ColumnSchema extends Object
{
    public $name;               // 字段名
    public $allowNull;          // 是否可以为NULL
    /**
     * @var string abstract type of this column. Possible abstract types include:
     * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
     * timestamp, time, date, binary, and money.
     */
    public $type;               // 字段的类型
    /**
     * @var string the PHP type of this column. Possible PHP types include:
     * `string`, `boolean`, `integer`, `double`.
     */
    public $phpType;            // 字段类型对应的PHP数据类型
    /**
     * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
     */
    public $dbType;
    public $defaultValue;       // 字段默认值
    public $enumValues;         // 若字段为枚举类型,该属性用于表示可供枚举的值
    /**
     * @var integer display size of the column.
     */
    public $size;
    public $precision;          // 若字段为数值,该属性用于表示精度
    /**
     * @var integer scale of the column data, if it is numeric.
     */
    public $scale;
    /**
     * @var boolean whether this column is a primary key
     */
    public $isPrimaryKey;       // 是否是主键
    public $autoIncrement = false;      // 是否是自增长字段
    /**
     * @var boolean whether this column is unsigned. This is only meaningful
     * when [[type]] is `smallint`, `integer` or `bigint`.
     */
    public $unsigned;           // 是否是unsigned,仅对支持的类型有效
    public $comment;            // 字段描述信息
    /**
     * Converts the input value according to [[phpType]] after retrieval from the database.
     * If the value is null or an [[Expression]], it will not be converted.
     * @param mixed $value input value
     * @return mixed converted value
     */
    public function phpTypecast($value)
    {
        if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) {
            return null;
        }
        if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
            return $value;
        }
        switch ($this->phpType) {
            case 'resource':
            case 'string':
                return is_resource($value) ? $value : (string) $value;
            case 'integer':
                return (int) $value;
            case 'boolean':
                return (bool) $value;
            case 'double':
                return (double) $value;
        }
        return $value;
    }
    /**
     * Converts the input value according to [[type]] and [[dbType]] for use in a db query.
     * If the value is null or an [[Expression]], it will not be converted.
     * @param mixed $value input value
     * @return mixed converted value. This may also be an array containing the value as the first element
     * and the PDO type as the second element.
     */
    public function dbTypecast($value)
    {
        // the default implementation does the same as casting for PHP but it should be possible
        // to override this with annotation of explicit PDO type.
        return $this->phpTypecast($value);
    }
}
如果觉得《深入理解Yii2.0》对您有所帮助,也请帮助《深入理解Yii2.0》。 谢谢!