# 捕获输出
除非你开发的是非常简单的控制台应用, 否则你应该不希望php脚本代码产生的输出 直接被扔到激活的终端上. 捕获这些输出和你刚才用以覆写启动处理器的方法类似.
在sapi_module_struct中还有⼀些有用的回调:
````c
typedef struct _sapi_module_struct {
...
int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC);
void (*flush)(void *server_context);
void (*sapi_error)(int type, const char *error_msg, ...);
void (*log_message)(char *message);
...
} sapi_module_struct;
````
#### 标准输出: ub_write
所有用户空间的echo和print语句产生的输出, 以及其他内部通过php_printf()或 PHPWRITE()产生的输出, 最终都将被发送到激活的SAPI的ub_write()方法. 默认情况, 嵌入式SAPI直接将这些数据交给stdout管道, 而不关心你的应用的输出策略.
假设你的应用想要把所有的输出都发送到⼀个独立的控制台窗口; 你可能需要实现⼀个类似于下面伪代码块所描述的回调:
````c
static int embed4_ub_write(const char *str, unsigned int str_length TSRMLS_DC)
{
output_string_to_window(CONSOLE_WINDOW_ID, str, str_length);
return str_length;
}
````
要让这个函数能够处理php产生的内容, 你需要在调用php_embed_init()之前对 php_embed_module结构做适当的修改:
````c
php_embed_module.ub_write = embed4_ub_write;
````
注意: 哪怕你决定你的应用不需要php产生的输出, 也必须为ub_write设置⼀个回调. 将它的值设置为NULL将导致引擎崩溃, 当然, 你的应用也不能幸免.
#### 缓冲输出: Flush
你的应用可能会使用缓冲php产生的输出进行优化, sapi层提供了⼀个回调用以通知 你的应用"现在请发送你的缓冲区数据", 你的应用并没有义务去实施这个通知; 不过, 由于 这个信息通常是由于足够的理由(比如到达请求结束位置)才产生的, 听从这个意见并不会有什么坏处.
下面的这对回调函数, 以256字节缓冲区缓冲数据由引擎安排执行flush.
````c
char buffer[256];
int buffer_pos = 0;
static int embed4_ubwrite(const char *str, unsigned int str_length TSRMLS_DC)
{
char *s = str;
char *d = buffer + buffer_pos;
int consumed = 0;
/* 缓冲区够用, 直接追加到缓冲区后面 */
if (str_length < (256 - buffer_pos)) {
memcpy(d, s, str_length);
buffer_pos += str_length;
return str_length;
}
consumed = 256 - buffer_pos; memcpy(d, s, consumed); embed4_output_chunk(buffer, 256); str_length -= consumed;
s += consumed;
/* 消耗整个传入的块 */
while (str_length >= 256) {
embed4_output_chunk(s, 256);
s += 256;
consumed += 256;
}
/* 重置缓冲区头指针内容 */ memcpy(buffer, s, str_length); buffer_pos = str_length; consumed += str_length;
return consumed;
}
static void embed4_flush(void *server_context)
{
if (buffer_pos < 0) {
/* 输出缓冲区中剩下的内容 */ embed4_output_chunk(buffer, buffer_pos); buffer_pos = 0;
}
}
````
在startup_php()中增加下面的代码, 这个基础的缓冲机制就就绪了:
````c
php_embed_module.ub_write = embed4_ub_write;
php_embed_module.flush = embed4_flush;
````
#### 标准错误: log_message
在启用了log_errors INI设置时, 在启动或执行脚本时如果碰到错误, 将激活 log_message回调. 默认的php错误处理程序会在处理显示(这里是调用log_message回调)之前, 格式化这些错误消息, 使其称为整齐的, 人类可读的内容.
关于log_message回调, 这里你需要注意的第⼀件事是它并不包含长度参数, 因此它并不是二进制安全的. 也就是说, 它只是按照NULL终止来处理字符串末尾.
使用它来做错误报告通常不会有什么问题, 实际上, 它可以用于在错误消息的呈现上 做更多的事情. 默认情况下, sapi/embed将会通过这个简单的内建回调, 发送这些错误消息到标准错误管道:
````c
static void php_embed_log_message(char *message)
{
fprintf (stderr, "%s\n", message);
}
````
如果你想发送这些消息到日志文件, 则可以使用下面的版本替代:
````c
static void embed4_log_message(char *message)
{
FILE *log;
log = fopen("/var/log/embed4.log", "a");
fprintf (log, "%s\n", message);
fclose(log);
}
````
#### 特殊错误: sapi_error
少数特殊情况的错误属于某个sapi, 因此将绕过php的主错误处理程序. 这些错误一般 是由于使用不当造成的, 比如非web应用不应该使用header()函数, 上传文件到控制台应用程序等.
由于这些情况都离你所开发的sapi/embed应用非常遥远, 因此最好保持这个回调为空. 不过, 如果你非要坚持去捕获每种类型错误的源, 也只需要实现⼀个回调函数, 并在调 用php_embed_init()之前覆写它就可以了.
## links
* [目录](<preface.md>)
* 20.4 [覆写INI_SYSTEM和INI_PERDIR选项](<20.4.md>)
* 20.6 [同时扩展和嵌入](<20.6.md>)
- about
- 开始阅读
- 目录
- 1 PHP的生命周期
- 1.让我们从SAPI开始
- 2.PHP的启动与终止
- 3.PHP的生命周期
- 4.线程安全
- 5.小结
- 2 PHP变量在内核中的实现
- 1. 变量的类型
- 2. 变量的值
- 3. 创建PHP变量
- 4. 变量的存储方式
- 5. 变量的检索
- 6. 类型转换
- 7. 小结
- 3 内存管理
- 1. 内存管理
- 2. 引用计数
- 3. 总结
- 4 动手编译PHP
- 1. 编译前的准备
- 2. PHP编译前的config配置
- 3. Unix/Linux平台下的编译
- 4. 在Win32平台上编译PHP
- 5. 小结
- 5 Your First Extension
- 1. 一个扩展的基本结构
- 2. 编译我们的扩展
- 3. 静态编译
- 4. 编写函数
- 5. 小结
- 6 函数返回值
- 1. 一个特殊的参数:return_value
- 2. 引用与函数的执行结果
- 3. 小结
- 7 函数的参数
- 1. zend_parse_parameters
- 2. Arg Info 与类型绑定
- 3. 小结
- 8 使用HashTable与{数组}
- 1. 数组(C中的)与链表
- 2. 操作HashTable的API
- 3. 在内核中操作PHP语言中数组
- 4. 小结
- 9 PHP中的资源类型
- 1. 复合类型的数据——{资源}
- 2. Persistent Resources
- 3. {资源}自有的引用计数
- 4. 小结
- 10 PHP中的面向对象(一)
- 1. zend_class_entry
- 2. 定义一个类
- 3. 定义一个接口
- 4. 类的继承与接口的实现
- 5. 小结
- 11 PHP中的面向对象(二)
- 1. 生成对象的实例与调用方法
- 2. 读写对象的属性
- 3. 小结
- 12 启动与终止的那点事
- 2. 小结
- 1. 关于生命周期
- 2. MINFO与phpinfo
- 3. 常量
- 4. PHP扩展中的全局变量
- 5. PHP语言中的超级全局变量
- 6. 小结
- 13 INI设置
- 1. 声明和访问ini设置
- 2. 小结
- 2. 小结
- 14 流式访问
- 1. 概览
- 2. 打开流
- 3. 访问流
- 4. 静态资源操作
- 5. 小结
- 15 流的实现
- 1. php流的表象之下
- 2. 包装器操作
- 3. 实现一个包装器
- 4. 操纵
- 5. 检查
- 6. 小结
- 16 有趣的流
- 1. 上下文
- 2. 过滤器
- 3. 小结
- 17 配置和链接
- 1. autoconf
- 2. 库的查找
- 3. 强制模块依赖
- 4. Windows方言
- 5. 小结
- 18 扩展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小结
- 19 设置宿主环境
- 1. 嵌入式SAPI
- 2. 构建并编译一个宿主应用
- 3. 通过嵌入包装重新创建cli
- 4. 老技术新用
- 5. 小结
- 20 高级嵌入式
- 1. 回调到php中
- 2. 错误处理
- 3. 初始化php
- 4. 覆写INI_SYSTEM和INI_PERDIR选项
- 5. 捕获输出
- 6. 同时扩展和嵌入
- 7. 小结
- 约定