1. 数据类型
各 DBMS 间,最明显、最常见的差异就在于所支持、实现的数据类型不同。Yii 的一个重要任务,就是消除这些区别,提供一个统一的开发界面供开发者使用。
1.1 抽象数据类型
在使用 Yii 进行数据库开发时,涉及到 2 个方面的数据类型:PHP 自身的数据类型,DBMS 的数据类型。其中,PHP 数据类型比较好整,反正就那么几个,跟平台、环境的关系也比较清楚。复杂的地方在于 DBMS 的数据类型。
因此,Yii 不但需要搞定各 DBMS 间数据类型的差异,还需要搞定 PHP 与 DBMS 间数据类类型的差异。因此,Yii 引入了一个逻辑层面的数据类型,来统一 PHP 与 DBMS,以及各 DBMS 之间数据类型的差异。这里我们把这个逻辑层面的数据类型称为抽象类型,在 yii\db\Schema 中进行定义:
<?abstract class Schema extends BaseObject {// 预定义抽象字段类型const TYPE_PK = 'pk';const TYPE_UPK = 'upk';const TYPE_BIGPK = 'bigpk';const TYPE_UBIGPK = 'ubigpk';const TYPE_CHAR = 'char';const TYPE_STRING = 'string';const TYPE_TEXT = 'text';const TYPE_TINYINT = 'tinyint';const TYPE_SMALLINT = 'smallint';const TYPE_INTEGER = 'integer';const TYPE_BIGINT = 'bigint';const TYPE_FLOAT = 'float';const TYPE_DOUBLE = 'double';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';const TYPE_JSON = 'json';}
这些类型与 DBMS 无关,在具体到特定的 DBMS 时,Yii 会自动转换成合适的数据库字段类型。我们在编程中,若需要指定字段类型,比如创建数据库之类,就使用这些抽象类型。这样的话,就不用考虑使用的类型具体的 DBMS 是否支持的问题了,可以使我们更加专注于开发。
1.2 数据类型转换
既然 Yii 使用抽象数据类型来统一控制,那么无可避免的,涉及到 PHP 数据类型和 DBMS 数据类型与抽象类型的转换问题。
1.2.1 抽象类型转数据库类型
首先来看看一个基类 yii\db\QueryBuilder :
<?class QueryBuilder extends \yii\base\BaseObject {// $typeMap 用于定义抽象数据类型到 DBMS 数据类型的映射关系,具体由各 QueryBuilder 子类实现。public $typeMap = [];// 将抽象数据类型转换成合适的 DBMS 数据类型public function getColumnType($type) {if ($type instanceof ColumnSchemaBuilder) {$type = $type->__toString();}// 映射表中已经有的,直接使用映射的类型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 中:
<?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_UPK => 'int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',Schema::TYPE_UBIGPK => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY',Schema::TYPE_CHAR => 'char(1)',Schema::TYPE_STRING => 'varchar(255)',Schema::TYPE_TEXT => 'text',Schema::TYPE_TINYINT => 'tinyint(3)',Schema::TYPE_SMALLINT => 'smallint(6)',Schema::TYPE_INTEGER => 'int(11)',Schema::TYPE_BIGINT => 'bigint(20)',Schema::TYPE_FLOAT => 'float',Schema::TYPE_DOUBLE => 'double',Schema::TYPE_DECIMAL => 'decimal(10,0)',Schema::TYPE_DATE => 'date',Schema::TYPE_BINARY => 'blob',Schema::TYPE_BOOLEAN => 'tinyint(1)',Schema::TYPE_MONEY => 'decimal(19,4)',Schema::TYPE_JSON => 'json'];// ... ...}
对于这些抽象数据类型而言,都可以转换成 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 NULL , DEFAULT 等。 yii\db\QueryBuilder::getColumnType() 也是能够识别出来并加以处理的。
1.2.2 数据库类型转抽象类型
反过来数据库的数据类型,转换成抽象数据类型。仍然以 MySQL 数据库为例,具体代码在 yii\db\mysql\Schema 中:
<?phpnamespace yii\db\mysql;class Schema extends \yii\db\Schema implements ConstraintFinderInterface {// 定义从数据库数据类型到抽象数据类型间的映射关系public $typeMap = ['tinyint' => self::TYPE_TINYINT,'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_DOUBLE,'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_CHAR,'datetime' => self::TYPE_DATETIME,'year' => self::TYPE_DATE,'date' => self::TYPE_DATE,'time' => self::TYPE_TIME,'timestamp' => self::TYPE_TIMESTAMP,'enum' => self::TYPE_STRING,'varbinary' => self::TYPE_BINARY,'json' => self::TYPE_JSON,];}
上面只是一个映射表,具体转换过程在生成字段信息 yii\db\ColumnSchema 时进行。 yii\db\ColumnSchema 保存了一个字段的各种相关信息,包括字段类型等。
字段信息的获取和填充,又发生在 yii\db\Schema::loadTableSchema() 中。该函数用于加载数据表信息 yii\db\TableSchema ,这是一个抽象函数,具体由各子类实现。
