Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tc Lib File Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and verify the platform floor

composer require tecnickcom/tc-lib-file

This is not a Laravel package — there is no service provider, facade, migration, or config file. It is a plain PHP library that drops into vendor/ and is auto-loaded. Check two things before going further:

  • php -v is 8.2+ (this alone disqualifies older Laravel 8/9 apps).
  • ext-curl and ext-pcre are present (php -m); ext-intl is optional but matters on macOS for Unicode path folding.

2. Look at the source, not the README

The API surface is tiny — five classes in src/. Read them in this order:

  • src/File.php — the only class with meaningful configuration; everything else is a leaf utility.
  • src/Byte.php — ~100 lines of bounds-checked big-endian readers.
  • src/Cache.php — temp-file naming and cleanup.
  • src/Dir.php, src/Exception.php — trivial.

The generated API docs at https://tcpdf.org/docs/srcdoc/tc-lib-file are the only reference; there are no guides, no recipes, and no Laravel examples.

3. Expect the first call to fail (by design)

File is strict-deny: allowedHosts and allowedPaths both default to [], so your first fopenLocal() on a storage_path() file will be rejected. The minimum viable wire-up in a Laravel app is a singleton bound with config-backed allowlists:

// config/tcfile.php
return [
    'allowed_paths' => [storage_path('app/imports'), storage_path('app/private')],
    'allowed_hosts' => ['partner.example.com'],
];

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(\Com\Tecnick\File\File::class, fn () => new \Com\Tecnick\File\File(
        allowedHosts: config('tcfile.allowed_hosts'),
        allowedPaths: config('tcfile.allowed_paths'),
        curlopts: [CURLOPT_MAXREDIRS => 3],
    ), );
}

4. First real use case

The one thing this library does that Laravel's Storage does not: structured byte reads from a binary stream. A realistic day-one task is sniffing a file's true type or pulling a header field out of an upload:

$fh = $file->fopenLocal(Storage::path("imports/{$name}"), 'rb');
$sig = (new \Com\Tecnick\File\Byte(fread($fh, 16)))->getULong(0);

If your need is "read a file," use Storage. If it is "read bytes of a file with bounds checking and typed exceptions," this is the tool.

Implementation Patterns

Bind once, treat as read-only config. The File instance is mutable (setAllowedHosts(), setAllowedPaths(), setCaseSensitivePaths()), which is a hazard under Octane or long-running workers. Either bind it as a scoped instance and never mutate it after construction, or construct it per job. Prefer the constructor arguments over setters for exactly this reason.

Wrap it behind an app-level adapter. Nothing about this package is Laravel-idiomatic, and its API (by-reference validators, false returns, SPL vs. library exceptions) leaks. A thin BinaryFileReader service that exposes header(string $path, int $offset): int lets you unit-test with Storage::fake() and swap implementations later.

Parse binaries with Byte, not with unpack() scattered around. Byte is immutable, validates every read against the string length, and throws \RangeException on overrun. This is genuinely nicer than raw unpack() for font/image/custom-format parsing:

$byte = new \Com\Tecnick\File\Byte($binary);
if ($byte->getUShort(0) === 0x4D5A) {      // "MZ"
    $offset = $byte->getULong(0x3C);       // PE header pointer
}
// 32-bit readers assume 64-bit PHP — check PHP_INT_SIZE if you target 32-bit.

Aliases exist for the OpenType crowd (getUFWord/getFWord), which is a hint at who actually consumes this.

Uploads: skip fopenLocal() on the temp path. UploadedFile::getRealPath() points into upload_tmp_dir/sys_get_temp_dir(), which is not in your allowedPaths. Adding the system temp dir to the allowlist is broad trust you probably do not want. Read the content with $request->file('x')->get() and hand it to Byte, or copy the upload into an allowlisted directory first:

$path = $request->file('doc')->store('imports', 'local');   // allowlisted root
$fh   = $file->fopenLocal(Storage::path($path), 'rb');

Remote fetches: use this when the allowlist is the feature. getUrlData()/fileGetContents() go through cURL regardless of allow_url_fopen, buffer in memory, and are bounded by maxRemoteSize (50 MB default). That is a genuinely useful pattern for "pull a partner's logo/PDF from a fixed set of hosts" in a job with no egress policy elsewhere:

$file = new \Com\Tecnick\File\File(
    allowedHosts: ['cdn.partner.com', 'downloads.partner.com'],
    curlopts: [CURLOPT_MAXREDIRS => 5, CURLOPT_TIMEOUT => 10],
);
$pdf = $file->getUrlData('https://cdn.partner.com/invoice/1234.pdf');

For general outbound HTTP, use the Http facade. Reach for this only when you want a hard host allowlist plus a compressed-size-independent memory ceiling in one call.

Cache: use it for derived artifacts, and always keep the returned path. getNewFileName() carries a CSPRNG suffix; $type/$key are deletion scopes, not lookup keys. You cannot re-derive a filename later.

$cache = (new \Com\Tecnick\File\Cache('thumbs'))
    ->setCachePath(storage_path('app/tc-cache'));

$path = $cache->getNewFileName('image', 'logo');   // store this in the DB / response
file_put_contents($path, $webp);

Clean up on a schedule rather than inline — deleteOlderThan() is the only sane long-term story:

