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
中:
<?php
namespace 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
,这是一个抽象函数,具体由各子类实现。