使用PHP将输入的SQL查询格式化为HTML?

15
我正在寻找一个 PHP 函数,可以将格式不规范的查询语句像这样:

$sql = "select name, size from things where color = 'green' order by price asc";

转换成在 HTML 页面上显示如下的格式:
SELECT
    name, size
FROM
    things
WHERE
    color = 'green'
ORDER BY
    price ASC';
phpMyAdmin 中已经有一些代码可以实现这个功能,我可以在那里查看相关内容。请注意保留原有的HTML标记。

7
你可以...什么问题? - Alix Axel
7个回答

30

我遇到了同样的问题,并编写了一个轻量级的 PHP 类来进行格式化/语法高亮。

https://github.com/jdorn/sql-formatter

我尚未对其在复杂查询(子查询、联合等)中进行全面测试,但它似乎可以很好地处理常见情况。

要获得完全准确的结果,您真正需要像 phpMyAdmin 使用的完整 SQL 解析器,但这需要超过 10,000 行代码分散在多个文件中,对于简单的调试可能过于繁琐。


美妙!在我的 SQL 构建器中使用 "beautiful!" 来显示结果。它完美地工作,只需插入即可。 - Luke
太好了!来自哥伦比亚的问候。 - JuanFernandoz
太好了!我希望我能给多个赞。虽然我不明白为什么这个答案没有被采纳。 - Ganesh Salunkhe
Ashish Makhija 的 lower response 功能在单个文件中也有相同的功能,只是没有颜色。 - FantomX1

