The Paths system provides a centralized way to handle file paths and URLs within your WordPress plugin or theme. It simplifies working with file system paths and their corresponding public URLs, making it easy to reference assets, read file contents, and generate URLs.
- Unified interface for file system paths and public URLs
- Type-safe path objects with helpful methods
- Automatic path normalization across different operating systems
- Built-in file operations with proper error handling
- Dependency injection support via the service container
The Paths service can be injected into any class that needs to work with file paths:
use VAF\WP\Framework\Paths\Paths;
class AssetManager
{
public function __construct(
private readonly Paths $paths
) {}
public function getImageUrl(string $imageName): string
{
$path = $this->paths->fromPluginRoot('resources/images/' . $imageName);
return $path->publicUrl();
}
public function loadConfig(): array
{
$path = $this->paths->fromPluginRoot('config/settings.json');
return $path->decodedJson();
}
}When you call fromPluginRoot(), you get a Path object with these methods:
$path = $this->paths->fromPluginRoot('resources/style.css');
// Get the public URL (for use in HTML, enqueuing assets, etc.)
$url = $path->publicUrl(); // https://example.com/wp-content/plugins/my-plugin/resources/style.css
// Get the absolute file system path
$absolutePath = $path->absolutePath(); // /var/www/html/wp-content/plugins/my-plugin/resources/style.css// Read file contents
$path = $this->paths->fromPluginRoot('data/config.json');
$content = $path->content(); // Returns file contents as string
// Check if file exists before reading
if ($path->exists()) {
$content = $path->content();
}$path = $this->paths->fromPluginRoot('assets/image.png');
// Check file properties
$exists = $path->exists(); // true/false
$readable = $path->isReadable(); // true/false
$writable = $path->isWritable(); // true/false
// Get file metadata
$size = $path->size(); // File size in bytes
$mimeType = $path->mimeType(); // e.g., "image/png"
// Decode JSON files
$data = $path->decodedJson(); // Returns decoded JSON as array
$obj = $path->decodedJson(false); // Returns decoded JSON as objectFor files that need to be publicly accessible:
class ThemeAssets
{
public function __construct(
private readonly Paths $paths
) {}
public function enqueueStyles(): void
{
$stylePath = $this->paths->fromPluginRoot('public/css/main.css');
if ($stylePath->exists()) {
wp_enqueue_style(
'my-plugin-styles',
$stylePath->publicUrl(),
[],
filemtime($stylePath->absolutePath())
);
}
}
public function getLogoUrl(): string
{
return $this->paths->fromPluginRoot('public/images/logo.png')->publicUrl();
}
}For files that should not be publicly accessible:
use VAF\WP\Framework\Paths\PathException;
class SecurityManager
{
public function __construct(
private readonly Paths $paths
) {}
public function loadPrivateKey(): string
{
$keyPath = $this->paths->fromPluginRoot('private/keys/api.key');
if (!$keyPath->exists()) {
throw new PathException('API key not found');
}
if (!$keyPath->isReadable()) {
throw new PathException('API key is not readable');
}
return trim($keyPath->content());
}
}class ConfigLoader
{
public function __construct(
private readonly Paths $paths
) {}
public function loadDatabaseConfig(): array
{
$configPath = $this->paths->fromPluginRoot('config/database.php');
if ($configPath->exists()) {
return include $configPath->absolutePath();
}
return [];
}
public function loadJsonConfig(string $filename): array
{
$path = $this->paths->fromPluginRoot('config/' . $filename);
if (!$path->exists()) {
return [];
}
try {
return $path->decodedJson();
} catch (PathException $e) {
// Handle invalid JSON
return [];
}
}
}Path operations throw PathException when something goes wrong:
use VAF\WP\Framework\Paths\PathException;
try {
$path = $this->paths->fromPluginRoot('data/important.txt');
$content = $path->content();
} catch (PathException $e) {
// Handle file not found or read errors
error_log('Failed to read file: ' . $e->getMessage());
}-
Check file existence before operations that might fail:
if ($path->exists() && $path->isReadable()) { $content = $path->content(); }
-
Use relative paths from plugin root:
// Good $this->paths->fromPluginRoot('assets/style.css'); // Avoid hardcoding absolute paths
-
Separate public and private files in your directory structure:
my-plugin/ ├── public/ # Files accessible via URL │ ├── css/ │ ├── js/ │ └── images/ ├── private/ # Files not accessible via URL │ ├── keys/ │ └── data/ └── config/ # Configuration files -
Cache file contents when appropriate:
class ConfigCache { private ?array $config = null; public function getConfig(): array { if ($this->config === null) { $path = $this->paths->fromPluginRoot('config/settings.json'); $this->config = $path->decodedJson(); } return $this->config; } }
class TemplateRenderer
{
public function __construct(
private readonly Paths $paths,
private readonly TemplateRenderer $renderer
) {}
public function renderTemplate(string $template, array $data = []): string
{
// Pass asset URLs to templates
$data['assetUrl'] = $this->paths->fromPluginRoot('public/')->publicUrl();
return $this->renderer->render($template, $data);
}
}class FileBasedSetting extends Setting
{
public function __construct(
private readonly Paths $paths,
string $baseName
) {
parent::__construct('file_setting', $baseName);
}
protected function getDefaultConfigPath(): string
{
return $this->paths->fromPluginRoot('config/defaults.json')->absolutePath();
}
}Here's a complete example of an asset manager using the Paths system:
namespace MyPlugin\Services;
use VAF\WP\Framework\Paths\Paths;
use VAF\WP\Framework\Hook\Attribute\AsHookContainer;
use VAF\WP\Framework\Hook\Attribute\Hook;
#[AsHookContainer]
class AssetManager
{
private array $scripts = [];
private array $styles = [];
public function __construct(
private readonly Paths $paths
) {}
#[Hook(tag: "wp_enqueue_scripts")]
public function enqueueAssets(): void
{
$this->enqueueStyles();
$this->enqueueScripts();
}
private function enqueueStyles(): void
{
$styles = [
'main' => 'public/css/main.css',
'theme' => 'public/css/theme.css',
];
foreach ($styles as $handle => $relativePath) {
$path = $this->paths->fromPluginRoot($relativePath);
if ($path->exists()) {
wp_enqueue_style(
'my-plugin-' . $handle,
$path->publicUrl(),
[],
filemtime($path->absolutePath())
);
}
}
}
private function enqueueScripts(): void
{
$scripts = [
'app' => [
'path' => 'public/js/app.js',
'deps' => ['jquery'],
'in_footer' => true
],
'admin' => [
'path' => 'public/js/admin.js',
'deps' => ['jquery', 'wp-api'],
'in_footer' => true
]
];
foreach ($scripts as $handle => $config) {
$path = $this->paths->fromPluginRoot($config['path']);
if ($path->exists()) {
wp_enqueue_script(
'my-plugin-' . $handle,
$path->publicUrl(),
$config['deps'],
filemtime($path->absolutePath()),
$config['in_footer']
);
}
}
}
public function getImageUrl(string $imageName): string
{
return $this->paths->fromPluginRoot('public/images/' . $imageName)->publicUrl();
}
public function loadManifest(): array
{
$manifestPath = $this->paths->fromPluginRoot('public/build/manifest.json');
if (!$manifestPath->exists()) {
return [];
}
return $manifestPath->decodedJson();
}
}