Problem/Motivation
When Drupal in installed in a non-standard installation structure, DRUPAL_ROOT is defined incorrectly and various parts of Drupal don't work.
This is important because the standard way of developing a Composer package, by defining a path repository in a project to symlink a git clone of the package, isn't currently possible with Drupal core.
Definitions
- project root: the folder where the root composer.json is
- app root (also drupal root): The folder where Drupal's entry-point index.php is. This is where the HTTP request to Drupal is made. Typically PROJECT_ROOT/web.
- scaffolding: The process which copies files into the app root when Drupal is installed with Composer. This is done by a Composer plugin within Drupal.
- Composer symlinking: The technique of using a 'path' repository for a package which points to a local git clone of the package. This causes Composer to symlink the package from the git clone into its location in the project. This is a method used to develop a package which needs to be used in the context of a Composer project -- such as Drupal core.
There are various kinds of installation structures for Drupal, depending on where and how Drupal core is installed:
- drupal/recommended-project: drupal scaffolded into the web/ folder.
- plain git clone of Drupal core, with `composer install` run at its root.
- drupal/legacy-project: drupal scaffolded into the project root.
- drupal core symlinked in by Composer, such as with the joachim-n/drupal-core-development-project Composer template. (This is the best way to develop a Composer package within a project -- see https://medium.com/pvtl/local-composer-package-development-47ac5c0e5bb4.)
- drupal installed in vendor/ - this is not possible because of other issues, and will not be fixed here, but this issue should aim to make DRUPAL_ROOT correct for this structure.
What causes the problem
We use Composer to install Drupal into the app root rather than /vendor, and Drupal
expects to find files like settings.php and extensions in the app root. Because of this, code in a Drupal project falls into several 'zones':
- Composer autoloader
- Composer vendor code
- Drupal core
- Drupal scaffolded files such as index.php and update.php
- Drupal settings, files, and extensions
These different zones don't all know how to get hold of code in the other zones:
- The autoloader always knows how to load Composer vendor code, and Drupal core code, because Composer installed it.
- Composer vendor code, e.g. Drush, knows the location of the Composer autoloader relative to itself, because it knows it's in vendor/foo/bar. But it has to make assumptions for the location of Drupal core or scaffolded files.
- Drupal core doesn't know the location of anything, because where it was installed is defined in composer.json. It has to make assumptions about how to get to the autoloader. It does that with a special autoload.php file at the root of the Drupal repository. (For the drupal/recommended-project template, an autoload.php is scaffolded into web/ so that it can go one level up and find the vendor folder. In drupal/legacy-project Drupal's include() loads the repository's autoload.php, and in drupal/recommended-project the same include() loads the scaffolded autoload.php.)
- Drupal core has to make assumptions about how to find its settings.
- Composer vendor code has to make assumptions about how to find Drupal settings. This affects things like Drush and Drupal Console.
- Drupal's scaffolded files can know about all code locations, since they are written by the scaffold plugin during Composer installation. During this process we can get the location of anything from Composer, and write it into the scaffolded files.
- Drupal settings.php file: knows the location of scaffolded index.php, as that is fixed relative to itself.
These assumptions all break in non-standard installation structures. In particular, the use of __DIR__ in these assumptions breaks when Drupal is symlinked into a project.
Fixing this is difficult because Drupal has MANY different entry points:
- HTTP request to the index.php file which is scaffolded into the app root
- HTTP request to scaffolded install.php
- HTTP request to scaffolded update.php
- HTTP request to core/rebuild.php, which is not scaffolded.
- tests run with phpunit
- tests run with run-tests.sh (this case doesn't really count, as run-tests.sh is only meant for Drupal CI, whose installation structure is a known quantity: see #3228531: document run-tests.sh as not intended for public consumption)
- code in a Composer package (e.g. Drush or Console) bookstraps Drupal
- scripts in core/scripts
Proposed resolution
Various approaches have been tried (see old branches in the issue fork). But ultimately, there's only one thing we can rely on: Composer knows the location of everything, because it installs everything.
Asking Composer runtime API every page load is potentially a performance hit, but it's simple to have Composer write a file containing the data we need during Composer installation.
So the plan is as follows:
1. For Composer vendor code which needs to find Drupal, have core's drupal/core-composer-scaffold Composer plugin write the locations class file into the plugin's own folder.
The file is written as a class, so that it can always be loaded by Composer. (Making it a plain file registered as a Composer autoload 'file' item causes problems with package dependency hierarchy.) This class currently only defines one constant, but #3208975: split the concept of DRUPAL_ROOT/app root into app root and Drupal web root could expand on that.
For cases where drupal/core-composer-scaffold is not installed, fall back on the existing behaviour for guessing the location of Drupal core.
Deprecate the $app_root parameter to DrupalKernel, since DrupalKernel uses the plugin.
2. For code in Drupal core which needs to get to the autoloader, replace all uses of __DIR__ with code which uses the actual path of the executed file. This is to prevent PHP from resolving symlinks.
Remaining tasks
See comment in the MR
API changes
- new functionality for drupal/core-composer-scaffold
- new DrupalLocations class, which 3rd party code can use to find Drupal's location
- DrupalKernel's $app_root parameter in various methods is deprecated
- DrupalKernel::guessApplicationRoot is deprecated and renamed. This is protected but Drush uses it, for instance.
Original summary
I usually link drupal inside my www directory like seen in the following ls output.
I think symlinking like that is not unusual, as it helps with version controlling of own projects.
core -> ../drupalcheckout/core
.htaccess
../drupalcheckout/index.php
../drupalcheckout/modules
profiles
robots.txt -> ../drupalcheckout/robots.txt
sites
themes -> ../drupalcheckout/themes
i've attached a script which will setup such an environment in the current directory.
With this setup BASE_ROOT in install.php will be set to the drupalcheckout directory.
Install then tries to find a settings.php in ../drupalcheckout/sites/default and not ./sites/default.
same Problem and fix should be considered for update.php and authorize.php
related discussions:
#1055856: Optimize settings.php discovery
#484554: Stop relying on Apache for determining the current path