1. 数据类型

各 DBMS 间,最明显、最常见的差异就在于所支持、实现的数据类型不同。Yii 的一个重要任务,就是消除这些区别,提供一个统一的开发界面供开发者使用。

1.1 抽象数据类型

在使用 Yii 进行数据库开发时,涉及到 2 个方面的数据类型:PHP 自身的数据类型,DBMS 的数据类型。其中,PHP 数据类型比较好整,反正就那么几个,跟平台、环境的关系也比较清楚。复杂的地方在于 DBMS 的数据类型。
因此,Yii 不但需要搞定各 DBMS 间数据类型的差异,还需要搞定 PHP 与 DBMS 间数据类类型的差异。因此,Yii 引入了一个逻辑层面的数据类型,来统一 PHP 与 DBMS,以及各 DBMS 之间数据类型的差异。这里我们把这个逻辑层面的数据类型称为抽象类型,在 yii\db\Schema 中进行定义:

  1. <?
  2. abstract class Schema extends BaseObject {
  3. // 预定义抽象字段类型
  4. const TYPE_PK = 'pk';
  5. const TYPE_UPK = 'upk';
  6. const TYPE_BIGPK = 'bigpk';
  7. const TYPE_UBIGPK = 'ubigpk';
  8. const TYPE_CHAR = 'char';
  9. const TYPE_STRING = 'string';
  10. const TYPE_TEXT = 'text';
  11. const TYPE_TINYINT = 'tinyint';
  12. const TYPE_SMALLINT = 'smallint';
  13. const TYPE_INTEGER = 'integer';
  14. const TYPE_BIGINT = 'bigint';
  15. const TYPE_FLOAT = 'float';
  16. const TYPE_DOUBLE = 'double';
  17. const TYPE_DECIMAL = 'decimal';
  18. const TYPE_DATETIME = 'datetime';
  19. const TYPE_TIMESTAMP = 'timestamp';
  20. const TYPE_TIME = 'time';
  21. const TYPE_DATE = 'date';
  22. const TYPE_BINARY = 'binary';
  23. const TYPE_BOOLEAN = 'boolean';
  24. const TYPE_MONEY = 'money';
  25. const TYPE_JSON = 'json';
  26. }

这些类型与 DBMS 无关,在具体到特定的 DBMS 时,Yii 会自动转换成合适的数据库字段类型。我们在编程中,若需要指定字段类型,比如创建数据库之类,就使用这些抽象类型。这样的话,就不用考虑使用的类型具体的 DBMS 是否支持的问题了,可以使我们更加专注于开发。

1.2 数据类型转换

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

1.2.1 抽象类型转数据库类型

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

  1. <?
  2. class QueryBuilder extends \yii\base\BaseObject {
  3. // $typeMap 用于定义抽象数据类型到 DBMS 数据类型的映射关系,具体由各 QueryBuilder 子类实现。
  4. public $typeMap = [];
  5. // 将抽象数据类型转换成合适的 DBMS 数据类型
  6. public function getColumnType($type) {
  7. if ($type instanceof ColumnSchemaBuilder) {
  8. $type = $type->__toString();
  9. }
  10. // 映射表中已经有的,直接使用映射的类型
  11. if (isset($this->typeMap[$type])) {
  12. return $this->typeMap[$type];
  13. // 是不是形如 "Schema::TYPE_INT(11) DEFAULT 0"
  14. } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
  15. if (isset($this->typeMap[$matches[1]])) {
  16. return preg_replace('/\(.+\)/', '(' . $matches[2] . ')',
  17. $this->typeMap[$matches[1]]) . $matches[3];
  18. }
  19. // 看看是不是形如 "Schema::TYPE_INT NOT NULL"
  20. } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
  21. if (isset($this->typeMap[$matches[1]])) {
  22. return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
  23. }
  24. }
  25. return $type;
  26. }
  27. // ... ...
  28. }

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

  1. <?
  2. namespace yii\db\mysql;
  3. class QueryBuilder extends \yii\db\QueryBuilder {
  4. public $typeMap = [
  5. Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
  6. Schema::TYPE_UPK => 'int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
  7. Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
  8. Schema::TYPE_UBIGPK => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',
  9. Schema::TYPE_CHAR => 'char(1)',
  10. Schema::TYPE_STRING => 'varchar(255)',
  11. Schema::TYPE_TEXT => 'text',
  12. Schema::TYPE_TINYINT => 'tinyint(3)',
  13. Schema::TYPE_SMALLINT => 'smallint(6)',
  14. Schema::TYPE_INTEGER => 'int(11)',
  15. Schema::TYPE_BIGINT => 'bigint(20)',
  16. Schema::TYPE_FLOAT => 'float',
  17. Schema::TYPE_DOUBLE => 'double',
  18. Schema::TYPE_DECIMAL => 'decimal(10,0)',
  19. Schema::TYPE_DATE => 'date',
  20. Schema::TYPE_BINARY => 'blob',
  21. Schema::TYPE_BOOLEAN => 'tinyint(1)',
  22. Schema::TYPE_MONEY => 'decimal(19,4)',
  23. Schema::TYPE_JSON => 'json'
  24. ];
  25. // ... ...
  26. }

对于这些抽象数据类型而言,都可以转换成 MySQL 的特定数据类型。这里我们看到, TYPE_MONEY 本来是 MySQL 所没有的数据类型,但通过将其映射成了 decimal(19,4) ,使得 MySQL 也可以支持 TYPE_MONEY 类型了。
yii\db\QueryBuilder::getColumnType() 实现了抽象数据类型到具体 DBMS 数据类型的转换。在具体的转换过程中,如果指定一个字段为 Schema::TYPE_STRING 之类的,那么就会被转换成 varchar(255) 。要是想指定成 varchar(64) 就直接使用 Schema::TYPE_STRING(64) 。还可以在这些抽象类型后面使用 NOT NULLDEFAULT 等。 yii\db\QueryBuilder::getColumnType() 也是能够识别出来并加以处理的。

1.2.2 数据库类型转抽象类型

反过来数据库的数据类型,转换成抽象数据类型。仍然以 MySQL 数据库为例,具体代码在 yii\db\mysql\Schema 中:

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

上面只是一个映射表,具体转换过程在生成字段信息 yii\db\ColumnSchema 时进行。 yii\db\ColumnSchema 保存了一个字段的各种相关信息,包括字段类型等。
字段信息的获取和填充,又发生在 yii\db\Schema::loadTableSchema() 中。该函数用于加载数据表信息 yii\db\TableSchema ,这是一个抽象函数,具体由各子类实现。