The Path Helper
Rhapsody\Core\Helpers\Path is a small static helper that returns absolute filesystem paths to the important directories of your application. Whenever your code needs to read, write, include, or check a file, build the location with Path instead of hand-written relative paths. It is dependency-free, requires no setup, and costs next to nothing at runtime.
use Rhapsody\Core\Helpers\Path;
$logFile = Path::logs('import.log');
// /var/www/mysite/storage/logs/import.log
Path gives you locations on the server's disk. It never returns a web address. See Paths vs URLs below.
Why use it?
Hard-coded and relative paths are one of the most common sources of “works on my machine” bugs. Path removes the whole category:
- Independent of the working directory. A relative path like
'storage/logs/app.log'is resolved against the current working directory, which is not the same for a web request, a CLI command, a cron job, or a persistent worker.Pathalways starts from your project root. - No
../../../chains. Moving a file to another folder never silently breaks the paths inside it. - Cross-platform. Slashes in the path you pass in are normalised to the operating system's native separator, so the same code works on Windows (WAMP), Linux, and macOS.
- One source of truth. If a directory ever moves, there is one place to update, not dozens of string literals.
Available Methods
Every method accepts an optional $path string that is appended to the directory.
| Method | Resolves to |
|---|---|
Path::root() | The application root (the folder that contains vendor/) |
Path::app() | app/ – your controllers, models, and other application code |
Path::config() | config/ |
Path::public() | public/ – used to store CSS / JS and images |
Path::storage() | storage/ |
Path::cache() | storage/cache/ |
Path::logs() | storage/logs/ |
Path::resources() | resources/ |
Path::views() | views/ |
Path::database() | db/ – migrations, seeds, and similar files (note the folder is named db, not database) |
Appending a sub-path
Pass the rest of the location as the argument. You can use forward slashes everywhere, even on Windows:
Path::app('Controllers/HomeController.php');
Path::config('mail.php');
Path::public('uploads/avatars');
Path::views('emails/welcome.twig');
Path::root('.env');
What you can rely on
- Results never end with a separator, with or without a sub-path. Pass the entire sub-path as the argument rather than concatenating pieces yourself.
- Forward and backward slashes in the argument are converted to the OS's native separator.
- Leading slashes in the argument are stripped, so
Path::config('/mail.php')andPath::config('mail.php')are identical. A leading slash does not mean “the root of the server's disk”. Pathonly builds strings. It does not check that the file exists and does not create missing folders.
Common Examples
Reading a config file
$settings = require Path::config('custom-settings.php');
Writing to a custom log file
$file = Path::logs('payments.log');
file_put_contents($file, date('c') . " Payment received\n", FILE_APPEND | LOCK_EX);
Saving an uploaded file
$directory = Path::public('uploads/avatars');
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
move_uploaded_file($tmpName, $directory . DIRECTORY_SEPARATOR . $safeFilename);
For a complete upload workflow with validation, see the File Uploader documentation.
Pointing at a theme's assets
$theme = $_ENV['APP_THEME'] ?? 'default';
$imageDirectory = Path::public('themes/' . $theme . '/images');
This is also how you would change where the image processor looks for source images. See Image Processing.
Checking for a file
if (is_file(Path::storage('exports/report.csv'))) {
// ...
}
How the Project Root Is Found
Every other method is built on Path::root(), which determines the base directory like this:
- If a
ROOT_DIRconstant is defined, it is used. The application'spublic/index.phpdefines this for you. - Otherwise
Pathfalls back to walking up from its own location insidevendor/arout/rhapsody-core/src/Helpers/to the project root.
With a standard install you never need to think about this. If you use a non-standard directory layout, define ROOT_DIR in your entry point before the framework boots and every Path method will follow it.
Because the fallback relies on the package living inside vendor/, make sure ROOT_DIR is defined in any custom entry script that runs from somewhere unusual (for example a standalone CLI or worker script).
Paths vs URLs
This is the most common point of confusion. Path answers “where is this file on the server?”. A browser needs to be told “what address do I request?”.
// Filesystem path – use for PHP file operations
Path::public('uploads/photo.jpg');
// C:\wamp64\www\mysite\public\uploads\photo.jpg (Windows)
// /var/www/mysite/public/uploads/photo.jpg (Linux)
<img src="{{ base_url }}/uploads/photo.jpg">
Rule of thumb: PHP file functions get a Path; HTML gets a URL. Never output a Path value into a page, as it would expose your server's directory structure.
Security: Never Pass Raw User Input
Path is a path builder, not a sandbox. It does not strip ../ segments, so a value taken straight from a request could escape the folder you intended.
Path::storage('uploads/' . $_GET['file']) can be abused with ?file=../../.env.
Reduce user input to a bare filename with basename(), or verify the resolved location stays inside the expected folder:
$base = realpath(Path::storage('uploads'));
$file = realpath(Path::storage('uploads/' . $name));
if ($base === false || $file === false || !str_starts_with($file, $base . DIRECTORY_SEPARATOR)) {
// Reject the request
}
Adding Your Own Locations
Need a shortcut for a folder your app uses constantly? Extend Path in your application and add a method. All of the inherited methods keep working.
namespace App\Helpers;
use Rhapsody\Core\Helpers\Path;
class AppPath extends Path
{
public static function uploads(string $path = ''): string
{
return self::storage('uploads/' . ltrim($path, '/\\'));
}
}
// Usage
AppPath::uploads('invoices/2026-001.pdf');
Remember the Golden Rule: never edit files in /vendor. Extending the class in your own app/ directory keeps your additions safe during framework updates.