wapmorgan/binary-stream
A PHP library for reading and parsing binary data from streams and files, with convenient typed readers for integers, strings, and custom structures, plus seeking and position tracking for binary file formats.
What it is: wapmorgan/binary-stream is a plain-PHP, Composer-installable library that abstracts binary reading/writing into typed field objects (Int32, UInt16, Byte, String, etc.) on top of stream resources, positioned as a more readable alternative to pack()/unpack(). There is no Laravel service provider, config, facade, or framework coupling — it is a zero-integration library, which is the strongest thing it has going for it.
Architecture fit:
unpack(), but that benefit is entirely developer-ergonomics, not capability. Native pack()/unpack() can express everything this library can.Integration feasibility: High on effort, low on friction. composer require wapmorgan/binary-stream is the whole install. Risk is not "can we wire it up" but "will it run on a modern runtime" and "who fixes it when it doesn't."
Technical risk — this is the crux:
Serializable deprecation) that commonly break libraries of this age. Assume it may emit deprecations or fail outright until proven on your target PHP version.unpack() in a tight loop. For bulk parsing of large files, expect a measurable regression versus a hand-rolled unpack() pass.Key questions to resolve before commitment:
phpunit/phpstan?unpack() format string suffice?Verdict: Technically viable, strategically weak. Adopt only behind an internal abstraction, only for a concrete binary-format requirement, and only with the assumption that we are adopting maintenance responsibility.
Stack fit: Framework-agnostic; works with any Laravel version that Composer resolves it against. Because there is no service provider, do not let it leak into application code as new BinaryStreamReader(...) scattered across controllers and jobs. Wrap it.
Recommended shape:
app/Binary/
BinaryDecoderInterface.php // our contract: readInt32(), readString(), etc.
BinaryStreamDecoder.php // thin adapter over wapmorgan/binary-stream
NativeDecoder.php // pack()/unpack() implementation (fallback / benchmark)
app/Providers/BinaryServiceProvider.php
This gives three things: a swap path to the native implementation, a single place to patch PHP-8 issues, and a mockable seam for tests.
Migration path (phased):
unpack() reference.BinaryDecoderInterface over the library; bind as singleton. Business code depends only on the interface.vcs Composer repository pointing at our fork, apply PHP 8 fixes, tag 1.0.0-our.1, and record it in the dependency register as internally owned.Compatibility checks to run explicitly:
^5.6|^7.0 constraint will fail resolution on PHP 8 and is itself a signal.composer dump-autoload -o.Sequencing: Put this behind the feature that needs it, not ahead of it. There is no reason to add a 2017 orphan to composer.json speculatively. If the feature slips, the dependency never ships.
Maintenance: The dominant cost. A 2017 package with zero dependents is a de facto in-house library the moment it enters composer.json. Budget for:
pack().Support: No vendor, no SLA. Upstream is a single maintainer on a repository with 52 stars and no issues triage guarantee. Realistically: self-support, with the adapter layer as our insulation. Compare against the support story for native pack()/unpack() — documentation, ubiquity, and zero dependency risk.
Scaling: Adequate for feature-scale workloads (parsing request payloads, decoding a file per job). Not appropriate for high-throughput bulk decoding: typed-field construction allocates per field, so decoding a 100 MB file field-by-field will be materially slower and more memory-hungry than a single unpack() pass or fopen/fread chunking. If the workload is "one payload per request," fine. If it's "decode 10k records per job," benchmark first and expect to write the native decoder anyway.
Failure modes to plan for:
| Failure | Symptom | Mitigation |
|---|---|---|
| PHP 8 incompatibility | Fatal on autoload/instantiation, or deprecation flood in logs | Spike before adoption; fork-and-patch if needed |
| Silent endianness mismatch | Plausible-but-wrong field values, no exception | Golden-file fixtures; assert exact values, not just "no error" |
| Integer overflow | Wrong values on large unsigned fields (esp. 32-bit) | Explicit width/range tests at boundaries |
| Memory exhaustion | OOM decoding large binary files | Stream in chunks; cap input size; queue off-request |
| Malformed/untrusted input | Unhandled exception or unbounded read | Validate stream length before decode; wrap in try/catch at the adapter boundary |
| Upstream disappearance | Repo archived/deleted | MIT license permits vendoring; keep fork ready |
Ramp-up: Low. Small API surface, so a mid-level developer is productive in under a day. The cost is documentation quality — sparse, README-driven, no examples for edge cases (bitfields, nested structs, endianness). Expect tribal knowledge to accumulate in the adapter layer; put the format spec next to it in the repo.
Recommendation: Treat this as a thin, replaceable convenience layer, not an architectural dependency. The adapter interface costs a day and converts every risk above from "blocking" to "swap out a class." If the spike is not clean on day one, skip the package entirely and write the decoder over native pack()/unpack() — it is roughly the same amount of code as the adapter, and you own it either way.
How can I help you explore Laravel packages today?