12
function getFormattedSQL($sql_raw)
{
 if( empty($sql_raw) || !is_string($sql_raw) )
 {
  return false;
 }

 $sql_reserved_all = array (
     'ACCESSIBLE', 'ACTION', 'ADD', 'AFTER', 'AGAINST', 'AGGREGATE', 'ALGORITHM', 'ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AND', 'AS', 'ASC',
     'AUTOCOMMIT', 'AUTO_INCREMENT', 'AVG_ROW_LENGTH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINLOG', 'BOTH', 'BY', 'CASCADE', 'CASE', 'CHANGE', 'CHANGED',
     'CHARSET', 'CHECK', 'CHECKSUM', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPRESSED', 'CONCURRENT', 
     'CONSTRAINT', 'CONTAINS', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY', 'DAY_HOUR', 'DAY_MINUTE', 
     'DAY_SECOND', 'DEFINER', 'DELAYED', 'DELAY_KEY_WRITE', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV',
     'DO', 'DROP', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'ELSE', 'ENCLOSED', 'END', 'ENGINE', 'ENGINES', 'ESCAPE', 'ESCAPED', 'EVENTS', 'EXECUTE',
     'EXISTS', 'EXPLAIN', 'EXTENDED', 'FAST', 'FIELDS', 'FILE', 'FIRST', 'FIXED', 'FLUSH', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULL', 'FULLTEXT',
     'FUNCTION', 'GEMINI', 'GEMINI_SPIN_RETRIES', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP', 'HAVING', 'HEAP', 'HIGH_PRIORITY', 'HOSTS', 'HOUR', 'HOUR_MINUTE',
     'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IGNORE', 'IN', 'INDEX', 'INDEXES', 'INFILE', 'INNER', 'INSERT', 'INSERT_ID', 'INSERT_METHOD', 'INTERVAL',
     'INTO', 'INVOKER', 'IS', 'ISOLATION', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LAST_INSERT_ID', 'LEADING', 'LEFT', 'LEVEL', 'LIKE', 'LIMIT', 'LINEAR',               
     'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOCKS', 'LOGS', 'LOW_PRIORITY', 'MARIA', 'MASTER', 'MASTER_CONNECT_RETRY', 'MASTER_HOST', 'MASTER_LOG_FILE',
     'MASTER_LOG_POS', 'MASTER_PASSWORD', 'MASTER_PORT', 'MASTER_USER', 'MATCH', 'MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR',
     'MAX_ROWS', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 'MEDIUM', 'MERGE', 'MINUTE', 'MINUTE_SECOND', 'MIN_ROWS', 'MODE', 'MODIFY',
     'MONTH', 'MRG_MYISAM', 'MYISAM', 'NAMES', 'NATURAL', 'NOT', 'NULL', 'OFFSET', 'ON', 'OPEN', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR',
     'ORDER', 'OUTER', 'OUTFILE', 'PACK_KEYS', 'PAGE', 'PARTIAL', 'PARTITION', 'PARTITIONS', 'PASSWORD', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE',
     'PROCESS', 'PROCESSLIST', 'PURGE', 'QUICK', 'RAID0', 'RAID_CHUNKS', 'RAID_CHUNKSIZE', 'RAID_TYPE', 'RANGE', 'READ', 'READ_ONLY',            
     'READ_WRITE', 'REFERENCES', 'REGEXP', 'RELOAD', 'RENAME', 'REPAIR', 'REPEATABLE', 'REPLACE', 'REPLICATION', 'RESET', 'RESTORE', 'RESTRICT',
     'RETURN', 'RETURNS', 'REVOKE', 'RIGHT', 'RLIKE', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SECOND', 'SECURITY', 'SELECT', 'SEPARATOR',
     'SERIALIZABLE', 'SESSION', 'SET', 'SHARE', 'SHOW', 'SHUTDOWN', 'SLAVE', 'SONAME', 'SOUNDS', 'SQL', 'SQL_AUTO_IS_NULL', 'SQL_BIG_RESULT',
     'SQL_BIG_SELECTS', 'SQL_BIG_TABLES', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_CALC_FOUND_ROWS', 'SQL_LOG_BIN', 'SQL_LOG_OFF',
     'SQL_LOG_UPDATE', 'SQL_LOW_PRIORITY_UPDATES', 'SQL_MAX_JOIN_SIZE', 'SQL_NO_CACHE', 'SQL_QUOTE_SHOW_CREATE', 'SQL_SAFE_UPDATES',
     'SQL_SELECT_LIMIT', 'SQL_SLAVE_SKIP_COUNTER', 'SQL_SMALL_RESULT', 'SQL_WARNINGS', 'START', 'STARTING', 'STATUS', 'STOP', 'STORAGE',
     'STRAIGHT_JOIN', 'STRING', 'STRIPED', 'SUPER', 'TABLE', 'TABLES', 'TEMPORARY', 'TERMINATED', 'THEN', 'TO', 'TRAILING', 'TRANSACTIONAL',    
     'TRUNCATE', 'TYPE', 'TYPES', 'UNCOMMITTED', 'UNION', 'UNIQUE', 'UNLOCK', 'UPDATE', 'USAGE', 'USE', 'USING', 'VALUES', 'VARIABLES',
     'VIEW', 'WHEN', 'WHERE', 'WITH', 'WORK', 'WRITE', 'XOR', 'YEAR_MONTH'
 );

 $sql_skip_reserved_words = array('AS', 'ON', 'USING');
 $sql_special_reserved_words = array('(', ')');

 $sql_raw = str_replace("\n", " ", $sql_raw);

 $sql_formatted = "";

 $prev_word = "";
 $word = "";

 for( $i=0, $j = strlen($sql_raw); $i < $j; $i++ )
 {
  $word .= $sql_raw[$i];

  $word_trimmed = trim($word);

  if($sql_raw[$i] == " " || in_array($sql_raw[$i], $sql_special_reserved_words))
  {
   $word_trimmed = trim($word);

   $trimmed_special = false;

   if( in_array($sql_raw[$i], $sql_special_reserved_words) )
   {
    $word_trimmed = substr($word_trimmed, 0, -1);
    $trimmed_special = true;
   }

   $word_trimmed = strtoupper($word_trimmed);

   if( in_array($word_trimmed, $sql_reserved_all) && !in_array($word_trimmed, $sql_skip_reserved_words) )
   {
    if(in_array($prev_word, $sql_reserved_all))
    {
     $sql_formatted .= '<b>'.strtoupper(trim($word)).'</b>'.'&nbsp;';
    }
    else
    {
     $sql_formatted .= '<br/>&nbsp;';
     $sql_formatted .= '<b>'.strtoupper(trim($word)).'</b>'.'&nbsp;';
    }

    $prev_word = $word_trimmed;
    $word = "";
   }
   else
   {
    $sql_formatted .= trim($word).'&nbsp;';

    $prev_word = $word_trimmed;
    $word = "";
   }
  }
 }

 $sql_formatted .= trim($word);

 return $sql_formatted;
}

