jackiedo/path-helper
PHP helper for working with local filesystem paths. Normalize directory separators, restyle paths for Unix/Windows/current OS, build absolute/relative paths, check if paths are absolute/relative, and test ancestor/descendant relationships. Composer install.
Installation:
composer require jackiedo/path-helper
Add to composer.json under require if using a monorepo or constrained environment.
First Use Case: Normalize paths in a Laravel service or controller:
use Jackiedo\PathHelper\Path;
$normalizedPath = Path::normalize(storage_path('app/logs/../backups'));
// Returns: storage_path('app/backups') with OS-specific separators
Where to Look First:
normalize(), absolute(), relative(), and isAbsolute() for 80% of use cases.unixStyle() or winStyle() when generating paths for cross-platform APIs (e.g., CLI tools or shared storage).isAncestor()/isDescendant() for security checks (e.g., ensuring uploaded files stay within storage/app/uploads/).Storage::put(), File::move()).$userUploadPath = Path::normalize(public_path('uploads/' . $request->file('avatar')->hashName()));
Storage::put($userUploadPath, $request->file('avatar')->get());
osStyle() to generate paths for commands or logs, ensuring consistency across dev/staging/prod.$logPath = Path::osStyle(storage_path('logs/laravel-' . date('Y-m-d') . '.log'));
Artisan::call('log:clear', ['--path' => $logPath]);
isAbsolute() and isDescendant() to prevent directory traversal.if (!Path::isAbsolute($request->path) || !Path::isDescendant($request->path, storage_path('app/safe-zone'))) {
abort(403, 'Invalid path');
}
absolute() and relative() for dynamic path resolution (e.g., generating URLs or symlinks).$basePath = Path::absolute('config/');
$relativeConfig = Path::relative($basePath, 'config/production.php');
// Output: '../production.php' (relative to basePath)
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton('pathHelper', function () {
return new \Jackiedo\PathHelper\Path();
});
}
Usage:
$helper = app('pathHelper');
$normalized = $helper->normalize($path);
$uploadPath = Path::normalize(storage_path('uploads/' . $userId));
if (!Path::isDescendant($uploadPath, storage_path('uploads'))) {
throw new \Exception('Upload path outside allowed directory');
}
$assetPath = Path::unixStyle(public_path('assets/images/logo.png'));
$url = asset(Path::relative(public_path(), $assetPath));
\Log::debug('Normalized path:', [
'input' => $rawPath,
'output' => Path::normalize($rawPath),
]);
Path::normalize() before passing paths to Storage::disk()->put() or File::copy() to avoid OS-specific issues.$normalizedPath = Path::normalize(storage_path('app/backups/' . $filename));
Storage::disk('local')->put($normalizedPath, $content);
protected $signature = 'backup:create {--path= : Backup path}';
public function handle()
{
$path = Path::normalize($this->option('path') ?? storage_path('backups'));
// Proceed with backup logic
}
Path helper in tests to isolate filesystem logic:
$this->partialMock(Jackiedo\PathHelper\Path::class, function ($mock) {
$mock->shouldReceive('normalize')->andReturn('mocked/path');
});
config/filesystems.php) to avoid hardcoding:
'disks' => [
'local' => [
'root' => Path::normalize(env('STORAGE_PATH', storage_path('app'))),
],
],
Path Existence Assumption:
Path::absolute() does not check if the path exists (unlike realpath()). Always validate existence separately:
$absolutePath = Path::absolute($relativePath);
if (!file_exists($absolutePath)) {
// Handle non-existent path
}
Windows Drive Letters:
C:\Users\file.txt are treated as absolute, but Path::isAbsolute() may behave unexpectedly on Unix-like systems. Normalize first:
$path = Path::normalize('C:\Users\file.txt');
Trailing Slashes:
isDescendant() may fail if paths have inconsistent trailing slashes. Normalize both paths:
$path1 = Path::normalize('/base/path/');
$path2 = Path::normalize('/base/path');
Path::isDescendant($path1, $path2); // true
Built-in Functions Conflict:
absolute_path()) in Laravel projects to prevent naming collisions with other packages or custom helpers.PHP 5.4+ Limitation:
Spatie\Path.Unexpected Path Normalization:
Path::normalize($path, '|') to visualize path segments:
$debugPath = Path::normalize($path, '|');
// Output: base|folder|..|file.txt
Absolute Path Issues:
Path::absolute($path) with realpath($path) to debug discrepancies:
$absolute = Path::absolute($path);
$realpath = realpath($path);
\Log::debug('Absolute vs Realpath', [$absolute, $realpath]);
Ancestor/Descendant Logic:
Path::isAncestor('/base', '/base/../parent'); // false (avoids traversal)
Path::isDescendant('/base/sub', '/base'); // true
Consistent Separators:
Path::unixStyle() for API responses or logs to ensure consistency:
$logPath = Path::unixStyle(storage_path('logs/error.log'));
Path Joining:
DIRECTORY_SEPARATOR for explicit control:
$joined = Path::normalize($path1 . DIRECTORY_SEPARATOR . $path2);
Laravel Helper Integration:
Str or File helpers by adding static methods:
if (!class_exists('Str')) {
class_alias('Jackiedo\PathHelper\Path', 'Str');
}
(Use cautiously to avoid conflicts.)Performance:
$normalizedCachePath = Path::normalize(config('cache.path'));
Alternative for PHP 8+:
Spatie\Path for modern PHP features:
composer require spatie/path
Example:
use Spatie\Path\Path as SpatiePath;
$path = SpatiePath::make($path)->normalize();
How can I help you explore Laravel packages today?