php-extended/php-file-object
Lightweight file object abstraction for PHP: immutable value objects representing files and their paths, with helpers for metadata and seamless integration into the php-extended ecosystem. Framework-agnostic, composable, and easy to use.
Install through Composer:
composer require php-extended/php-file-object
This package is the implementation half of a two-package pair: php-extended/php-file-interface defines the contracts, and php-extended/php-file-object provides concrete classes that satisfy them. The first place to look is therefore not this package's src/, but the interface package:
# contracts first — this is what your code should depend on
ls vendor/php-extended/php-file-interface/src
# then the concrete implementations
ls vendor/php-extended/php-file-object/src
composer show php-extended/php-file-object # version + transitive deps
A minimal first use case is treating a filesystem path as a value object and passing it to code that is typed against the interface, so the concrete implementation can be swapped later (or mocked in tests):
use PhpExtended\File\FileInterface;
function checksum(FileInterface $file): string
{
// read through whatever accessor the interface exposes
}
// Instantiate the concrete class once, at the edge of your application.
// Confirm the exact class name in vendor/php-extended/php-file-object/src.
$file = new \PhpExtended\File\File(__DIR__ . '/composer.json');
echo checksum($file);
That is the intended shape of the library: construct concrete file objects at the boundary (controllers, commands, service wiring), and let the rest of the codebase speak only in interfaces.
Interface-first type hints. Never let the concrete class leak past the construction site. Type-hint FileInterface (and any iterator/metadata interfaces the package ships) in your service signatures. This is the entire point of the interface/object split and makes your code testable without touching the disk.
public function __construct(private FileInterface $configFile) {}
Factory or DI wiring for construction. Because the concrete classes are an implementation detail, create them in one place — a factory, a Symfony service definition, or a small Container closure — rather than sprinkling new calls through the codebase.
// config/services.yaml
services:
PhpExtended\File\FileInterface:
factory: ['App\File\FileFactory', 'fromPath']
arguments: ['%kernel.project_dir%/var/data.csv']
Decorator for caching and cross-cutting concerns. The interface boundary is the natural seam for a decorator that memoises expensive reads, normalises paths, or logs access, while delegating to the wrapped object:
final class CachedFile implements FileInterface
{
public function __construct(private FileInterface $inner) {}
// delegate every method; cache the expensive ones
}
Iteration over directories. If the interface package exposes an iterator contract, use foreach against the iterator rather than scandir()/glob() — you get typed objects instead of raw strings and stay consistent with the rest of the abstraction.
Testing with mocks. In unit tests, mock FileInterface via PHPUnit and avoid the filesystem entirely. Reserve real file objects for a small number of integration tests that exercise the concrete implementation.
Composition with the broader php-extended ecosystem. These packages tend to be small, single-responsibility value objects designed to compose. Expect to pass a file object into parsers, readers, or validators from sibling packages rather than having this package do the parsing itself.
src/ end-to-end before adopting it; it is small enough that this takes minutes and removes all guesswork about method names and constructor signatures.php-file-interface will ripple into this package. Use an exact constraint ("php-extended/php-file-object": "1.2.*") and commit composer.lock.composer why php-extended/php-file-interface.false/exception returns on read, and do not cache file contents in the object unless you own the lifecycle.__DIR__, %kernel.project_dir%) into the constructor.php-extended packages typically target specific PHP minors. Verify the require block before adding it to a project on an older or bleeding-edge runtime.var_dump($file) on a value object is usually enough to see the wrapped path; if behaviour is surprising, confirm you are not holding a stale object after a file move, and check get_class() to ensure a decorator is actually wrapping the concrete class rather than being wrapped in the wrong order.FileInterface yourself (or decorate) rather than to subclass the concrete class. Subclassing couples you to internals the maintainers never intended as an API.SplFileInfo or Symfony's Filesystem later is a one-file change.How can I help you explore Laravel packages today?