Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Project Root Laravel Package

konsulting/project-root

Resolve the correct root path when developing a Composer package or using it as a dependency. Project Root lets you target a package name and resolve paths relative to the host project, avoiding repeated “dirty” path-detection logic.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the Package:

    composer require konsulting/project-root
    
  2. Basic Usage: Replace fragile path logic (e.g., dirname(__DIR__, 4)) with:

    use Konsulting\ProjectRoot;
    
    $projectRoot = ProjectRoot::forPackage('your-package-name')->resolve(__DIR__);
    
  3. Where to Look First:

    • README.md: For installation and basic usage.
    • Tests: In /tests/ for edge cases (e.g., symlinked dependencies).
    • Source Code: /src/ProjectRoot.php (50 lines) to understand the resolution logic.
  4. First Practical Use Case: A Laravel package that needs to write logs to the project’s storage/logs/ directory (not its own):

    $logPath = ProjectRoot::forPackage('your-package')->resolve(__DIR__ . '/../../../storage/logs');
    

Implementation Patterns

Common Workflows

1. Dependency-Aware Path Resolution

Replace hardcoded paths in packages:

// Before (fragile)
$configPath = __DIR__ . '/../../../../config/package.php';

// After (robust)
$configPath = ProjectRoot::forPackage('your-package')->resolve(__DIR__ . '/config/package.php');

2. CLI Commands

Resolve project roots in Artisan commands or standalone scripts:

use Konsulting\ProjectRoot;

$projectRoot = ProjectRoot::forPackage('your-cli-package')->resolve(__DIR__);
$this->info("Project root: " . $projectRoot);

3. Service Providers

Dynamically load project-specific configs:

public function boot()
{
    $projectRoot = ProjectRoot::forPackage('your-service-provider')->resolve(__DIR__);
    $this->app->singleton('projectConfig', function () use ($projectRoot) {
        return require $projectRoot . '/config/project.php';
    });
}

4. Asset Compilation

Locate project assets (e.g., public/ or resources/) from a package:

$publicPath = ProjectRoot::forPackage('your-asset-package')->resolve(__DIR__ . '/../../../public');

Integration Tips

Laravel-Specific Patterns

  • Avoid Mixing with Laravel Helpers: Use ProjectRoot for package-relative paths (e.g., vendor/package/storage). Reserve Laravel’s storage_path(), public_path() for project-wide paths.

  • Package Bootstrapping: Resolve paths in register() (not boot()) to ensure they’re available early:

    public function register()
    {
        $packageRoot = ProjectRoot::forPackage('your-package')->resolve(__DIR__);
        $this->mergeConfigFrom($packageRoot . '/config/package.php', 'package');
    }
    

Performance Optimization

  • Cache Results: If resolving paths repeatedly (e.g., in a loop), cache the result:
    static private $resolvedPaths = [];
    $projectRoot = self::$resolvedPaths[$packageName] ?? ProjectRoot::forPackage($packageName)->resolve(__DIR__);
    self::$resolvedPaths[$packageName] = $projectRoot;
    

Testing

  • Unit Test Path Resolution: Mock __DIR__ to simulate different contexts:

    $mockDir = __DIR__ . '/../../../vendor/your-package';
    $resolver = ProjectRoot::forPackage('your-package')->resolve($mockDir);
    $this->assertEquals('/path/to/project', $resolver);
    
  • Integration Test in CI: Test with --prefer-source and --prefer-dist to cover symlinked vs. vendored scenarios:

    composer install --prefer-dist && vendor/bin/phpunit
    composer install --prefer-source && vendor/bin/phpunit
    

Gotchas and Tips

Pitfalls

1. Package Name Mismatch

  • Issue: forPackage('wrong-name') returns incorrect paths.
  • Fix: Validate the package name against composer.json:
    $composerJson = json_decode(file_get_contents(__DIR__ . '/../../composer.json'), true);
    if ($composerJson['name'] !== 'your-package') {
        throw new \RuntimeException("Package name mismatch!");
    }
    

2. Symlinked Dependencies

  • Issue: realpath() may resolve symlinks incorrectly on Windows/Linux.
  • Fix: Normalize paths early:
    $path = str_replace('\\', '/', $path);
    $path = preg_replace('/\/+/', '/', $path);
    

3. Composer Autoloader Changes

  • Issue: Post-PHP 8.2, Composer’s autoloader may behave differently.
  • Fix: Test with:
    composer dump-autoload --optimize
    

4. Windows Path Separators

  • Issue: DIRECTORY_SEPARATOR may not work as expected in cross-platform packages.
  • Fix: Use forward slashes consistently:
    $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
    

5. Nested Dependencies

  • Issue: Deeply nested dependencies (e.g., vendor/a/b/c/your-package) may break resolution.
  • Fix: Use composer show -v your-package to debug the actual install path.

Debugging Tips

1. Verify Package Root

Check the resolved path matches expectations:

$resolved = ProjectRoot::forPackage('your-package')->resolve(__DIR__);
file_put_contents('/tmp/debug-path.txt', $resolved);

2. Composer Autoloader Inspection

Inspect how Composer resolves the package:

composer show -v your-package

Look for Installed Path to confirm the expected directory.

3. Fallback Logic

Add a fallback for critical paths:

try {
    $path = ProjectRoot::forPackage('your-package')->resolve(__DIR__ . '/config');
} catch (\Exception $e) {
    $path = __DIR__ . '/config'; // Fallback to package root
    \Log::warning("Project root resolution failed: " . $e->getMessage());
}

Configuration Quirks

1. No Configuration Required

The package is zero-config. No .env or service provider setup is needed.

2. Package Name Must Match composer.json

The argument to forPackage() must match the name field in composer.json:

{
    "name": "vendor/your-package",
    "require": { ... }
}

Use the full namespace (e.g., vendor/your-package, not just your-package).

3. Edge Cases in resolve()

  • Relative Paths: Pass __DIR__ or an absolute path to resolve().
    // Correct (relative to package root)
    $path = ProjectRoot::forPackage('your-package')->resolve(__DIR__ . '/config');
    
    // Incorrect (absolute path may bypass resolution)
    $path = ProjectRoot::forPackage('your-package')->resolve('/absolute/path');
    

Extension Points

1. Custom Resolution Logic

Extend the package by subclassing ProjectRoot:

class CustomProjectRoot extends \Konsulting\ProjectRoot
{
    public function resolve($path, $fallback = null)
    {
        $resolved = parent::resolve($path);
        return $resolved ?: $fallback;
    }
}

2. Add Caching Layer

Cache resolved paths globally:

class CachedProjectRoot extends \Konsulting\ProjectRoot
{
    static private $cache = [];

    public function resolve($path)
    {
        $package = $this->packageName;
        return self::$cache[$package] ?? (self::$cache[$package] = parent::resolve($path));
    }
}

3. Support for Multi-Root Projects

Extend to handle monorepos or custom Composer repos:

class MultiRootProjectRoot extends \Konsulting\ProjectRoot
{
    public function resolve($path, $root = null)
    {
        if ($root) {
            return $root . '/' . ltrim($path, '/');
        }
        return parent::resolve($path);
    }
}

Laravel-Specific Gotchas

1. Avoid Overriding Laravel’s Path Helpers

  • Bad: Use ProjectRoot for project-wide paths (e.g., storage_path()).
  • Good: Use ProjectRoot for package-specific paths (e.g., vendor/package/storage).

2. **

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity