捕获输出

除非你开发的是非常简单的控制台应用, 否则你应该不希望php脚本代码产生的输出 直接被扔到激活的终端上. 捕获这些输出和你刚才用以覆写启动处理器的方法类似.

在sapi_module_struct中还有⼀些有用的回调:

  1. typedef struct _sapi_module_struct {
  2. ...
  3. int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC);
  4. void (*flush)(void *server_context);
  5. void (*sapi_error)(int type, const char *error_msg, ...);
  6. void (*log_message)(char *message);
  7. ...
  8. } sapi_module_struct;

标准输出: ub_write

所有用户空间的echo和print语句产生的输出, 以及其他内部通过php_printf()或 PHPWRITE()产生的输出, 最终都将被发送到激活的SAPI的ub_write()方法. 默认情况, 嵌入式SAPI直接将这些数据交给stdout管道, 而不关心你的应用的输出策略.

假设你的应用想要把所有的输出都发送到⼀个独立的控制台窗口; 你可能需要实现⼀个类似于下面伪代码块所描述的回调:

  1. static int embed4_ub_write(const char *str, unsigned int str_length TSRMLS_DC)
  2. {
  3. output_string_to_window(CONSOLE_WINDOW_ID, str, str_length);
  4. return str_length;
  5. }

要让这个函数能够处理php产生的内容, 你需要在调用php_embed_init()之前对 php_embed_module结构做适当的修改:

  1. php_embed_module.ub_write = embed4_ub_write;

注意: 哪怕你决定你的应用不需要php产生的输出, 也必须为ub_write设置⼀个回调. 将它的值设置为NULL将导致引擎崩溃, 当然, 你的应用也不能幸免.

缓冲输出: Flush

你的应用可能会使用缓冲php产生的输出进行优化, sapi层提供了⼀个回调用以通知 你的应用”现在请发送你的缓冲区数据”, 你的应用并没有义务去实施这个通知; 不过, 由于 这个信息通常是由于足够的理由(比如到达请求结束位置)才产生的, 听从这个意见并不会有什么坏处.

下面的这对回调函数, 以256字节缓冲区缓冲数据由引擎安排执行flush.

  1. char buffer[256];
  2. int buffer_pos = 0;
  3. static int embed4_ubwrite(const char *str, unsigned int str_length TSRMLS_DC)
  4. {
  5. char *s = str;
  6. char *d = buffer + buffer_pos;
  7. int consumed = 0;
  8. /* 缓冲区够用, 直接追加到缓冲区后面 */
  9. if (str_length < (256 - buffer_pos)) {
  10. memcpy(d, s, str_length);
  11. buffer_pos += str_length;
  12. return str_length;
  13. }
  14. consumed = 256 - buffer_pos; memcpy(d, s, consumed); embed4_output_chunk(buffer, 256); str_length -= consumed;
  15. s += consumed;
  16. /* 消耗整个传入的块 */
  17. while (str_length >= 256) {
  18. embed4_output_chunk(s, 256);
  19. s += 256;
  20. consumed += 256;
  21. }
  22. /* 重置缓冲区头指针内容 */ memcpy(buffer, s, str_length); buffer_pos = str_length; consumed += str_length;
  23. return consumed;
  24. }
  25. static void embed4_flush(void *server_context)
  26. {
  27. if (buffer_pos < 0) {
  28. /* 输出缓冲区中剩下的内容 */ embed4_output_chunk(buffer, buffer_pos); buffer_pos = 0;
  29. }
  30. }

在startup_php()中增加下面的代码, 这个基础的缓冲机制就就绪了:

  1. php_embed_module.ub_write = embed4_ub_write;
  2. php_embed_module.flush = embed4_flush;

标准错误: log_message

在启用了log_errors INI设置时, 在启动或执行脚本时如果碰到错误, 将激活 log_message回调. 默认的php错误处理程序会在处理显示(这里是调用log_message回调)之前, 格式化这些错误消息, 使其称为整齐的, 人类可读的内容.

关于log_message回调, 这里你需要注意的第⼀件事是它并不包含长度参数, 因此它并不是二进制安全的. 也就是说, 它只是按照NULL终止来处理字符串末尾.

使用它来做错误报告通常不会有什么问题, 实际上, 它可以用于在错误消息的呈现上 做更多的事情. 默认情况下, sapi/embed将会通过这个简单的内建回调, 发送这些错误消息到标准错误管道:

  1. static void php_embed_log_message(char *message)
  2. {
  3. fprintf (stderr, "%s\n", message);
  4. }

如果你想发送这些消息到日志文件, 则可以使用下面的版本替代:

  1. static void embed4_log_message(char *message)
  2. {
  3. FILE *log;
  4. log = fopen("/var/log/embed4.log", "a");
  5. fprintf (log, "%s\n", message);
  6. fclose(log);
  7. }

特殊错误: sapi_error

少数特殊情况的错误属于某个sapi, 因此将绕过php的主错误处理程序. 这些错误一般 是由于使用不当造成的, 比如非web应用不应该使用header()函数, 上传文件到控制台应用程序等.

由于这些情况都离你所开发的sapi/embed应用非常遥远, 因此最好保持这个回调为空. 不过, 如果你非要坚持去捕获每种类型错误的源, 也只需要实现⼀个回调函数, 并在调 用php_embed_init()之前覆写它就可以了.