谢谢,但这是一个好的格式化程序吗?在我看来,每个保留字都放在单独的一行上并不是最佳的格式,例如: SELECT x FROM x JOIN ON WHERE A UNION SELECT x FROM x JOIN ON WHERE A - FantomX1

7

使用phpMyAdmin非常简单:

require 'libraries/common.inc.php';

$sql= "select * from test";
$parsed_sql = PMA_SQP_parse($sql);    
echo PMA_SQP_formatHtml($parsed_sql);

添加样式表以启用语法高亮。如果您在sqlparser文档中阅读一些内容,您会发现其他不同格式类型的函数。

唯一的问题是phpMyAdmin有点庞大,仅使用SQL解析可能会使您想剥离所有其他功能...


我尝试过这个,但发现它远非直截了当,最终放弃了。 - Simon Nuttall
我自己尝试了这个例子(通过在phpmyadmin目录下创建一个test.php文件),并且它对我有效。 - Veger
1
好的,现在我已经通过将脚本保存在 /usr/share/phpmyadmin/ 并从 /phpmyadmin/ 在浏览器中打开它来使其工作。正如你所说,我需要在文档中深入挖掘以获得漂亮的格式。这并不像我希望的那样直截了当,但非常感谢你的指引。 - Simon Nuttall

3
function sql_format($query) {
  $keywords = array("select", "from", "where", "order by", "group by", "insert into", "update","SET", ",");
  foreach ($keywords as $keyword) {
      if (preg_match("/($keyword *)/i", ",", $matches)) {
        $query = str_replace($matches[1],strtoupper($matches[1]) . "<br/>&nbsp;&nbsp;  ", $query);  
      }
    else if(preg_match("/($keyword *)/i", $query, $matches)) {
      $query = str_replace($matches[1],"<br>".strtoupper($matches[1]) . "<br/>&nbsp;  ", $query);
    }
  }
  return $query;
}

谢谢,美丽的格式化程序。我不喜欢每个关键字都在单独的行上 - 就像Iosif一样,几乎像Jeremy Dorn的上层库。 - FantomX1

1

像这样做就可以了——添加任何其他要解析的 SQL 关键字:

function sql_format($query) {
  $keywords = array("select", "from", "where", "order by", "group by", "insert into", "update");
  foreach ($keywords as $keyword) {
    if (preg_match("/($keyword *)/i", $query, $matches)) {
      $query = str_replace($matches[1], "\n" . strtoupper($matches[1]) . "\n  ", $query);
    }
  }
  return $query;
}

这取决于一些可能的错误。 - Alix Axel
3
当然可以...这只是个起始建议 :) - Thilo
哦,还要用“<pre>...</pre>”将返回字符串包装起来,在 HTML 中进行缩进显示。 - Thilo

1

我认为目前在PECL或类似的库中没有免费可用的代码可以实现这个功能。这很遗憾,因为它本身是一个相当不错的小工具。(尽管只有相对有限的用途。)

因此,你已经回答了自己的问题 - phpMyAdmin可能是一个不错的首选。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接