A secure, fluent path builder for PHP with sanitization strategies and Laravel 11-12 integration.
- 🔒 Security First: Built-in protection against directory traversal attacks with comprehensive validation
- 🎯 Fluent API: Clean, readable path building with method chaining
- 🔧 Sanitization Strategies: Multiple strategies for different use cases (hash, slug, snake_case, timestamp)
- 📁 Extension Preservation: Automatic file extension preservation during sanitization
- 🚀 Immutable Operations: Thread-safe path building with no side effects
- 🏗️ Strategy Pattern: Extensible sanitization system with automatic validation
- 🎨 Type Safety: Full type hints and IDE autocompletion
- 📦 Laravel Integration: Optional Laravel Storage facade integration
- ⚡ Error Handling: Comprehensive exception handling with specific error types
composer require hdaklue/larapathuse Hdaklue\PathBuilder\PathBuilder;
use Hdaklue\PathBuilder\Enums\SanitizationStrategy;
// Or use the facade in Laravel
use LaraPath;
// Basic usage (static)
$path = PathBuilder::base('uploads')
->add('images')
->add('avatar.jpg')
->toString(); // "uploads/images/avatar.jpg"
// Laravel facade usage (auto-registered)
$path = LaraPath::base('uploads')
->add('images')
->add('avatar.jpg')
->toString();
// With sanitization strategies
$path = PathBuilder::base('uploads')
->add('user-123', SanitizationStrategy::HASHED)
->add('My File.jpg', SanitizationStrategy::SLUG)
->validate()
->toString(); // "uploads/a665a45920422f9d417e4867efdc4fb8/my-file.jpg"For detailed examples of how LaraPath solves critical problems in multi-tenant Laravel applications, see Real World Problems.
The guide covers 5 essential scenarios:
- Multi-tenant storage organization with privacy protection
- Database-friendly storage strategies across multiple disks
- File naming conflicts and data loss prevention
- Cross-platform filename sanitization and migration
- Team-wide consistency and standardization
Perfect for understanding LaraPath's value in complex Laravel applications.
LaraPath automatically preserves file extensions during sanitization, ensuring your files maintain their proper types.
// Hash sensitive data (preserves extensions)
$path = PathBuilder::base('storage')
->addFile('[email protected]', SanitizationStrategy::HASHED)
->toString(); // "storage/d549c81aa88e6e76e1f4c141aaae4c6e.com"
// Create URL-friendly names (preserves extensions)
$path = PathBuilder::base('uploads')
->addFile('My Amazing File!.pdf', SanitizationStrategy::SLUG)
->toString(); // "uploads/my-amazing-file.pdf"
// Convert to snake_case (preserves extensions)
$path = PathBuilder::base('files')
->addFile('CamelCase Name.docx', SanitizationStrategy::SNAKE)
->toString(); // "files/camel_case_name.docx"
// Add timestamps for uniqueness (preserves extensions)
$path = PathBuilder::base('temp')
->addFile('session.log', SanitizationStrategy::TIMESTAMP)
->toString(); // "temp/session_1640995200.log"
// Directory names (no extensions to preserve)
$path = PathBuilder::base('uploads')
->add('User Documents', SanitizationStrategy::SLUG)
->toString(); // "uploads/user-documents"All sanitization strategies automatically detect and preserve file extensions:
// Complex filename with special characters
$path = PathBuilder::base('documents')
->addFile('My Complex File Name!@#.pdf', SanitizationStrategy::SLUG)
->toString(); // "documents/my-complex-file-name.pdf"
// Multiple dots in filename - preserves only the last extension
$path = PathBuilder::base('archives')
->addFile('backup.2023.tar.gz', SanitizationStrategy::SLUG)
->toString(); // "archives/backup-2023-tar.gz"
// Files without extensions work normally
$path = PathBuilder::base('configs')
->addFile('README', SanitizationStrategy::SLUG)
->toString(); // "configs/readme"
// Hidden files (starting with dot)
$path = PathBuilder::base('configs')
->addFile('.env.example', SanitizationStrategy::SLUG)
->toString(); // "configs/env.example"$builder = PathBuilder::base('files/video.mp4');
// Extract path components
$extension = $builder->getExtension(); // "mp4"
$filename = $builder->getFilename(); // "video.mp4"
$filenameWithoutExt = $builder->getFilenameWithoutExtension(); // "video"
$directory = $builder->getDirectoryPath(); // "files"
// Modify paths
$newPath = $builder->replaceExtension('webm')->toString(); // "files/video.webm"LaraPath is automatically registered in Laravel applications with facade support and container binding.
// Using the facade (recommended for Laravel)
use LaraPath;
$exists = LaraPath::base('uploads')
->add('avatar.jpg')
->exists('public'); // Uses Storage::disk('public')->exists()
// Using container binding
$builder = app('larapath');
$size = $builder->base('files')
->add('document.pdf')
->size(); // Uses Storage::size()
// Using static methods (framework-agnostic)
use Hdaklue\PathBuilder\PathBuilder;
$url = PathBuilder::base('images')
->add('logo.png')
->url('public'); // Uses Storage::disk('public')->url()
// Delete file
$deleted = LaraPath::base('temp')
->add('cache.tmp')
->delete(); // Uses Storage::delete()
// Get file size in different formats
$path = LaraPath::base('uploads')->add('document.pdf');
$size = $path->size(); // Raw bytes: 1048576
$formatted = $path->getSizeFormatted(); // Human readable: "1 MB"
$sizeInKB = $path->getSizeInKB(); // Binary KB: 1024.0
$sizeInMB = $path->getSizeInMB(); // Binary MB: 1.0
$sizeInKBDecimal = $path->getSizeInKBDecimal(); // Decimal KB: 1048.576LaraPath provides comprehensive security and validation features:
// Automatic path validation
$path = PathBuilder::base('uploads')
->add('../../../etc/passwd') // Dangerous path
->validate() // Throws UnsafePathException
->toString();
// Manual safety check
$isSafe = PathBuilder::isSafe('uploads/../dangerous/path'); // false
$isSafe = PathBuilder::isSafe('uploads/safe/file.txt'); // true
// File existence validation
$path = PathBuilder::base('uploads')
->addFile('document.pdf')
->mustExist('public') // Throws PathNotFoundException if file doesn't exist
->toString();
$path = PathBuilder::base('uploads')
->addFile('new-file.pdf')
->mustNotExist('public') // Throws PathAlreadyExistsException if file exists
->toString();LaraPath throws specific exceptions for different error conditions:
use Hdaklue\PathBuilder\Exceptions\UnsafePathException;
use Hdaklue\PathBuilder\Exceptions\PathNotFoundException;
use Hdaklue\PathBuilder\Exceptions\PathAlreadyExistsException;
use Hdaklue\PathBuilder\Exceptions\InvalidSanitizationStrategyException;
try {
$path = PathBuilder::base('../dangerous')
->addFile('file.txt')
->validate();
} catch (UnsafePathException $e) {
// Handle directory traversal attempt
echo "Unsafe path detected: " . $e->getMessage();
}
try {
$path = PathBuilder::base('uploads')
->addFile('missing.txt')
->mustExist('local');
} catch (PathNotFoundException $e) {
// Handle missing file
echo "File not found: " . $e->getMessage();
}
try {
$path = PathBuilder::base('uploads')
->add('input', 'InvalidStrategy');
} catch (InvalidSanitizationStrategyException $e) {
// Handle invalid sanitization strategy
echo "Invalid strategy: " . $e->getMessage();
}Create custom sanitization strategies by implementing the SanitizationStrategyContract:
use Hdaklue\PathBuilder\Contracts\SanitizationStrategyContract;
class UuidStrategy implements SanitizationStrategyContract
{
public static function apply(string $input): string
{
return \Str::uuid()->toString();
}
}
// Use custom strategy
$path = PathBuilder::base('files')
->add('temp-file', UuidStrategy::class)
->toString(); // "files/550e8400-e29b-41d4-a716-446655440000"LaraPath automatically validates that custom strategies implement the required contract:
// ✅ Valid strategy - implements SanitizationStrategyContract
$path = PathBuilder::base('files')
->add('input', MyCustomStrategy::class)
->toString();
// ❌ Invalid strategy - throws InvalidSanitizationStrategyException
$path = PathBuilder::base('files')
->add('input', 'NonExistentStrategy')
->toString(); // Exception: Strategy class NonExistentStrategy not found
// ❌ Invalid strategy - missing contract implementation
$path = PathBuilder::base('files')
->add('input', \stdClass::class)
->toString(); // Exception: Strategy class stdClass must implement SanitizationStrategyContract interfacePathBuilder::base(string $path, ?SanitizationStrategy $strategy = null): selfadd(string $name, ?SanitizationStrategy $strategy = null): selfaddFile(string $filename, ?SanitizationStrategy $strategy = null): selfaddTimestampedDir(): selfaddHashedDir(string $input, string $algorithm = 'md5'): selfreplaceExtension(string $newExt): selfgetExtension(): stringgetFilename(): stringgetFilenameWithoutExtension(): stringgetDirectoryPath(): stringensureTrailing(): selfremoveTrailing(): selfvalidate(): selftoString(): string
mustExist(string $disk = 'local'): selfmustNotExist(string $disk = 'local'): selfexists(string $disk = 'local'): boolsize(string $disk = 'local'): intgetSizeFormatted(string $disk = 'local', int $precision = 3): stringgetSizeInKB(string $disk = 'local'): floatgetSizeInMB(string $disk = 'local'): floatgetSizeInGB(string $disk = 'local'): floatgetSizeInKBDecimal(string $disk = 'local'): floatgetSizeInMBDecimal(string $disk = 'local'): floatgetSizeInGBDecimal(string $disk = 'local'): floaturl(string $disk = 'local'): stringdelete(string $disk = 'local'): bool
PathBuilder::build(array $segments): stringPathBuilder::join(string ...$segments): stringPathBuilder::normalize(string $path): stringPathBuilder::isSafe(string $path): boolPathBuilder::buildRelativePath(string $absolutePath, string $basePath): string
All strategies automatically preserve file extensions when present:
SanitizationStrategy::HASHED- MD5 hash of input (preserves extensions:user.txt→hash.txt)SanitizationStrategy::SLUG- URL-friendly slug (preserves extensions:My File!.pdf→my-file.pdf)SanitizationStrategy::SNAKE- snake_case conversion (preserves extensions:CamelCase.docx→camel_case.docx)SanitizationStrategy::TIMESTAMP- Appends Unix timestamp (preserves extensions:file.log→file_1640995200.log)
- PHP ^8.2
- illuminate/support ^11.0|^12.0 (for Laravel integration)
composer testMIT License. See LICENSE for details.
Please see CONTRIBUTING.md for details.
If you discover any security related issues, please email [email protected] instead of using the issue tracker.