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
Paths, not URLs: 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. Path always 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') and Path::config('mail.php') are identical. A leading slash does not mean “the root of the server's disk”.
  • Path only 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:

  1. If a ROOT_DIR constant is defined, it is used. The application's public/index.php defines this for you.
  2. Otherwise Path falls back to walking up from its own location inside vendor/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.

Danger: 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.


Related Documentation