soluble / metadata
从数据库查询中提取元数据
Requires
- php: ^7.1
- soluble/datatype: ^0.11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.13
- phpstan/phpstan: ^0.10.5
- phpstan/phpstan-phpunit: ^0.10
- phpstan/phpstan-strict-rules: ^0.10
- phpunit/phpunit: ^7.5
- soluble/dbwrapper: ^1.3.2
README
soluble-metadata
是一个 低级 库,目前专注于 MySQL,它以可扩展性、速度和便携性为出发点,从 SQL 查询中提取元数据。
用例
您可以利用 soluble/metadata 按照它们的类型(当渲染 HTML 表格、生成 Excel 表格...)格式化/渲染结果查询数据,进行基本验证(最大长度、小数...)...
特性
- 从 SQL 查询中提取元数据信息(数据类型等)
- 跨各种驱动实现提供通用 API。
- 依赖原生数据库驱动信息(不解析 PHP 中的查询)
- 即使查询没有返回结果(空结果集)也能正常工作。
- 经过不同实现(libmariadb、mysqlnd、libmysql、pdo_mysql)的仔细测试。
底层,元数据提取依赖于驱动方法
mysqli_stmt::result_metadata()
和PDO::getColumnMeta()
。尽管soluble-metadata
API 统一了它们的用法和类型检测,但更高级的功能仍存在差异。在文档中已做出特别努力来区分在不同驱动之间切换时可能出现的可移植性问题。在使用时请注意。
要求
- PHP 引擎 7.1+(v1.2.0)、7.0+ 和 5.4(v1.0.0)
- 启用 Mysqli 或 PDO_mysql 扩展 (Mysqli 提供更多功能)
文档
- 此 README 和 API 文档 可用。
安装
通过 composer 立即安装。
$ composer require soluble/metadata
大多数现代框架都会包含 composer,但请确保包含以下文件
<?php // include the Composer autoloader require 'vendor/autoload.php';
基本示例
<?php use Soluble\Metadata\Reader; use Soluble\Datatype\Column\Type as DataType; $conn = new \mysqli($hostname,$username,$password,$database); $conn->set_charset($charset); $metaReader = new Reader\MysqliMetadataReader($conn); $sql = "select * from `my_table`"; try { $md = $metaReader->getColumnsMetadata($sql); } catch (\Soluble\Metadata\Exception\InvalidQueryException $e) { // ... } foreach($md as $column_name => $col_def) { $datatype = $col_def->getDatatype(); echo $column_name . "\t" . $datatype . "\t"; echo ($col_def->isNullable() ? 'Y' : 'N') . '\t'; switch ($datatype) { case DataType::TYPE_STRING: // equivalent to 'string' echo $col_def->getCharacterOctetLength() . "\t"; break; case DataType::TYPE_INTEGER: echo ($col_def->isNumericUnsigned() ? 'Y' : 'N') . "\t"; break; case DataType::TYPE_DECIMAL: echo ($col_def->isNumericUnsigned() ? 'Y' : 'N') . "\t"; echo $col->getNumericPrecision() . "\t"; // For DECIMAL(5,2) -> precision = 5 echo $col->getNumericScale() . "\t"; // For DECIMAL(5,2) -> scale = 2 break; // ...see the doc for more possibilitities } echo $col_def->getNativeType() . PHP_EOL; }
可以打印类似以下内容
...
用法
步骤 1. 初始化元数据读取器
-
对于 Mysqli:将现有的 mysqli 连接发送到
MysqlMetadataReader
<?php use Soluble\Metadata\Reader; $conn = new \mysqli($hostname,$username,$password,$database); $conn->set_charset($charset); $reader = new Reader\MysqliMetadataReader($conn);
-
对于 Pdo_mysql:将现有的 pdo_mysql 连接发送到
PdoMysqlReader
<?php use Soluble\Metadata\Reader; $conn = new \PDO("mysql:host=$hostname", $username, $password, [ \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" ]); $reader = new Reader\PDOMysqlMetadataReader($conn);
步骤 2. 从 SQL 查询中提取元数据
<?php //.... $reader = new Reader\MysqliMetadataReader($conn); $sql = " SELECT `p`.`post_id`, `p`.`title` AS `post_title` `p`.`created_at`, 'constant' AS `constant_col`, 1 + 2 AS `computed_col`, null as `null_col` COUNT(`c`.*) as `nb_comments`, MAX(`c`.`created_at`) as latest_comment FROM `post` AS `p` LEFT OUTER JOIN `comment` as `c` ON `c`.`post_id` = `p`.`post_id` GROUP BY `p`.`post_id`, `p`.`title`, `p`.`created_at`, `constant_col`, `computed_col`, `null_col` "; try { $meta = $reader->getColumnsMetadata($sql); } catch (\Soluble\Metadata\Exception\InvalidQueryException $e) { //... } /* The resulting ColumnsMetadata will contain something like: [ "post_id" => '<Soluble\Datatype\Column\Definition\IntegerColumn>', "post_title" => '<Soluble\Datatype\Column\Definition\StringColumn>', "created_at" => '<Soluble\Datatype\Column\Definition\DatetimeColumn>', "constant_col" => '<Soluble\Datatype\Column\Definition\StringColumn>', "computed_col" => '<Soluble\Datatype\Column\Definition\IntegerColumn>', "null_col" => '<Soluble\Datatype\Column\Definition\NullColumn>', "nb_comments" => '<Soluble\Datatype\Column\Definition\IntegerColumn>', "latest_comment" => '<Soluble\Datatype\Column\Definition\DateTimeColumn>' ] */
或者,当您想从表获取元数据时,可以使用辅助方法
$reader->getTableMetadata($table)
。
步骤 3:获取列类型(4 种选项)
<?php // ... $meta = $reader->getColumnsMetadata($sql); // Retrieve a specific column (i.e. 'post_title') // Note the parameter is the column alias if defined $col = $meta->getColumn('post_title'); // Type detection // ---------------------- // Option 1, type detection by datatype name // ------------------------------------------ echo $col->getDatatype(); // -> 'string' (equivalent to Soluble\Datatype\Column\Type::TYPE_STRING) /* The normalized datatypes are defined in the Soluble\Datatype\Column\Type::TYPE_(*) and can be : 'string', 'integer', 'decimal', 'float', 'boolean', 'datetime', 'date', 'time', 'bit', 'spatial_geometry' */ // Option 2, type detection by classname // -------------------------------------- if ($col instanceof \Soluble\Datatype\Column\IntegerColumn) { // ... could be also BitColumn, BlobColumn, BooleanColumn // ... DateColumn, DateTimeColumn, DecimalColumn, FloatColumn // ... GeometryColumn, IntegerColumn, StringColumn, TimeColumn, // ... NullColumn } // Option 3, type detection by interface (more generic) // ----------------------------------------------------- if ($col instanceof \Soluble\Datatype\Column\NumericColumnInterface) { // ... for example NumericColumnInterface // ... includes DecimalColumn, FloatColumn, IntegerColumn } // Option 4, type detection by helper functions (more generic) // ----------------------------------------------------------- $col->isText(); // Whether the column contains text (CHAR, VARCHAR, ENUM...) $col->isNumeric(); // Whether the column is numeric (INT, DECIMAL, FLOAT...) $col->isDatetime(); // Whether the column is a datetime (DATETIME) $col->isDate(); // Whther the column is a date (DATE)
步骤 4:获取数据类型额外信息
以下方法受支持,并且可以在 mysqli
和 PDO_mysql
驱动程序之间便携。
<?php // ... // For all types // ------------- echo $col->getOrdinalPosition(); // -> 2 (column position in the query) echo $col->isNullable() ? 'nullable' : 'not null'; echo $col->isPrimary() ? 'PK' : ''; // Many columns may have the primary flag // The meaning of it depends on your query // For integer and decimal types echo $col->isNumericUnsigned(); // Whether the numeric value is unsigned. // For decimal based types // ----------------------- echo $col->getNumericPrecision(); // For DECIMAL(5,2) -> 5 is the precision echo $col->getNumericScale(); // For DECIMAL(5,2) -> 2 is the scale // For character/blob based types // ------------------------------ echo $col->getCharacterOctetLength(); // Octet length (in multibyte context length might differs)
获取列规范。
以下方法也便携。
<?php // ... echo $col->getAlias(); // Column alias name -> "post_title" (or column name if not aliased) echo $col->isComputed(); // Whenever there's no table linked (for GROUP, constants, expression...) echo $col->getTableAlias(); // Originating table alias -> "p" (or table name if not aliased) // If empty, the column is computed (constant, group,...)
以下示例中使用的示例方法在
pdo_mysql
和mysqli
驱动程序中给出不同的结果。如果需要可移植性,请谨慎使用它们!!!
<?php // ... echo $col->getTableName(); // Originating table -> "post" // (*) PDO_mysql always return the table alias if aliased echo $col->getName(); // Column original name -> "title". // (*) PDO_mysql always return the alias if aliased echo $col->getNativeType(); // Return the column definition native type // i.e: BIGINT, SMALLINT, VARCHAR, ENUM // (*) PDO_mysql consider // - ENUM, SET and VARCHAR as CHAR echo $col->isGroup(); // Whenever the column is part of a group (MIN, MAX, AVG,...) // (*) PDO_mysql is not able to retrieve group information // (*) Mysqli: detection of group is linked to the internal driver // Check your driver with mysqli_get_client_version(). // - mysqlnd detects: // - COUNT, MIN, MAX // - libmysql detects: // - COUNT, MIN, MAX, AVG, GROUP_CONCAT // - libmariadb detects: // - COUNT, MIN, MAX, AVG, GROUP_CONCAT and growing // For numeric types // ----------------- echo $col->isAutoIncrement(); // Only make sense for primary keys. // (*) Unsupported with PDO_mysql echo $col->isNumericUnsigned(); // Whether the numeric value is unsigned. // (*) Unsupported with PDO_mysql
不受支持的方法
这些方法在 mysqli 和 PDO_mysql 实现中仍然不受支持,但保留为参考。
<?php // ... echo $col->getColumnDefault(); // Always return null echo $col->getCharacterMaximumLength(); // Returns $col->getCharacterOctetLength() // and does not (yet) handle multibyte aspect.
API
AbstractMetadataReader
使用 Reader\AbstractMetadataReader::getColumnsMetadata($sql)
提取查询元数据。
<?php use Soluble\Metadata\Reader; use PDO; $conn = new PDO("mysql:host=$hostname", $username, $password, [ PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" ]); $reader = new Reader\PdoMysqlMetadataReader($conn); $sql = "select id, name from my_table"; $columnsMeta = $reader->getColumnsMetadata($sql);
ColumnsMetadata
Soluble\Metadata\ColumnsMetadata
允许遍历列信息或返回特定列作为 Soluble\Datatype\Column\Definition\AbstractColumnDefinition
。
<?php $reader = new Reader\PdoMysqlMetadataReader($conn); $sql = "select id, name from my_table"; $columnsMeta = $reader->getColumnsMetadata($sql); foreach ($columnsMeta as $col_name => $col_def) { echo $coldev->getDatatype() . PHP_EOL; } $col = $columnsMeta->getColumn('id'); echo $col->getDatatype();
AbstractColumnDefinition
元数据信息存储为 Soluble\Datatype\Column\Definition\AbstractColumnDefinition
对象,该对象
AbstractColumnDefinition 实现
以下是 Soluble\Datatype\Column\Definition\AbstractColumnDefinition
的具体实现列表。
它们可以用作检查列数据类型的替代方法。例如
use Soluble\Datatype\Column\Definition; if ($coldef instanceof Definition\DateColumnInterface) { // equivalent to // if ($coldef->isDate()) { $date = new \DateTime($value); echo $value->format('Y'); } elseif ($coldef instanceof Definition\NumericColumnInterface) { echo number_format($value, $coldef->getNumericPrecision); }
支持读取器
目前仅支持 pdo_mysql 和 mysqli 驱动程序。
未来想法
- 实现更多驱动程序(pgsql...),欢迎贡献!!!
贡献
欢迎贡献,请参阅贡献指南
注意
目前,元数据是通过执行带有限制0的查询从底层数据库驱动读取的(几乎没有性能损失)。这确保您的查询始终被正确解析(即使是疯狂的查询),几乎不需要任何努力。
底层驱动方法 mysqli_stmt::result_metadata()
、PDO::getColumnMeta()
分别由 Mysql 和 PdoMysql 元数据读取器使用,在 PHP 网站上标记为实验性,可能发生变化。实际上,它们自 5.4 以来没有变化,是稳定的。如果 PHP 驱动程序发生变化,添加特定驱动程序应该非常容易。
遗憾的是,PDO_mysql 和 mysqli 在功能方面存在一些差异。通常最好使用 mysqli 而不是 pdo。PDO 缺少一些功能,如自动增长的检测、enum、set、unsigned、分组列,并且不区分表/列别名及其原始表/列名。
如果您想依赖这个特定的功能(别名),请查看类似phpmyadmin sql-parser的替代方案。
此外,如果您正在寻找一个更高级的元数据读取器(但仅限于表 - 不是查询),请查看soluble-schema项目,该项目在相同的数据类型标准下共享,同时以更便携的方式公开更多信息,如外键等。