Download the PHP package iliaal/phpser without Composer

On this page you can find all versions of the php package iliaal/phpser. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package phpser

phpser

Tests Version License: BSD-3-Clause Follow @iliaa

A PHP serialization extension in C, targeting read-heavy cache workloads where decode time matters more than encode time or payload size.

Why phpser?

PHP cache workloads pay decode cost on every read. Encode happens once per write. The default igbinary was the right answer for over a decade, but lags on three shapes that show up everywhere: packed numeric arrays, deep-nested structures, and same-class DTO batches (Laravel queue payloads, cached models).

phpser is decoder-optimized. Pointer-equality dict intern, refcount-reuse of zend_strings, pre-sized hash tables with direct arPacked writes, tagged scalar runs. On the shapes above, it cuts size by 60-65% and decode time by 70-77% vs igbinary. On general-purpose rowsets it sits within 1% of igbinary's size with 25% faster encode and ~5% slower decode.

Not a universal win. Encode on small rowsets (100 rows) costs +30% over igbinary, and object-heavy mixed shapes pay +42% on encode because obj->handlers->get_properties is per-object. The bench table below has the full shape-by-shape breakdown.

Install

On a minimal PHP image (e.g. php:8.x-cli from Docker Hub), PIE needs a few build tools installed first:

unzip is load-bearing on Debian: composer shells out to /usr/bin/unzip when extracting PIE's prebuilt-binary zip. If unzip is missing, composer silently falls back to PHP's ZipArchive which lays the .so out at a path PIE doesn't check, and install fails with ExtensionBinaryNotFound even though the zip downloaded fine.

From source

Pre-built binaries

Pre-built .dlls for Windows (PHP 8.3-8.5, TS/NTS, x64) and .sos for Linux glibc (x86_64, arm64) and macOS arm64 (PHP 8.4-8.5) are attached to each GitHub release. PIE fetches the matching binary automatically; falls back to source-build when no asset matches.

Usage

Basic round-trip. The encoded payload is opaque bytes; treat it as a binary blob in storage (no JSON-safety, no UTF-8 guarantees):

HMAC-signed mode for untrusted storage (memcached, redis, files, cookies). The signed entry points wrap the payload in a constant-time HMAC-SHA256 frame; tampered or foreign-keyed input is rejected before any decoding work runs:

allowed_classes option on both unserialize entry points. Same shape as PHP's native unserialize($payload, ['allowed_classes' => ...]):

When decoding attacker-controlled bytes, use one of the two restricted modes or the signed entry point. See SECURITY.md for the full threat model.

✨ Features

Bench (opt PHP 8.4.22-dev NTS release, 1000 iters, median of 9 runs)

Shape Size: ig → ps Encode: ig → ps Decode: ig → ps
rowset_100 4570 → 4771 (+4.4%) 9k → 11k ns (+30%) 9k → 10k ns (~parity)
rowset_1000 47K → 48K (+1.1%) 143k → 113k ns (-25%) 93k → 98k ns (+5%)
packed_1k 5495 → 1941 (-65%) 4.2k → 1.4k ns (-67%) 7.0k → 1.7k ns (-77%)
packed_10k 60K → 22K (-63%) 41k → 16k ns (-61%) 67k → 17k ns (-73%)
deep_50 419 → 424 (parity) 1.3k → 0.65k ns (-49%) 1.7k → 1.5k ns (-9%)
dto_100 7083 → 6362 (-10%) 14k → 18k ns (+22%) 25k → 22k ns (-11%)
dto_1000 73K → 65K (-12%) 175k → 173k ns (parity) 250k → 214k ns (-14%)
dto_mixed 22K → 29K (+33%) 54k → 76k ns (+42%) 103k → 88k ns (-14%)

Wins: packed numerics ~65% smaller + ~75% faster decode + ~61% faster encode. Deep-nested ~49% faster encode at parity size. Rowset_1000 encode beats igbinary by ~25%, size within 1.1%; decode pays a ~5% tax for the front-loaded dict header walk + refcount-reuse machinery. DTO workloads (Laravel-queue-style payloads, single-class arrays): 10-12% smaller, 11-14% faster decode vs igbinary thanks to dict dedup on prop names + the class-entry lookup cache that amortizes zend_lookup_class_ex across same-typed batches.

