Contents
- Changelog
- [Unreleased]
- [2.6.0] - 2026-07-06
- [2.5.0] - 2026-07-06
- [2.4.0] - 2026-07-06
- [2.3.0] - 2026-07-06
- [2.2.0] - 2026-07-05
- [2.1.0] - 2026-07-05
- [2.0.0] - 2026-07-01
- [1.4] - 2010-07-12
- [1.3.5-beta1] - 2007-10-15
- [1.3.3] - 2007-03-29
- [1.3.2] - 2007-03-01
- [1.3.1] - 2006-12-01
- [1.2] - 2005-12-13
- [1.1] - 2005-12-05
Changelog
All notable changes to PL/php are documented in this file.
The format is based on Keep a Changelog, and the project aims to follow Semantic Versioning.
[Unreleased]
Added
- PGXN packaging. A
META.jsonconforming to version 1.0.0 of the PGXN distribution metadata spec, so PL/php can be released on PGXN and installed withpgxn install plphp. It lists the four extensions in the distribution (plphpplus thejsonb_plphp,hstore_plphp, andbytea_plphptransforms).
Fixed
- Documentation: the README test-count badge and the INSTALL note on the optional transforms, which still described a single jsonb transform.
[2.6.0] - 2026-07-06
Added
- Private per-function data:
$_SD. Each function now has an$_SDassociative array that persists across calls to that function within a session and is private to it, the per-function counterpart to the session-global$_SHARED. The pair mirrors PL/Python’sSDandGD.$_SDstarts empty and is reset when the function is redefined. Typical use is caching a prepared plan once per session. byteatransform (bytea_plphp). A newCREATE EXTENSION bytea_plphpaddsTRANSFORM FOR TYPE bytea, mapping abyteato a raw, binary-safe PHP string (the bytes themselves, not the\x...text form) and back, mirroring PL/Python’sbytea<->bytes. Embedded NUL bytes survive the round trip, which the default text path truncates. Applies in nested contexts like the other transforms.
[2.5.0] - 2026-07-06
Added
hstoretransform (hstore_plphp). A newCREATE EXTENSION hstore_plphpaddsTRANSFORM FOR TYPE hstore, mapping anhstoreto a PHP associative array of string keys to string-or-null values and back (a PHPnullvalue becomes an hstoreNULL). Companion to the existingjsonb_plphp.- Transforms reach nested contexts. A declared
jsonb/hstoretransform now also applies to values of that type inside composite argument/result fields,RETURNS SETOF/return_nextrows, and a trigger’s$_TDrows (including the'MODIFY'return), not only as top-level arguments and results. Rows read back through SPI are still left in text form. - Structured
pg_raise.pg_raise(level, message [, detail [, hint [, sqlstate]]])now attachesDETAIL,HINTand (forERROR) a customSQLSTATE, mirroring PL/pgSQL’sRAISE ... USING. The fields are readable on a caughtPgErrorand survive an uncaught error out to the client. TRUNCATEandINSTEAD OFtriggers. Statement-levelTRUNCATEtriggers ($_TD['event'] = 'TRUNCATE') and viewINSTEAD OFtriggers ($_TD['when'] = 'INSTEAD OF') are now supported; previously the handler errored on the unrecognized event/timing.- Domains over arrays and composites. A function argument or result typed
as a domain is now handled as its base type, so a domain over an array
arrives as a PHP array (and can be returned as one) and a domain over a
composite as an associative array. The domain’s
CHECKconstraints are still enforced on results. Scalar domains were already transparent. - Project logo and brand assets. Light/dark PL/php logos and a square icon
under
doc/assets/, wired into the README and language reference.
Fixed
- Uncaught database errors lost their SQLSTATE, DETAIL and HINT. An error
that unwound to the top of a PL/php call was reported with only its message
(and
SQLSTATE XX000); it now carries the originalSQLSTATE,DETAILandHINTthrough thezend_bailoutreporting path.
[2.4.0] - 2026-07-06
Added
- Composite values convert structurally. Composite-typed columns in rows,
fields of composite arguments, and composites inside arrays now arrive as
associative PHP arrays (recursively, arrays included), and PHP arrays
convert back to composite values on return, in
return_nextrows, and through triggerMODIFY, completing the structural-conversion work begun with array columns in 2.3.0. anycompatibleandanycompatiblearrayare accepted as polymorphic argument/return types on PostgreSQL 13 and newer.- ASAN in CI. A workflow job builds with AddressSanitizer and runs the
suite against a
libasan-preloaded server, catching memory-safety bugs mechanically (newASAN_FLAGShook in the Makefiles). - A reproducible benchmark suite (
bench/) and published results (doc/benchmarks.md) comparing PL/php with PL/pgSQL and PL/Perl: within a few percent on scalar and string work, 1.75x PL/Perl on SPI row loops. - Error CONTEXT lines. Messages raised while PL/php code runs carry a
CONTEXT: PL/php function "name"line (or the anonymous-block/compilation variants), like every other procedural language.
Fixed
- Per-function memory contexts. Each compiled function’s descriptor and subsidiary data (fmgr info records included) now live in their own memory context, deleted wholesale on redefinition, implementing a FIXME as old as the file. This also closes a use-after-free window: the compiled- function cache used to briefly point at freed memory during recompilation, which an unluckily timed statement cancel could have hit.
- Backend crash when an error crossed nested PL/php calls. A PostgreSQL
error unwinding out of a handler’s
zend_tryleft Zend’s bailout environment pointing into a dead stack frame; the next uncaught error then jumped into garbage. Nested regression cases added.
[2.3.0] - 2026-07-06
Added
- Continuous integration: a GitHub Actions matrix building and running
the full regression suite (core +
jsonb_plphp) on PostgreSQL 11-18 and PHP 8.1/8.2/8.3/8.4 for every pull request. - VARIADIC parameters. A variadic function’s collected arguments arrive
as a single PHP array, matching every other PL (
VARIADIC "any"remains unsupported and is rejected with an error). Previously any VARIADIC declaration failed.
Changed
- Array columns inside rows are PHP arrays now. An array-typed column in
$_TD['new']/['old'], in rows fromspi_fetch_row/spi_fetchrow, or in a composite argument’s fields used to arrive as its literal text form ({a,b}); it now converts to a PHP array, and converts back when assigned (e.g. triggerMODIFY). Code that string-parsed those values should use the array directly.
Fixed
- PHP 8.1, 8.2, and 8.4 compatibility. PL/php now builds and passes the
full suite on PHP 8.1 through 8.4: a version guard for the
php_module_startupsignature change (8.2), explicitfgetcsvarguments in the cookbook (8.4 deprecation), and a clear compile error below 8.1. The Makefiles now prefer thelibphpmatchingPHP_CONFIG’s version when several are installed (see INSTALL for a packaging caveat about shared SONAMEs).
[2.2.0] - 2026-07-05
Added
- Catchable database errors. Every database error raised by an SPI call is
now thrown as a
PgErrorPHP exception (withgetSQLState(),getDetail(), andgetHint()), sotry/catchworks the way PL/Perl’sevaland PL/Tcl’scatchdo.pg_raise('error', ...)andelog('ERROR', ...)throw aPgErrorwith SQLSTATEP0001. Uncaught errors abort the statement with the same message format as before. Database errors insidesubtransaction()callbacks are now catchable too. - Whole-array set-returning functions. An SRF may return the entire result
set as one array with one element per row (PL/Perl’s “return a reference to
an array” form), instead of or in addition to calling
return_next. This had been PL/php 1.x behavior that 2.0 silently broke (such functions returned zero rows). spi_each(query, callable): invoke a callback once per row, streaming over a cursor; returningfalsestops early. The inline-loop equivalent of PL/Tcl’sspi_exec -array a $query { body }.plphp.on_init: a snippet of PHP source executed when the interpreter is first initialized in a session, before modules andplphp.start_proc; the counterpart ofplperl.on_init.jsonb_plphptransform extension: withTRANSFORM FOR TYPE jsonb, jsonb arguments arrive as native PHP values (arrays/int/float/bool/null) and PHP values convert straight back to jsonb, likejsonb_plperl(which PL/Tcl has no equivalent of). PL/php core gained theTRANSFORM FOR TYPEprotocol support this builds on.A tested cookbook (
doc/cookbook.md):filter_varCHECK constraints, bcrypt passwords, HMAC tokens, recursive JSON reshaping, regex set-returning functions, a JSON-diff audit trigger, batch processing with periodic commits, streaming scans that stop early, CSV and XML shredding, and zlib compression. Every recipe in the “Tested” section runs in the newcookbookregression test.- New
coverageregression test pinning previously-untested paths: DML and utility statements throughspi_exec, trigger arguments ($_TD['args']), cursor behavior acrossspi_commitand rolled-back subtransactions,plphp.start_procfailure handling, multibyte and TOAST-sized values, PHP exception handling, and DO-block runtime errors.
Fixed
- INOUT parameters in procedures work.
CALLon a PL/php procedure with INOUT parameters used to fail (“function returning record called in context that cannot accept type record”): a procedure’s result is always a record (even with a single INOUT parameter), which broke both the single-OUT scalar-return shortcut and the record descriptor lookup (which needed aReturnSetInfothatCALLnever supplies; it is now derived from the parameter declarations viaget_call_result_type). The usual assignment convention now works:$param = ...;. - Array conversion rewritten in both directions, fixing three
long-standing FIXMEs:
- Returning a PHP array containing
nullnow produces a SQLNULLelement instead of raising an error. - String elements are now properly quoted and escaped on output; embedded quotes, backslashes, commas, braces, and spaces survive the round trip.
- Array input is now parsed with a real parser instead of being rewritten
into PHP source and passed through
zend_eval_string. Unquoted text elements (e.g.{foo,bar}) previously crashed with an undefined-constant error and now arrive as strings; quoted/escaped elements are decoded correctly; data no longer flows througheval().
- Returning a PHP array containing
- Array arguments are now detected from the argument’s declared type instead
of the old “value starts with
{” heuristic, so atextargument whose value happens to start with a brace is no longer misparsed as an array.
[2.1.0] - 2026-07-05
Added
- Cursor-streaming SPI:
spi_query(query)opens a cursor and returns its name,spi_fetchrow(cursor)fetches one row at a time (returningfalseand closing the cursor at exhaustion), andspi_cursor_close(cursor)abandons a cursor early. Large result sets can now be scanned in constant memory instead of being materialized byspi_exec. Matches PL/Perl’sspi_query/spi_fetchrow/spi_cursor_close.
Changed
- Breaking:
spi_query_prepared(plan, args...)now opens a cursor and returns its name for use withspi_fetchrow, matching PL/Perl semantics. In 2.0 it was an alias ofspi_exec_prepared(returning a materialized result resource); code that relied on the alias should callspi_exec_preparedinstead.
[2.0.0] - 2026-07-01
A ground-up modernization of PL/php (the previous release, 1.4, dates from 2010) for current software. Tested on PostgreSQL 11 through 18 with PHP 8.3 (embed SAPI, non-thread-safe).
Added
- Anonymous
DOblocks:DO $$ ... $$ LANGUAGE plphp, via an inline handler. - Event trigger functions:
RETURNS event_trigger, with$_TD['event']and$_TD['tag']. - Prepared statements:
spi_prepare,spi_exec_prepared,spi_query_prepared, andspi_freeplan. - Transaction control in procedures:
spi_commitandspi_rollback. - Explicit subtransactions:
subtransaction(callable, ...). - Quoting helpers:
quote_literal,quote_nullable,quote_ident. elog(level, message)supportingDEBUG/LOG/INFO/NOTICE/WARNING/ERROR.- Session initialization: module autoloading from a
plphp_modulestable and aplphp.start_procconfiguration setting. - Packaging as a first-class extension (
CREATE EXTENSION plphp) and a regression test for every new feature. - Documentation: a language reference (
doc/plphp.md) and PL/Perl and PL/Tcl feature comparisons.
Changed
- Ported the C code to the PostgreSQL 18 API:
FunctionCallInfo.args[](PG 12),TupleDescAttr(PG 10),SearchSysCache1,strlcpy,TextDatumGetCString,ALLOCSET_DEFAULT_SIZES,uint64SPI row counts (PG 11), and the removal ofSPI_restore_connection(PG 10). - Ported the interpreter glue to the PHP 8 Zend API: the new zval/refcount
model, hash-table and resource APIs,
call_user_function,zend_rebuild_symbol_table, embed startup, and the PHP 8.1+zend_error_cbsignature. - Uncaught PHP exceptions (for example calling an undefined function) are now
reported as PostgreSQL errors instead of silently returning
NULL. - PHP deprecations (such as the legacy
"${var}"string interpolation) are now non-fatal notices. - Replaced the autoconf build with a PGXS
Makefile. - Version guards keep releases back to PostgreSQL 11 working (a
FunctionCallInfoargument shim before PG 12, the pre-CommandTagevent-trigger tag before PG 13,EmitWarningsOnPlaceholdersbefore PG 15, and an explicitSPI_start_transactionafter commit/rollback before PG 15).
Removed
- The obsolete
pg_pltemplateinstall scripts (the catalog was removed in PostgreSQL 13). - The redundant
plphpuvariant.
Security
- PHP’s
safe_modewas removed in PHP 5.4, so PL/php can no longer be sandboxed. It is now an untrusted, superuser-only language, created without theTRUSTEDattribute; only superusers may install the extension or create PL/php functions.
[1.4] - 2010-07-12
Added
- Support for PostgreSQL 8.4 and 9.0.
- Support for PHP 5.3.
[1.3.5-beta1] - 2007-10-15
Added
- Support for parameter names.
- Support for PostgreSQL 8.3.
[1.3.3] - 2007-03-29
Added
booltype in return values.- Column names resembling numbers.
Changed
- Map PHP
E_STRICTtoWARNINGinstead ofERROR.
Fixed
- Several memory leaks.
- Bugs in argument handling.
[1.3.2] - 2007-03-01
Changed
- Link against the PHP embed SAPI instead of Apache’s
mod_php, making the build robust against internal PHP changes.
Added
configuresupport for detecting required utilities and libraries.
[1.3.1] - 2006-12-01
Changed
- Minor Makefile cleanups.
[1.2] - 2005-12-13
Added
- Set-returning functions.
- Support for PostgreSQL 8.0.
[1.1] - 2005-12-05
Supports PostgreSQL 8.0 and 8.1.
Added
- A PGXS-based build that no longer requires the PHP or PostgreSQL sources.
- Rudimentary SPI support for running queries and processing results.
- Trigger support, including aborting/skipping an operation and modifying the tuple before insert/update.
- Function validation at creation time (syntax errors are reported immediately).
- Propagation of PHP errors and warnings to PostgreSQL.
Changed
- Overhauled memory handling; SPI results are now opaque PHP resources, and a private symbol table is created and cleared per call.