responsive-sk / slim4-root
Root path management with auto-discovery, base path detection and testing utilities for Slim 4 applications
Requires
- php: ^7.4 || ^8.0
- psr/container: ^1.0 || ^2.0
- slim/slim: ^4.0
Requires (Dev)
- php-di/php-di: ^6.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
- slim/psr7: ^1.0
- squizlabs/php_codesniffer: ^3.12
README
Root path management with auto-discovery and base path detection for Slim 4 applications. Say goodbye to relative paths like __DIR__ . '/../../../config'
and hello to clean, consistent path access.
New in 1.4
- Automatic root path discovery for common directory structures
- Built-in path validation with detailed error messages
- Cross-platform path normalization for Windows and Unix
- Dedicated exception handling for path-related errors
- Enhanced developer experience with more intuitive API
- No more relative paths with
../
- everything is relative to the root - Base path detection for applications running in subdirectories
- Testing utilities for easier test setup and execution
- Monolog integration for easy logging setup
Features
- Centralized path management for Slim 4 applications
- Auto-discovery of common directory structures
- Support for custom directory structures
- Path validation and normalization
- Middleware for accessing paths in route handlers
- Base path detection for applications running in subdirectories
- Testing utilities for easier test setup and execution
- Monolog integration for easy logging setup
- PSR-11 container integration
- No dependencies (except Slim 4 and PSR Container)
- Fully tested
- Ready for PHP 7.4 and 8.0+
Requirements
- PHP 7.4+ or 8.0+
- Slim 4
Installation
composer require responsive-sk/slim4-root
Usage
Basic Usage
use Slim4\Root\Paths; // Create a new Paths instance with auto-discovery enabled $paths = new Paths(__DIR__); // Get paths relative to the root $configPath = $paths->getConfigPath(); $viewsPath = $paths->getViewsPath(); $logsPath = $paths->getLogsPath(); // Get all paths at once $allPaths = $paths->getPaths(); // No more ../../../ paths! // Instead of: // require_once __DIR__ . '/../../../vendor/autoload.php'; // Use: // require_once $paths->path('vendor/autoload.php');
With Custom Paths
use Slim4\Root\Paths; // Create a new Paths instance with custom paths $paths = new Paths( __DIR__, [ 'config' => __DIR__ . '/app/config', 'views' => __DIR__ . '/app/views', 'logs' => __DIR__ . '/app/logs', ], true, // Enable auto-discovery (default: true) false // Disable path validation (default: false) ); // Get paths $configPath = $paths->getConfigPath(); // Returns __DIR__ . '/app/config' $viewsPath = $paths->getViewsPath(); // Returns __DIR__ . '/app/views' $logsPath = $paths->getLogsPath(); // Returns __DIR__ . '/app/logs'
Path Auto-Discovery
use Slim4\Root\PathsDiscoverer; // Create a new PathsDiscoverer instance $discoverer = new PathsDiscoverer(); // Discover paths $discoveredPaths = $discoverer->discover(__DIR__); // Use discovered paths var_dump($discoveredPaths);
Path Validation
use Slim4\Root\PathsValidator; use Slim4\Root\Exception\InvalidPathException; // Create a new PathsValidator instance $validator = new PathsValidator(); // Validate paths try { $validator->validate([ 'config' => __DIR__ . '/config', 'views' => __DIR__ . '/views', ], true); // Strict validation (throws exception if path doesn't exist) } catch (InvalidPathException $e) { echo $e->getMessage(); }
Path Normalization
use Slim4\Root\PathsNormalizer; // Create a new PathsNormalizer instance $normalizer = new PathsNormalizer(); // Normalize paths $normalizedPath = $normalizer->normalize('C:\\path\\to\\project\\'); // Returns: 'C:/path/to/project'
With DI Container
use Slim4\Root\PathsProvider; use DI\ContainerBuilder; // Create container $containerBuilder = new ContainerBuilder(); // Register paths services $rootPath = dirname(__DIR__); PathsProvider::register( $containerBuilder->build(), $rootPath, [], // Custom paths true, // Enable auto-discovery false // Disable path validation ); // Get paths from the container $paths = $container->get(Slim4\Root\PathsInterface::class);
With Paths Middleware
use Slim4\Root\PathsMiddleware; use Slim\Factory\AppFactory; // Create app $app = AppFactory::createFromContainer($container); // Add the middleware to the app $app->add($container->get(PathsMiddleware::class)); // Access paths in a route handler $app->get('/', function ($request, $response) { $paths = $request->getAttribute('paths'); $configPath = $paths->getConfigPath(); // ... return $response; });
With Base Path Detection
use Slim4\Root\BasePathMiddleware; use Slim\Factory\AppFactory; // Create app $app = AppFactory::createFromContainer($container); // Add the middleware to detect and set the base path $app->add(new BasePathMiddleware($app)); // Now all routes will work correctly even if your app is in a subdirectory $app->get('/', HomeAction::class);
Integration with Twig
use Slim\Views\Twig; use Slim4\Root\PathsInterface; // Register Twig with the container $container->set(Twig::class, function (ContainerInterface $container) { $paths = $container->get(PathsInterface::class); $twig = Twig::create($paths->getViewsPath(), [ 'cache' => $paths->getCachePath() . '/twig', 'debug' => true, 'auto_reload' => true, ]); return $twig; });
Integration with Monolog
use Monolog\Level; use Slim4\Root\Integration\MonologFactory; use Slim4\Root\PathsInterface; use Psr\Log\LoggerInterface; // Register Logger with the container $container->set(LoggerInterface::class, function (ContainerInterface $container) { $paths = $container->get(PathsInterface::class); $factory = new MonologFactory($paths); return $factory->createLogger([ 'name' => 'app', 'path' => 'app.log', 'level' => Level::Debug, 'rotating' => true, 'max_files' => 7, ]); });
Or use the MonologProvider
for easier setup:
use Monolog\Level; use Slim4\Root\Integration\MonologProvider; // Register a single logger MonologProvider::register($container, [ 'name' => 'app', 'path' => 'app.log', 'level' => Level::Debug, ]); // Register multiple loggers MonologProvider::registerMultiple($container, [ 'default' => [ 'path' => 'app.log', 'level' => Level::Debug, ], 'error' => [ 'path' => 'error.log', 'level' => Level::Error, ], ]); // Use loggers $logger = $container->get(LoggerInterface::class); // Default logger $errorLogger = $container->get('logger.error');
For more details, see Monolog Integration.
Testing Utilities
The package includes a Testing
module that provides utilities for easier test setup and execution.
TestContainer
TestContainer
is a simple static container for sharing objects between tests without using globals.
use Slim4\Root\Testing\TestContainer; use Slim4\Root\Paths; // In your bootstrap file $paths = new Paths(__DIR__); TestContainer::set(Paths::class, $paths); // In your tests $paths = TestContainer::get(Paths::class);
Bootstrap File
The package includes a bootstrap file that you can use in your PHPUnit configuration:
// tests/bootstrap.php require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../vendor/responsive-sk/slim4-root/src/Testing/bootstrap.php';
Or you can create your own bootstrap file:
// tests/bootstrap.php require_once __DIR__ . '/../vendor/autoload.php'; use Slim4\Root\Paths; use Slim4\Root\Testing\TestContainer; // Create Paths object $rootPath = (string)realpath(__DIR__ . '/..'); $paths = new Paths($rootPath); // Store in TestContainer TestContainer::set(Paths::class, $paths);
Available Methods
PathsInterface
getRootPath()
- Get the root path of the projectgetConfigPath()
- Get the config pathgetResourcesPath()
- Get the resources pathgetViewsPath()
- Get the views pathgetAssetsPath()
- Get the assets pathgetCachePath()
- Get the cache pathgetLogsPath()
- Get the logs pathgetPublicPath()
- Get the public pathgetDatabasePath()
- Get the database pathgetMigrationsPath()
- Get the migrations pathgetStoragePath()
- Get the storage pathgetTestsPath()
- Get the tests pathpath(string $path)
- Get a path relative to the root pathgetPaths()
- Get all paths as an associative array
BasePathMiddleware
process(ServerRequestInterface $request, RequestHandlerInterface $handler)
- Process the request and set the base path for the Slim application
PathsDiscoverer
discover(string $rootPath)
- Discover paths in the given root path
PathsValidator
validate(array $paths, bool $strict)
- Validate paths
PathsNormalizer
normalize(string $path)
- Normalize path
TestContainer
set(string $key, mixed $value)
- Set an item in the containerget(string $key, mixed $default = null)
- Get an item from the containerhas(string $key)
- Check if an item exists in the containerremove(string $key)
- Remove an item from the containerclear()
- Clear all items from the container
MonologFactory
createLogger(array $config = [])
- Create a new Monolog logger instancecreateConsoleLogger(string $name, Level $level)
- Create a console loggercreateFileLogger(string $name, string $path, Level $level)
- Create a file loggercreateRotatingLogger(string $name, string $path, int $maxFiles, Level $level)
- Create a rotating file logger
MonologProvider
register(ContainerInterface $container, array $config)
- Register a logger in the containerregisterMultiple(ContainerInterface $container, array $loggers)
- Register multiple loggersregisterConsoleLogger(ContainerInterface $container, string $name, Level $level)
- Register a console loggerregisterFileLogger(ContainerInterface $container, string $name, string $path, Level $level)
- Register a file loggerregisterRotatingLogger(ContainerInterface $container, string $name, string $path, int $maxFiles, Level $level)
- Register a rotating file logger
Customizing Paths
You can customize the paths by passing an array of custom paths to the constructor:
$paths = new Paths( __DIR__, [ 'config' => __DIR__ . '/app/config', 'views' => __DIR__ . '/app/views', 'logs' => __DIR__ . '/app/logs', 'cache' => __DIR__ . '/app/cache', 'public' => __DIR__ . '/public', 'database' => __DIR__ . '/app/database', 'migrations' => __DIR__ . '/app/database/migrations', 'storage' => __DIR__ . '/app/storage', 'tests' => __DIR__ . '/tests', ], true, // Enable auto-discovery false // Disable path validation );
Feature Comparison
Feature | v1.1 | v1.2 | v1.3 | v1.4 |
---|---|---|---|---|
Auto-discovery | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
Path validation | ❌ Basic | ✅ Comprehensive | ✅ Comprehensive | ✅ Comprehensive |
Path normalization | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
Error handling | ❌ Generic exceptions | ✅ Dedicated exceptions | ✅ Dedicated exceptions | ✅ Dedicated exceptions |
Relative paths | ❌ Manual ../ |
✅ Everything relative to root | ✅ Everything relative to root | ✅ Everything relative to root |
Base path detection | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
Testing utilities | ❌ No | ❌ No | ✅ Basic | ✅ Enhanced |
Monolog integration | ❌ No | ❌ No | ❌ No | ✅ Yes |
Test coverage | ✅ Good | ✅ Excellent | ✅ Excellent | ✅ Excellent |
Flexibility | ✅ Good | ✅ Excellent | ✅ Excellent | ✅ Excellent |
Auto-Discovery
The Paths
class can automatically discover common directory structures in your project. This is enabled by default, but you can disable it by passing false
as the third parameter to the constructor.
// With auto-discovery (default) $paths = new Paths(__DIR__); // Without auto-discovery $paths = new Paths(__DIR__, [], false);
The auto-discovery process looks for the following directories:
config
- Looks forconfig
,app/config
,etc
resources
- Looks forresources
,app/resources
,res
views
- Looks forresources/views
,templates
,views
,app/views
assets
- Looks forresources/assets
,assets
,public/assets
cache
- Looks forvar/cache
,cache
,tmp/cache
,storage/cache
logs
- Looks forvar/logs
,logs
,log
,storage/logs
public
- Looks forpublic
,web
,www
,htdocs
database
- Looks fordatabase
,db
,storage/database
migrations
- Looks fordatabase/migrations
,migrations
,db/migrations
storage
- Looks forstorage
,var
,data
tests
- Looks fortests
,test
Path Validation
The Paths
class can validate that all paths exist. This is disabled by default, but you can enable it by passing true
as the fourth parameter to the constructor.
// Without validation (default) $paths = new Paths(__DIR__); // With validation $paths = new Paths(__DIR__, [], true, true);
If validation is enabled and a path doesn't exist, an InvalidPathException
will be thrown:
try { $paths = new Paths(__DIR__, [], true, true); } catch (\Slim4\Path\Exception\InvalidPathException $e) { echo $e->getMessage(); // "Configured path for 'views' is not a valid directory: /path/to/views" }
Testing
composer test
Documentation
For detailed documentation, see:
Examples
- Simple Example - Basic usage in a typical Slim 4 project
- Template Engines Integration - Integration with popular PHP template engines
- Advanced Use Cases - Detailed use cases for various scenarios and architectures
- Testing - Testing utilities and integration with PHPUnit
- Monolog Integration - Integration with Monolog for logging
License
The MIT License (MIT). Please see License File for more information.
Credits
About Responsive.sk
Responsive.sk is a web development company specializing in creating modern, responsive web applications using the latest technologies and best practices.
Roadmap
We're planning to expand this package with integrations for other frameworks and libraries. Check out our TODO list for upcoming features and ways to contribute.
Community
We're looking to collaborate with Laminas and Cycle ORM communities to create integrations for these frameworks. If you're interested in contributing, please check out our TODO list and get in touch!