tecnickcom/tc-lib-file
PHP utilities for low-level file access and byte-level reading. Provides local and remote file reads with host/path allowlists, big-endian binary byte readers, a prefixed temp-file cache, and typed exceptions. Part of the TCPDF ecosystem. Requires PHP 8.2+ with curl and pcre.
What it actually is. tecnickcom/tc-lib-file is a narrow, low-level primitive library, not an application framework component. It exposes four small classes — File (allowlisted local/remote reads via cURL), Byte (big-endian binary readers over an in-memory string), Cache (prefixed temp-file lifecycle with age-based pruning), and Dir (writable-ancestor lookup) — plus a single Exception type. There is no Laravel service provider, no config file, no facade, no queue/event integration, and no opinion about where files live. Adoption cost is therefore almost entirely integration and governance work we would do ourselves, not library configuration.
Where it earns its place. Laravel's native primitives do not overlap much here. Storage/File facades give us path abstractions but no byte-level structured readers; Http gives us an HTTP client but no host allowlist with redirect-target revalidation and a memory-bounded response cap. If our roadmap includes PDF manipulation, font subsetting/embedding, image header inspection, TTF/OTF table parsing, or any binary format work, Byte plus the File allowlist semantics are genuinely useful. If our roadmap is CRUD + JSON APIs, this package has essentially zero surface area for us and the correct decision is to decline.
Architecture fit. Fits as an infrastructure-layer dependency consumed through a thin internal adapter, not called directly from controllers or jobs. The strict-deny default posture (allowedHosts and allowedPaths both empty by default) is the right default and composes well with a Laravel config-driven singleton. The API is small and stable enough to wrap: roughly a dozen public methods across four classes.
Integration feasibility. High in mechanical terms: composer require, PHP 8.2+ matches current Laravel 11/12 runtime expectations, PSR-4 autoload, no global state beyond K_PATH_CACHE. Two hard prerequisites: ext-curl must be present (it is disabled on some hardened images) and 64-bit PHP is assumed by getULong/getLong — a 32-bit build would produce wrong values rather than an error, which is the dangerous failure class.
Technical risk.
| Risk | Severity | Notes |
|---|---|---|
| License ambiguity | High | README states GNU LGPL v3; GitHub/Packagist metadata reports NOASSERTION. These must reconcile before adoption. LGPL in a distributed on-prem appliance imposes relinking/source-availability obligations; SaaS use is generally unaffected but needs counsel sign-off, not engineering judgment. |
| Single-maintainer / bus factor | Medium | 8 stars, 0 dependents. Mitigated by the parent TCPDF/tc-lib-pdf ecosystem (~100M installs claim) meaning the maintainer is established, but this specific leaf package has no independent community. |
| Release metadata anomaly | Medium | "Last release: 2026-10-17" is inconsistent with observed time; verify actual tag cadence and whether this is an active or effectively frozen package before planning upgrades. |
Silent setCachePath() fallback |
Medium | Falls back to K_PATH_CACHE without error when the given path is a stream wrapper or non-writable. A misconfigured cache path degrades silently. |
| Reserved cURL callbacks | Medium | CURLOPT_WRITEFUNCTION, PROGRESSFUNCTION, XFERINFOFUNCTION, NOPROGRESS, and (with redirects) HEADERFUNCTION are overwritten. Any platform-wide cURL instrumentation, tracing, or egress proxy implemented at that layer will conflict. |
Default maxRemoteSize 50 MB |
Medium | Buffers in memory, measured post-decompression (zip-bomb exposure). Default is too generous for shared PHP-FPM pools. |
| Windows canonicalization gaps | Medium | 8.3 short names, ADS (file.txt:stream), trailing dots/spaces, and reserved device names are not expanded before allowlist matching — a path-confusion surface if allowlist inputs are ever user-influenced on Windows hosts. |
| Platform-dependent path semantics | Low–Medium | Case sensitivity is auto-probed per volume; macOS Unicode folding requires ext-intl or degrades to byte comparison. Behavior differs across our Linux CI and any macOS developer machines. |
Key questions for the maintainer / our own review.
NOASSERTION value reported by GitHub? Can we get an explicit LICENSE file and SPDX identifier confirmation?File allowlist API, given it is a security boundary?maxRemoteSize and the reserved-cURL-callback list be considered stable public contract, or are they subject to change in a minor release?Stack fit. Adopt behind a single internal service — e.g. App\Support\BinaryFile\BinaryFileService — bound as a singleton. Nothing in the application should import Com\Tecnick\File\* directly; enforce that with a PHPStan/architecture rule or a Deptrac layer so the dependency stays swappable. This is the single highest-leverage decision, because it converts a legal/maintenance risk into a one-file change.
Configuration shape. A dedicated config/binary-file.php sourcing allowed_hosts, allowed_paths, max_remote_size, max_redirects, cache_path, and case_sensitive_paths from env. Allowlists must be explicit per environment; never derive them from request input, and never use '*'. Fail fast at boot (a provider register() assertion) if allowedPaths is empty in production — a silent strict-deny in prod looks identical to "file not found" and will burn an on-call rotation.
Laravel-idiomatic mapping. Use storage_path('app/...') values as allowedPaths entries, and be aware the library canonicalizes via realpath(), so symlinked release directories (current -> releases/42) and Forge/Vapor-style layouts resolve correctly — verify this explicitly in staging rather than assuming. For local reads, prefer this package's fopenLocal/fReadInt path only where byte-level access is actually required; for whole-file moves, deletes, and uploads, keep using Storage. Do not let this package become a second, competing filesystem abstraction.
Compatibility. Pin the version in composer.json with a caret constraint but commit the lockfile, and treat minor bumps as reviewable because the allowlist is a security boundary. Conflicting global concerns to audit before rollout: any cURL wrapper or middleware (conflicts with reserved callbacks), open_basedir settings (documented interaction with CURLOPT_FOLLOWLOCATION), and upload_tmp_dir overrides that change the cache fallback target. Set maxRemoteSize to a realistic per-endpoint value (single-digit MB) rather than inheriting 50 MB.
Migration path. This is net-new adoption, not a replacement, so "migration" means consolidating scattered raw file_get_contents/curl_init/fopen calls behind the adapter. Sequence:
maxRemoteSize, set CURLOPT_MAXREDIRS explicitly, assert getCachePath() matches configured path at boot to catch the silent fallback, and add a scheduled deleteOlderThan() job if the cache is used.Maintenance. Very low churn expected — the API surface is four classes and the domain (reading bytes, opening files) is stable. Ongoing cost is dominated by governance, not code: tracking upstream releases, re-reviewing allowlist code on minor bumps, and the license obligation itself. Budget a recurring few hours per quarter plus a dependency-bot PR review, not a standing allocation. The zero-dependents figure cuts both ways: no transitive supply-chain surface, but also no ecosystem pressure testing.
Support. Single-maintainer, sponsor-funded project. There is no commercial SLA, no security contact process documented for this package in isolation, and no evidence of an LTS branch. Our mitigation is the adapter boundary: if the package is abandoned, the replacement surface is one file plus the tests. The parent TCPDF ecosystem's longevity is a genuine positive signal but should not be relied on as a commitment for this leaf package.
Scaling. Horizontal scaling is mostly clean — no shared state, no daemon, cache files are per-instance and keyed with a CSPRNG suffix so concurrent workers do not collide. Two caveats. First, memory: remote fetches buffer fully in memory, capped by maxRemoteSize as post-decompression bytes; under concurrency this multiplies against memory_limit and can produce OOM-kills rather than clean errors. Second, cache locality: the cache directory resolves to K_PATH_CACHE, then upload_tmp_dir, then the system temp dir — on multi-node or ephemeral-container deployments that means per-node, per-lifetime caches. That is acceptable for scratch data but must not be mistaken for a durable store; delete() only ever touches its own prefix, so no cross-tenant deletion hazard, but also no protection against unbounded growth if pruning is not scheduled.
Failure modes and observability. Expect four distinct signatures and instrument each: (a) \RangeException from out-of-bounds Byte reads — a programming error, should page nobody but must be caught to avoid 500s; (b) \Com\Tecnick\File\Exception for cURL option rejection, unresolvable cache directory, and invalid cache key arguments — configuration-shaped, alertable; (c) false returns for unreadable URLs and unfollowed redirects — the most dangerous signature, because it is indistinguishable from "empty file" unless the adapter raises; wrap it and convert to a typed exception; (d) silent allowlist denial, which presents as "file not found" and will misdirect incident response. Emit a structured log line on every rejection with the attempted host/path prefix so triage is possible without code archaeology. Also surface a metric for cache directory size and file count if the Cache class is used in a hot path.
Ramp-up. Low for a competent PHP engineer: the API is small, the README is unusually good, and the failure semantics are documented. Realistic ramp is 1–2 days to productive, with the caveats concentrated in three places that deserve explicit onboarding notes: (1) allowlist normalization and matching rules (ports, IPv6 bracketing, punycode, trailing root dot, case sensitivity, NFC folding); (2) the by-reference quirk on isValidURL()/isValidFile() versus the by-value isAllowedUrl()/isAllowedFile() — a genuine footgun that will appear in code review; (3) the reserved cURL callback list, which will silently override anything a developer tries to set for tracing. Assign a single owner for the adapter and the allowlist configuration; the blast radius of a wrong allowlist entry is the entire file-access boundary.
How can I help you explore Laravel packages today?