6

我构建工具来分析源代码。此类工具必须正确读取源代码文件,尤其是在字符编码方面。例如,“字符串文字中精确的字节字符串是什么?” (PHP 文字和 HTML 文本)。

我可能错误的理解是 PHP 源文件只是 8 位字符(也就是说,PHP 引擎以这种方式读取它们[对]?,因为它们应该只包含 8 位字符)。但是,八位字符采用哪种编码?(我想是为了匹配 ISO-8859-1 (-x?) [有人能引用章节吗?和大多数欧洲国家/字符集的字符串。

但很明显,这对 Unicode 来说是有问题的。据我所知,大多数 PHP 应用程序处理 Unicode 本质上是通过将包含 UTF-8 字节序列的字符串插入 8 位 PHP 字符串中。在此之后,如果您告诉服务器您正在生成 UTF-8 文本,则可以生成其 HTML 包含 Unicode UTF-8 序列的脚本。

对于上述情况,可以将 PHP 文件读取为 8 位字符文本,这在我看来与语言匹配。

令我困惑的是编码为 UTF-8 的 PHP 源文件(Joomla 包有大约 1800 个源文件,其中大约 10 个是 UTF-8,其余不是)。在 UTF-8 渲染中正确显示的任何(非 ASCII)欧洲字符实际上都被编码为多字节序列。我想以 UTF-8 形式提供的此类页面将正确呈现 HTML。但是,在文本编辑器中明显正确呈现的欧洲字符或其他 Unicode 字符的任何字符串比较根本不起作用。字符串文字不会包含它们看起来包含的内容。程序员使用 UTF-8 文件是因为这是编辑器提供的吗?他们是故意这样做的吗?还是只是一个对大多数工作无关紧要的事故?

那么,应该如何读取 PHP 源文件呢?(特别是,用什么字符编码?)一个可能的答案是,总是作为 ISO-8859-1 8 位代码,不管实际内容或 BOM (我看到很多 UTF-8 BOM 标记的 PHP 文件)。另一个答案是 UTF-8,如果有标记的话。

[我们的工具读取和写入任意编码。一个“微不足道”的工具是一个字符中的读取文件编码,在另一种编码中写入相同的代码点。以这种方式读取 UTF-8 PHP 文件会使我们在编写 ISO8859-1 等效文件时遇到麻烦,因为许多 UTF-8 代码点(例如,欧元符号)无法在 ISO8859-x 中编码。]

编辑 8 月 30 日:我们现在检查 PHP 文件以查看它们是否具有 UTF-8 BOM,或者是否具有所有合法的 UTF-8 序列。在这两种情况下,我们都将文件读取为 UTF-8;否则我们默认将其读取为 ISO8859-1。如果我们修改它,我们现在保留文件编码。(把这一切做好是相当多的工作)。这似乎是一个安全的策略,但这可能与 PHP 程序员所期望的不同。

4

3 回答 3

10

TL;博士

ASCII


在 PHP 5.4 之前,PHP 解释器根本不关心 PHP 文件的字符集,这一点可以从zend.script_encoding ini 指令只出现在该版本中的事实证明。它基本上总是将其视为ASCII。

例如,当 PHP 需要识别一个函数名称时,它恰好包含超过 ASCII-7 位的字符(嗯,任何带有任何标签的标签实体,但你明白我的意思......),它只是在具有相同字节序列的符号表 - 以一种方式编写的元音变音(或其他......)将与以另一种方式编写的元音变音不同地对待。尝试一下。为了向后兼容,如果 zend.script_encoding 没有设置,这仍然是默认行为。还要注意显示什么是有效标识符的正则表达式,您可以看到它是字符集中性的(嗯......除了拉丁字母,它们在 ASCII-7 位范围内),而是向您显示字节。

这也将我们引向了declare(encoding)构造。如果您在文件中看到 THAT,那是该特定文件的最终字符集(仅限)。在遇到一个之前使用其他东西,如果你看到不止一个 - 在它的声明语句之后尊重第二个。

如果没有...

在静态上下文中(即,当您不知道有效的 ini 设置时),当字符集很重要时,您需要回退到其他内容(理想情况下是用户定义的内容),或者只处理 ASCII-7 位以外的字符作为纯二进制,并以某种统一的类似代码点的方式显示它们。

在动态上下文中(例如,如果您可以暂时重命名文件,在该位置创建一个具有该名称的临时文件;让它回显 zend.script_encoding 的值;恢复正常文件),您应该使用zend.script_encoding 值(如果可用),否则回退到其他值(就像在静态上下文中一样)。

相同的处理方式适用于字符串、HTML 片段和 PHP 文件的任何其他内容——它只是作为二进制字符串读取,除了某些对 PHP 词法分析器很重要的 ASCII 字符(即字节),例如序列 "<?php "(注意都是 ASCII 字符...);单引号字符串中的撇号;等等 - 解释器本身并不关心字符串的字符集,如果您必须在屏幕上显示字符串的内容,您应该使用上述方法找出最好的方法。


边缘案例(在评论中要求):

1.

对允许的编码有限制吗?

任何地方似乎都没有任何允许的编码列表,或者至少我找不到。鉴于这是 --enable-zend-multibyte 编译设置的继承者,所有风格的 UTF 编码肯定会在该列表中。即使其他 (ANSI) 编码对 PHP 本身没有影响,也不应该阻止您使用该值作为提示。

2.

如果源文件是 UTF-16(用于声明的 8 位 ascii 字符之间的 8 位空字节),“声明(编码)”如何工作?

zend.script_encoding 在遇到 declare(encoding) 之前一直使用。如果未设置,则假定为 ASCII。即使在 UTF-16 文件中,这也不应该是一个问题……对吧?(我不使用 UTF-16)虽然这对于编码为 UTF-16 的 PHP 文件可能是个问题,但我认为可以公平地说绝大多数开发人员只是不使用 UTF-16 编码他们的脚本。他们的数据,当然,如果应用程序的情况需要的话。但不是脚本本身。大多数 PHP 文件都是用 ANSI 编码或 UTF-8 编码的。

3.

如果 .ini 或文件设置是 UTF-8 或其他,那么标识符可能仅取自 x41-xFF 范围内的代码点,而不是取自代码点 x100 以上?

我没有尝试提供无效的 UTF-8 字节来告诉你答案,手册也没有说明任何关于这个问题的内容。我会假设 PHP 执行将失败并出现解析错误。或者至少应该。就您的工具而言,无论如何它都应该报告无效的 UTF-8 序列,因为即使 PHP 允许,这仍然是一个 QA 问题。

4.

对于 UTF 编码,字符串中的字符是否表示为它们的 UTF 代码点(这没有意义,因为 PHP 字符串似乎只有 8 位字符)?

不。字符串和非 PHP 内容中的字符仍被视为只是一个字节序列,您可以通过查看 strlen() 的输出来确认这一点,并查看它与 mb_strlen() 的区别,后者是尊重的编码(嗯......它尊重 mbstring.internal_encoding 设置是准确的,但仍然)。

5.

如果不是,将编码设置为 UTF 是什么意思?

AFAIK,它会影响符号表中的查找。设置 UTF 后,变音符号以不同的方式编写,或者以不同的 UTF 风格编写,最终得到相同的 UTF 代码点……它们都将集中在同一个符号上,而不是没有声明(编码),其中逐字节而是进行字节比较。我在这里说“AFAIK”,因为坦率地说,我自己从来没有使用过这样的实验......我是一个“做好事'everything-as-valid-UTF-8'-er”。

于 2013-09-01T20:57:07.767 回答
9

正如已经多次重复的那样,PHP 文件对 x7f 以上的字节没有任何编码。你所能知道的只是字节 x00 到 x7f 是 ascii。

开头带有 BOM 标记的文件不是有效的 PHP。所以没有什么比 iso-8859-1 或 utf-8 中的 PHP 文件更好的了。它是纯 8 位的。

PHP 文件不是 iso-8859-x,因为这些编码不包含所有可能的字节值。如您所知,x7f 到 x9f 在 iso-8859-1 中无效,但任何 PHP 文件都可能包含它们。

PHP 文件也不是 utf-8,因为它可能包含无效的 utf-8 序列,但不会是无效的。

大图

写作时的约定字符集

PHP 文件可以按照约定进行编码,但这取决于程序员的判断。他会告诉他的编辑,这样的项目是 utf-8 或 iso-8859-1 或其他。

但同样,这只是程序员的约定。他的编辑器威胁 PHP 文件,好像它是这样那样的编码。编码仅用于在编辑器中显示文件并允许程序员对其进行编辑。

编译期间没有字符集

如上所述,编译器不需要知道程序员假定的编码。唯一重要的是文件中的字节序列是什么。

消费定义的隐式或显式字符集

PHP 生成一些通过 Internet 发送到浏览器的数据。在浏览器显示数据的时候,编码肯定是定义的,但是怎么定义呢?

  • 编码可以在 HTTP 标头中定义,像这样Content-Type: text/html; charset=utf-8
  • 它可以在 HTML 输出本身中定义:<meta charset="utf-8">
  • 或者,如果字符集没有明确定义,浏览器会根据文档中存在的字节序列(例如有效的 utf-8 序列或 BOM)做出有根据的猜测。

当然,PHP 应用程序从不让浏览器选择是一种很好的做法,但并不要求在任何地方定义编码。

更多细节

通常,程序员选择的编码将与浏览器链末尾使用的相同,PHP 文件中的所有字符串都将使用相同的编码。

但情况并非如此。有正当的理由,为什么不会这样。让我们看一些例子:

不同的语言,不同的编码

我使用 Joomla,因为它是 1.0 版。在这个版本中,语言文件都有自己的编码。法语是 iso-8859-1,而阿拉伯文件是 windows-1256 和俄语文件 koi8-r。对于那些重要的编码,但不是所有其他文件,它们可以被视为 utf-8 或 iso-5598-1。(同时,Joomla 切换到 utf-8。)

异构数据库

我们的一个 Web 应用程序连接到两个不同的数据库,一个恰好在 utf-8 中,另一个在 windows-1252 中。这意味着,该项目中的所有字符串都不是相同的编码。我尽可能多地使用 utf-8,但我需要使用mb_*PHP 中的函数组来回转换编码。

PHP的转换函数

仅仅存在编码转换函数mb_convert_encoding, iconv,utf8_encode等就表明在同一个项目中可以存在不同编码的字符串。

好习惯

定义你的编码并坚持下去!最好的选择是使用 utf-8。如果需要其他编码的其他字符串,您可以随时编写类似的内容$s=mb_convert_encoding('Уровень','ucs-2','utf8');

再说一遍:您不能在 PHP 中使用 BOM 标记。原因很简单:BOM 标记在开始标记之前有两个字节<?php。因此,它们被发送到浏览器。如果之后尝试发送header(),则会生成错误,并且不会发送标头。

结论

  • 一般来说,不需要确定 PHP 文件的编码。只有最终呈现的 HTML 文件的编码很重要。
  • 以用于显示最终结果的相同编码编辑所有文件是一种很好的做法。但它真的只对语言文件很重要(如果你使用任何 i18n 系统)。
  • 虽然实际上一个文件中的所有字符串都采用相同的编码,但没有什么能让心怀不轨的程序员在同一个文件中以不同的编码编写字符串,并且仍然得到一个工作程序。

最后,PHP 中的编码只是编写时使用的约定问题,以及浏览器中用于呈现页面的字符集。在这两者之间,PHP 文件没有特定的编码,它只是普通的 8 位。

于 2013-09-07T21:16:54.107 回答
3

确实没有办法可靠地告诉 PHP 源文件的编码。真的可以是任何东西。如您所知,唯一的通用标识符是 BOM,但大多数人会将其从源文件中删除,因为它们可能会在输出时造成麻烦。

如何处理这个取决于你想做什么。通常,这并不重要,因为 PHP 文件会自行声明其编码,例如通过发送Content-type标头(或者它是隐式定义的,例如因为它是项目的一部分,其约定是使用某种编码)。编码问题并没有真正出现,因为文件在执行时会自行排序。

如果您正在构建一个以某种形式操作或分析 PHP 源文件的工具,那么编码可能并不重要,但我们必须更多地了解您的情况才能进行评估。

大多数 IDE 处理这种不确定性的方式是他们要求开发人员手动指定项目、文件夹和/或文件所在的编码。也许这也是您的选择。

于 2013-08-31T19:03:54.737 回答