Inline C and external libraries¶
Zephir is designed so that almost everything you need can be expressed in Zephir itself, and the compiler can reason about it (type-infer, optimize, validate). For the rare cases where you really do need to drop into raw C (calling a system library, declaring a static C helper, defining a macro shared by several methods), Zephir offers two escape hatches:
%{ … }%blocks (commonly called cblocks): embed raw C inside a.zepfile.extra-cflags/extra-libs/extra-sources/extra-classesinconfig.json: tell the build system about headers, libraries, additional.cfiles, and pre-existing PHP-extension class entries.
Last-resort escape hatch
Code inside a %{ … }% block is opaque to the Zephir compiler. Type inference, the read/write/mutation detectors, the nonexistent-class/unused-variable warnings, and the call-graph analyses all stop at the block boundary. Errors and warnings about it come from the C compiler (GCC/Clang/MSVC), not from Zephir, so debugging is rougher. Prefer a custom optimizer whenever the target functionality can be expressed as a function call. Optimizers participate fully in Zephir's analysis pipeline.
%{ … }% cblocks¶
A cblock is a fragment of literal C, opened with %{ and closed with }%. Its contents are copied verbatim into the generated .c file. The legal positions are:
1. File-scope cblocks (before / after namespace)¶
Use these for #includes, #defines, file-scope static helpers, and other top-level declarations. They are emitted right after Zephir's own #include block in the generated .zep.c, so they can rely on the PHP / Zend / Zephir-kernel headers being already in scope.
%{
// Emitted at the top of the generated .c file, right after the
// auto-generated PHP / Zend / kernel includes.
#define MAX_FACTOR 10
}%
namespace Stub;
%{
// You can have several file-scope cblocks; they accumulate in source order.
#include "kernel/require.h"
// File-scope C helpers are fine. They're invisible to PHP, callable from
// any cblock inside the methods of this file.
static long fibonacci(long n) {
if (n < 2) return n;
else return fibonacci(n - 2) + fibonacci(n - 1);
}
}%
class Cblock
{
// ... see below for statement-scope cblocks
}
2. Statement-scope cblocks (inside a method body)¶
Statement-scope cblocks read and write the surrounding Zephir variables directly. You reference them by their declared Zephir name. Built-in scalar types map naturally to their C counterparts:
| Zephir type | C type the cblock sees |
|---|---|
int / uint / long / ulong | zend_long |
char / uchar | char |
double | double |
bool | zend_bool |
string | zend_string * (or zval, depending on context, see Method.php's parameter handling) |
var / array / object | zval |
A short example mixing the two scopes:
class Cblock
{
public function fibonacci10()
{
long a = 0;
%{
// Calls the file-scope C helper declared above.
// `a` is a `zend_long` here. Zephir's `long` maps to it directly.
a = fibonacci(MAX_FACTOR);
}%
return a;
}
}
What you cannot do inside a statement-scope cblock:
- Declare new Zephir-tracked variables. Declare any C locals you need inside the cblock. They don't escape.
- Return from the enclosing Zephir method. Compute a value into a Zephir-scoped variable and
returnit from Zephir code after the block. - Rely on Zephir's read/write detectors. If you mutate a Zephir variable from inside a cblock, mark the change visible to Zephir by reading the variable from a
letstatement afterwards, or the compiler may have optimized away the load.
Build-time vs. parse-time errors¶
Syntax errors inside a cblock are reported by the C compiler with a confusing line number. The offset is from the start of the generated .c file, not the .zep file. When debugging a cblock failure, open the corresponding ext/<namespace>/<class>.zep.c and look at the absolute line number from the C-compiler error. The cblock content lives at roughly the position you'd expect, surrounded by Zephir-generated scaffolding.
Linking against external libraries¶
When a cblock #includes a header from a library that isn't part of PHP itself, you need to tell the build system how to find the header and how to link the library. Four config.json keys cover this:
| Key | Purpose |
|---|---|
extra-cflags | Compiler flags applied during the C build, typically -I<dir> to add a header search path. |
extra-libs | Linker flags, typically -L<dir> for library search paths plus -l<name> for the library itself. |
extra-sources | Extra .c files you want compiled and linked into the same extension. Paths are resolved relative to the ext/ directory. |
extra-classes | Pre-existing PHP-extension classes implemented in C that you want exposed alongside your Zephir classes. Each entry names the header, source file, init function and class-entry symbol. |
A typical libevent-using configuration:
{
"extra-cflags": "-I/usr/local/Cellar/libevent/2.0.21_1/include",
"extra-libs": "-L/usr/local/Cellar/libevent/2.0.21_1/lib -levent",
"extra-sources": ["utils/event_helpers.c"]
}
Then a Zephir file can #include <event2/event.h> from a cblock, call event_base_new() etc. directly, and the linker will find libevent. See the config.md page for the full schema of each key.
When to use a custom optimizer instead¶
A custom optimizer is a small PHP class registered in config.json under optimizer-dirs that the compiler invokes whenever it sees a particular function name. The optimizer's optimize() method returns a CompiledExpression whose code is the C expression the compiler should emit at the call site. The Zephir-side code calls the function normally:
…and your optimizer emits whatever C it wants (including a call into the same external library) as a tracked Zephir expression. The compiler sees a normal function call, can reason about the return type, applies its analyses, and you don't pay the debuggability cost of an opaque cblock.
Use a cblock when:
- The C code is declarative (
#includes,#defines, file-scopestatichelpers) and not callable as a function from Zephir. - You're prototyping and don't yet want to write the optimizer scaffolding.
- The C-side intermediate state genuinely can't be modeled as a function (e.g. allocating into a
staticcache).
Use an optimizer when:
- The cblock would be a one-liner function call.
- You'd otherwise have to write the same cblock in several methods.
- You want Zephir to participate in type inference and warning generation around the call.
Historical note¶
Cblocks were introduced in PR #21 (inspired by Go's cgo), and the extra-libs config key landed in PR #636. The feature has been silently supported for many years. See issue #654 for the original "why isn't this documented?" report.