rowset_100 encode (+30%) is the durable gap: a fixed-cost floor for the dict header emission and first-row inline emissions, amortized over too few rows to recover. The absolute time is small (11 µs for the entire 100-row payload). Decode is essentially at parity (per-run delta median +0.4%, absolute ratio +6%): the skip-DICT cache-eviction policy keeps ['a','b','c']-style repeated values in DICT slots so detect_packed_run picks the TAG_PACKED_STRINGS typed-run path instead of falling back to PACKED_MIXED mid-rowset.

dto_mixed encode (+42%) is the durable encode gap on object-heavy shapes: obj->handlers->get_properties is called per object and isn't trivially avoidable without a custom fast path for default property layouts.

Design highlights

The core ideas that drive the perf wins above:

Where phpser diverges from igbinary

igbinary is the closest reference point. The areas where there's still measurable perf to take, and that this project targets, are:

  1. Pre-sized HT + direct arPacked writes on decode. When the wire format declares PACKED_LEN N, allocate the HT once via zend_new_array(N) and write directly into arPacked with ZVAL_* macros. Skips N zend_hash_next_index_insert calls, including their hash computation, growth checks, and capacity tuning. Shipped.
  2. Tagged scalar runs. [1, 2, 3, ...] (1000 longs) emits as a single PACKED_LONGS header + N zigzag varints, not 1000 (tag, varint) pairs. Decode is one tight loop with no per-element tag dispatch. Shipped.
  3. Inline-cache pointer intern. 16-slot ring of recently-seen zend_string*. Hit rate near 100% on rowset shapes (PHP interns literals; the same "id" zend_string pointer flows through every row). Skips the byte-hash entirely on cache hits. Shipped.
  4. Eager dict materialization with warm hashes. All dict zend_strings allocated up front during header parse and their hashes pre-computed. zend_hash_add_new reuses the cached hash. Shipped.
  5. update insert on assoc decode. Originally add_new to skip the existence-check, but adversarial wire payloads with duplicate keys would produce phantom buckets that violate PHP's last-write-wins semantic (count($arr) != count(array_unique(array_keys($arr)))). Reverted to zend_hash_update for security-boundary correctness; add_new is a real but small perf win the cost of breaking adversarial payloads cleanly. Shipped.
  6. Inline-short-string tag with upgrade-on-second-encounter. TAG_STR_INLINE (0x0c) and KEY_STR_INLINE (0x02) are emitted on a string's first occurrence; the next occurrence triggers an in-place upgrade to a dict entry, and all subsequent ones emit TAG_STR_DICT. Singletons (e.g. row_X values in a rowset) never hit the upgrade branch. They cost nothing in the dict header. The intern cache doubles as the "seen once?" signal: high bit of idx distinguishes INLINE_EMITTED from DICT_IDX. No pre-pass; single walk of the zval tree as before.

    A count-then-emit variant was tried first: pre-walk the zval tree to tag occurrences, then emit inline for singletons and dict for repeats. The pre-pass cost ~200 ns per string and ate the per-singleton savings, so the single-walk upgrade-on-second-encounter version above is what ships. rowset_1000 encode landed at 25% faster than igbinary (up from 8% in the pre-upgrade implementation), with payload size dropping from +5% to +2.7%.

  7. Skip refcount machinery during build. All zvals built during decode are fresh and unshared until handed back to PHP. Internal writes can skip Z_TRY_ADDREF guards.

Local dev build

The hand-rolled Makefile builds against an in-tree ~/php-src-8.4-opt checkout without phpize/autoconf. Useful for hacking on the extension while also hacking on PHP itself:

Override PHP_SRC= to target a different in-tree PHP checkout. Load alongside igbinary for the A/B bench:

The config.m4 auto-detects the session extension and registers phpser as a session.serialize_handler when available.

Limitations / known gaps

Wire format (V1)

Varints are LEB128 (unsigned); signed values use zigzag encoding. Tags 0x10/0x11 plus 0x0a/0x0d/0x0e/0x0f each implicitly claim the next id in encounter order, so the decoder reconstructs back-refs by counting container tags as it parses.

🔗 PHP Performance Toolkit

Companion native PHP extensions:


Follow on XBlog • If this cut your cache decode CPU, ⭐ star it!


All versions of phpser with dependencies

PHP Build Version
Package Version
Requires php Version >=8.3
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package iliaal/phpser contains the following files

Loading the files please wait ...