Weave 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

Drupal Finder Laravel Package

webflo/drupal-finder

A small PHP utility that locates the root of a Drupal installation from any directory, supporting both Drupal 7 and 8+. It detects the Drupal version, core path, and site directory, making it easy for tools like Drush, Composer plugins, and custom scripts to work reliably across project layouts.

View on GitHub
Deep Wiki
Context7

Getting Started

webflo/drupal-finder does one thing: given a starting directory, it walks up the tree until it finds a Drupal root (and the Composer root that contains it). That is the entire surface area — locateRoot() plus three getters. It exists so that tools, scripts and Composer plugins can answer "where is Drupal?" without hardcoding web/, docroot/ or assuming the CWD is the project root.

Install it as a dev dependency:

composer require --dev webflo/drupal-finder

First use case — a script that must work regardless of whether the project uses web/, docroot/ or the project root as docroot:

<?php
require __DIR__ . '/vendor/autoload.php';

use DrupalFinder\DrupalFinder;

$finder = new DrupalFinder();
if (!$finder->locateRoot(getcwd())) {
    fwrite(STDERR, "Not inside a Drupal project\n");
    exit(1);
}

$drupalRoot   = $finder->getDrupalRoot();   // .../web  (or project root)
$composerRoot = $finder->getComposerRoot(); // .../     (where composer.json lives)
$vendorDir    = $finder->getVendorDir();    // .../vendor

echo "Drupal: $drupalRoot\n";

Where to look first: read src/DrupalFinder.php end to end — it is roughly a hundred lines. Understanding the marker files it checks is more valuable than any documentation:

  • Drupal 8+ root: core/lib/Drupal.php
  • Drupal 7 root: includes/bootstrap.inc
  • Composer root: composer.json
  • Vendor dir: vendor/autoload.php (or the vendor-dir config)

If you are writing a package that itself lives in vendor/, look at src/DrupalFinderComposerRuntime.php instead — it derives the starting path from Composer's registered autoloaders so it does not depend on getcwd().

Implementation Patterns

Validate once, then pass the root around. The finder is cheap but not free, and its state is per-instance. Resolve at the entry point of your CLI command and hand the plain string paths to the rest of your code:

$finder = new DrupalFinder();
$finder->locateRoot(getcwd());
$drupalRoot = $finder->getDrupalRoot();

// Fail fast with an actionable message rather than letting nulls leak inward.
if ($drupalRoot === false) {
    throw new RuntimeException(
      'Could not locate a Drupal root from ' . getcwd() . '. Run with --root=/path/to/drupal.'
    );
}

Add an explicit --root escape hatch. Discovery is a convenience, not a contract. Every tool worth its salt lets the user override:

$start = $options['root'] ?? getcwd();
$finder->locateRoot($start);

Inside a vendor-installed tool, use DrupalFinderComposerRuntime. It resolves the vendor directory via Composer\Autoload\ClassLoader::getRegisteredLoaders() and locates upward from there, so it works when the tool is invoked from a subdirectory, a git hook, or a Makefile:

use DrupalFinder\DrupalFinderComposerRuntime;

$finder = new DrupalFinderComposerRuntime();
$drupalRoot = $finder->getDrupalRoot();

Composer script / plugin integration. For a Composer plugin, resolve the root from the event's getComposer()->getConfig()->get('vendor-dir') parent rather than getcwd(), or simply use the runtime finder inside a post-autoload-dump handler. For plain scripts:

"scripts": {
  "drupal:cache-rebuild": "MyTool\\Scripts::rebuild"
}

and inside Scripts::rebuild() call new DrupalFinderComposerRuntime().

Derive paths from the right root. Two roots, two purposes:

Need Use
Site files, core/, sites/default/settings.php, web-relative URLs getDrupalRoot()
composer.json, vendor/, patches, project-level config, phpunit.xml at project root getComposerRoot()

A common bug is joining sites/default onto the Composer root in a docroot-style project, or joining vendor/autoload.php onto the Drupal root in a project where vendor sits one level up.

Wrap it behind your own interface. The library has essentially no extension points, so if you need caching, logging or fallback behaviour, write a five-line adapter that memoises the result. This also insulates you from API churn.

Shell usage. The package ships a bin/drupal-finder executable that prints the discovered Drupal root — handy in shell scripts and CI:

cd web/sites/default && ../../../vendor/bin/drupal-finder

Gotchas and Tips

  • locateRoot() return value and getter types have shifted across versions. Depending on the release, getDrupalRoot() may return an empty string, null, or false when nothing was found. Always compare against false/empty explicitly, pin the constraint (^1.0) and check the changelog before upgrading. Never rely on truthiness of a path string alone.
  • State is not reset between calls. A DrupalFinder instance retains the first result; calling locateRoot() again with a different path on the same instance is unreliable. Create a new instance per lookup.
  • Nested composer.json files break discovery. The walk stops at the first composer.json it encounters going upward, so a sub-package or a stray composer.json inside your docroot can yield the wrong Composer root. If you support monorepos, prefer passing the known project root in explicitly.
  • Partially installed projects. Before composer install completes there is no core/lib/Drupal.php, so you get a Composer root but no Drupal root. Distinguish "no project at all" from "project not yet installed" in your error messages — the fix is different.
  • DrupalFinderComposerRuntime requires the Composer 2 autoloader to be loaded and the package to live in the same vendor directory as the project. It does not work for globally installed (composer global) tools; fall back to getcwd() there.
  • getVendorDir() is not getComposerRoot() . '/vendor'. Respect the returned value — projects legitimately override vendor-dir, and the getter can be false when vendor/autoload.php is absent even though composer.json was found.
  • Drupal 7 detection is marker-based (includes/bootstrap.inc). A stale or vendored copy of that file inside a subdirectory can misidentify the root; pass a precise start path.
  • Symlinks and Windows. Paths are normalised with realpath-style resolution in current releases; on Windows expect backslashes and always run results through realpath()/str_replace('\\', '/', ...) before comparing or emitting them.
  • No generic framework detection. This is Drupal-only by design. If you also support Backdrop or plain Composer projects, add your own marker probing rather than trying to coax this library.
  • Licence: GPL-2.0, not MIT. Keeping it a require-dev dependency in an application is unproblematic, but do not link it into a distributed proprietary binary without reviewing the obligations.
  • You may not need it. Drush, Drupal Check and PHPStan's Drupal extension already do root discovery; use drupal-finder only for your own standalone scripts and Composer plugins, so you are not reimplementing detection badly or duplicating a dependency someone else already installed.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource