soluble/metadata

从数据库查询中提取元数据

1.3.2 2018-12-22 11:25 UTC

This package is auto-updated.

Last update: 2024-09-20 06:26:57 UTC


README

PHP Version PHP Version Build Status codecov Scrutinizer Quality Score Latest Stable Version Total Downloads License

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 提供更多功能)

文档

安装

通过 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:获取数据类型额外信息

以下方法受支持,并且可以在 mysqliPDO_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_mysqlmysqli 驱动程序中给出不同的结果。如果需要可移植性,请谨慎使用它们!!!

<?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项目,该项目在相同的数据类型标准下共享,同时以更便携的方式公开更多信息,如外键等。

编码标准