// app/Console/Commands/PruneBinaryCache.php  (Schedule::command(...)->hourly())
(new \Com\Tecnick\File\Cache('thumbs'))
    ->setCachePath(storage_path('app/tc-cache'))
    ->deleteOlderThan(86400);

Dir::findParentDir() is a TCPDF-compat shim, not an app utility. It exists so PDF/font stacks can locate a writable cache/ folder without K_PATH_CACHE. In a Laravel app you already know your writable path; only pull this in when integrating legacy TCPDF-era code.

Testing. Byte needs no fixtures beyond strings — excellent for table-driven PHPUnit tests. For File, pass caseSensitivePaths: true|false explicitly so allowlist tests pass on any CI runner, and point allowedPaths at a Storage::fake() root.

Gotchas and Tips

  • Empty allowlists mean "reject everything," not "allow everything." This trips up nearly every first integration, and the failure mode is a false return or a \Com\Tecnick\File\Exception, not a warning. Log the rejected path/host alongside the failure or you will be debugging blind.
  • isValidURL() and isValidFile() take arguments by reference and rewrite them (trim, prepend file://). They only accept variables — passing a literal is a fatal error. Use isAllowedUrl() / isAllowedFile() for literals and expressions; those are pure booleans.
  • Symlink traps. allowedPaths stores both literal and realpath() forms, which saves you when storage_path() sits behind public/storage or a current -> releases/42 deploy symlink. But on macOS /tmp and /var canonicalize to /private/...; if you build allowlists from env vars in a container, verify with getCachePath()-style introspection rather than assuming.
  • Case sensitivity is auto-detected per volume. APFS is case-insensitive by default; a case-sensitive volume or a case-insensitive bind mount on Linux will silently break matching. Set caseSensitivePaths explicitly in Docker/K8s deployments.
  • CURLOPT_MAXREDIRS defaults to 0, which means no redirects at all. A 3xx is reported as unreadable — and critically, the 3xx body is never returned as content (this matters when open_basedir disables CURLOPT_FOLLOWLOCATION). Each followed Location is re-validated against allowedHosts, so redirect targets need to be listed too.
  • Some cURL options are reserved and silently replaced: CURLOPT_WRITEFUNCTION, CURLOPT_PROGRESSFUNCTION, CURLOPT_XFERINFOFUNCTION, CURLOPT_NOPROGRESS, and (with redirects on) CURLOPT_HEADERFUNCTION. Don't try to implement streaming downloads by passing a write callback — it will be overwritten. The library buffers in memory by design.
  • maxRemoteSize counts inflated bytes. A gzipped response is measured after decompression, which is the safe direction, but a legitimate 40 MB PDF that compresses to 5 MB still counts as 40 MB.
  • Cache strips _ (and everything outside [A-Za-z0-9-]) from prefix/type/key; + and / become - so base64 prefixes survive. An empty prefix is replaced by a random one. Consequence: Cache('app') and Cache('app_v2') do not collide on deletion — but also, you cannot reconstruct names by concatenation. Always persist what getNewFileName() returns.
  • delete(null, $key) throws (a key needs a type), and deleteOlderThan(-1) throws rather than deleting everything. Both are deliberate footguns removed.
  • setCachePath() falls back silently to K_PATH_CACHE, then upload_tmp_dir, then the system temp dir. Compare getCachePath() to what you passed if cache location matters (it usually does — temp dirs get wiped).
  • Two exception families. Library throws \Com\Tecnick\File\Exception; Byte throws SPL \RangeException for out-of-bounds reads. A generic catch (\Com\Tecnick\File\Exception) will miss truncated-file errors. Catch both in parser code.
  • fopenLocal() force-adds the b flag, so Windows CRLF translation won't corrupt byte reads — but a stream you opened yourself and passed elsewhere will not get that treatment.
  • Input rejection is part of the contract: paths containing NUL bytes and URLs containing C0 control chars or DEL are reported as invalid rather than throwing, keeping the bool validators honest. If you rely on parse_url() elsewhere, this library is stricter — do not assume parity.
  • Windows limits are not expanded: 8.3 short names (PROGRA~1), alternate data streams (file.txt:stream), trailing dots/spaces, and reserved device names are treated as untrusted literals. Allowlisting C:\data will not match a path spelled C:\PROGRA~1\data.
  • Version sanity check. The metadata shows a 2026 release date and zero dependents, which is inconsistent — this is a transitive dependency of tc-lib-pdf/TCPDF (100M+ installs), so the real usage is invisible in the dependents count but the version metadata is worth verifying with composer show -a tecnickcom/tc-lib-file before pinning. You likely already have it installed if TCPDF is in the tree; do not declare a conflicting constraint.
  • Debugging remote fetches: enable CURLOPT_VERBOSE temporarily via setCurlOpts() (it is not in the reserved list) and capture with CURLOPT_STDERR. Remember that unreadable URLs return false, not an exception, so distinguish "blocked by allowlist" from "network error" by checking isAllowedUrl() first.
  • Where to extend: there is no hook system. The extension points are the constructor options and the public setters; for anything else, compose (BinaryFileReader wrapping Byte) rather than hoping for injection points. Treat the package as a sealed primitive and keep your Laravel-facing code outside it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core