Wrong DRUPAL_ROOT with non-standard code structure

Created on 22 September 2012, almost 12 years ago
Updated 3 June 2024, 22 days ago

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 πŸ› document run-tests.sh as not intended for public consumption Fixed )
- code in a Composer package (e.g. Drush or Console) bookstraps Drupal
- scripts in core/scripts
- HTTP request to core/modules/statistics/statistics.php (yay! special case!)

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, add a new Composer plugin which is inside core, whose sole job is to write the locations class file into the plugin's own folder. Make the plugin required by core, so it's always present, even in projects that don't use the scaffold plugin. And nobody else needs to worry about defining it, as to change the location values you set them in composer.json.

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.

This plugin should always be installed with Drupal, whether using the drupal/recommended-project or drupal/legacy-project templates, or running Composer on a git clone of 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.

API changes

- New Composer plugin in Drupal core
- 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 β†’

πŸ› Bug report
Status

Needs work

Version

11.0 πŸ”₯

Component
BaseΒ  β†’

Last updated about 1 hour ago

Created by

πŸ‡©πŸ‡ͺGermany derEremit

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • The Needs Review Queue Bot β†’ tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne
  • last update 10 months ago
    Custom Commands Failed
  • last update 10 months ago
    Composer error. Unable to continue.
  • Status changed to Needs review 10 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    New branch & MR for 11.x

  • last update 10 months ago
    Composer error. Unable to continue.
  • Status changed to Needs work 10 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Have not reviewed.

    But appears to have composer error and can't trigger retest.

  • last update 10 months ago
    Custom Commands Failed
  • last update 10 months ago
    Custom Commands Failed
  • last update 10 months ago
    30,150 pass
  • Status changed to Needs review 10 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Fixed the errors.

  • last update 10 months ago
    30,150 pass
  • Status changed to Needs work 10 months ago
  • πŸ‡³πŸ‡±Netherlands bbrala Netherlands

    I only found one real question in regards of a missed dirname(__DIR__, 2); in one of the files. There is other places we might want to use our new found power instead of some of the references through __DIR__. Most seem in tests though which i'd not touch probably.

    I've run through this whole issue and went through the MR intensely. This seems like an elegant solution that will enable us to take a step forward in how core is structured.

  • last update 10 months ago
    30,152 pass
  • last update 10 months ago
    30,152 pass
  • last update 10 months ago
    30,156 pass
  • last update 10 months ago
    30,156 pass
  • Status changed to Needs review 10 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Resolved all points.

  • Pipeline finished with Failed
    10 months ago
    Total: 1099s
    #20178
  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne

    The latest version of MR looks great. Added suggestions for improvement.
    The only thing that I want to clarify is the upgrade path for the existing projects. I reviewed CR, and there is only mention of the new API, but no upgrade steps. As I understand, existing projects need to update the autoload section of the composer.json file, right?

  • πŸ‡¬πŸ‡§United Kingdom joachim

    > The only thing that I want to clarify is the upgrade path for the existing projects. I reviewed CR, and there is only mention of the new API, but no upgrade steps. As I understand, existing projects need to update the autoload section of the composer.json file, right?

    Oh that's a good point!

    In the MR, any code that uses the \Drupal\Composer\Plugin\Locations\DrupalLocation class also has a fallback for if that class is not found.

    That fallback is for the benefit of existing installations, but also for plain git clones, where there is no drupal scaffolding done and so the DrupalLocation won't get written.

    So actually, the CR needs to be changed, and possibly the docs as well, to say this: you have access to DrupalLocation IF you know you are in a project that uses drupal/core-drupal-locations -- in other words, in code that you own in your project, or in a Composer project template that requires drupal/core-drupal-locations and sets up scaffolding. Generally, in thid-party code, you should use DrupalKernel::getAppRoot().

  • Status changed to Needs work 9 months ago
  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne

    Ok, so PR looks good. I set NW for CR updates.

  • Status changed to Needs review 9 months ago
  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne

    MR needs to rebase

  • Status changed to Needs work 9 months ago
  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne
  • Open on Drupal.org β†’
    Environment: PHP 8.2 & MySQL 8
    last update 9 months ago
    Not currently mergeable.
  • Pipeline finished with Failed
    9 months ago
    Total: 1180s
    #22921
  • last update 9 months ago
    30,210 pass
  • Pipeline finished with Failed
    9 months ago
    Total: 269s
    #23451
  • Status changed to Needs review 9 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Updated the README and the CR.

  • last update 9 months ago
    30,365 pass
  • Pipeline finished with Success
    9 months ago
    Total: 1105s
    #23652
  • Status changed to RTBC 9 months ago
  • πŸ‡ΊπŸ‡¦Ukraine voleger Ukraine, Rivne

    GitlabCI (wow, 4 times faster than DrupalCI) was not happy with CSpell check. I added an entry to a dictionary. The changes look good to me for RTBC

  • Status changed to Needs work 9 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    'definining' is a typo, not a word :)

    I'll fix.

  • Status changed to RTBC 9 months ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Fixed.

  • last update 9 months ago
    30,365 pass
  • Pipeline finished with Failed
    9 months ago
    Total: 1476s
    #23683
  • Status changed to Needs work 9 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I think the BC layer in bootstrap.inc is not correct - it should use Drupal\Locations\DrupalLocation; and not use Drupal\Composer\Plugin\Locations\DrupalLocation; - also the comment in bootstrap.inc is out-of-date.

    Plus I think people need to guidance about whether to deploy DrupalLocation.php or should it be it .gitgnore like the other scaffolded files.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    > I think the BC layer in bootstrap.inc is not correct

    Fixed, and same problem in DrupalKernel too.

    > Plus I think people need to guidance about whether to deploy DrupalLocation.php or should it be it .gitgnore like the other scaffolded files.

    It can be either -- it's documented in the code, but I'll add it to the README:

     * To ensure the project's codebase is portable, the generated class must not
     * use absolute paths. It should instead use the __DIR__ constant, which in the
     * generated class will give the project root.
    

    > should it be it .gitgnore like the other scaffolded files

    We could do that, but code to add files to .gitignore is in the drupal/core-composer-scaffold and it's marked @internal. Is it ok for the locations plugin to call the scaffold plugin given they're both Drupal core?

  • last update 9 months ago
    Custom Commands Failed
  • Pipeline finished with Failed
    9 months ago
    Total: 303s
    #23829
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    We could do that, but code to add files to .gitignore is in the drupal/core-composer-scaffold and it's marked @internal. Is it ok for the locations plugin to call the scaffold plugin given they're both Drupal core?

    Maybe this new composer plugin should be part of scaffold - as it is very similar to the autoload.php we're scaffolding.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    I did look at doing that at one point, but I felt that its function was sufficiently different from Scaffold to count as feature creep - Scaffold just copies files from one place to another according to a recipe. This generates a class.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Hiding all the patches.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    DrupalCon Lille conversation -- things to change:

    1. Merge new Composer plugin into Scaffold - we've agreed that Scaffold's purpose is generally 'Make the Composer project happy with Drupal's weirdnesses'. It already generates the autoload.php file.
    2. Look at https://getcomposer.org/doc/04-schema.md#classmap instead of PSR autoload.
    3. Be clearer in the CR about what existing sites need to or can do -- cover various different setups.

  • Pipeline finished with Failed
    7 months ago
    Total: 324s
    #50258
  • First commit to issue fork.
  • Pipeline finished with Failed
    6 months ago
    Total: 181s
    #71822
  • Pipeline finished with Failed
    6 months ago
    Total: 162s
    #71829
  • Pipeline finished with Success
    6 months ago
    Total: 650s
    #71841
  • πŸ‡ΊπŸ‡ΈUnited States capysara

    I rebased and fixed conflicts in composer.lock and core/lib/Drupal/Core/Test/FunctionalTestSetupTrait.php. In FunctionalTestSetupTrait.php, this line had been removed altogether: chdir(DRUPAL_ROOT), see 5ef2211afc. In this MR, the line was changed to chdir($app_root). I don't have much context for either issue so I kept the change from this issue.

    I updated the error messaging for coding standards, but I didn't address any of the comments in the MR. I was just trying to get this back to green.

    (Also, sorry Joachim, I didn't notice Assigned until after I started committing things.)

  • Issue was unassigned.
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Thanks!
    I probably should have unassigned myself a while ago -- haven't got the mental space to dive back into one at the moment. Thanks for keeping it ticking over!

    It might be worth seeing if we can do without the chdirs() which are removed in 5ef2211afc.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    I'm trying to rebase this, and I'm not managing to update Composer from lock, and I don't remember how I did it previously!

    > COMPOSER_ROOT_VERSION=11.0-dev composer update --lock

    says:

    Your requirements could not be resolved to an installable set of packages.
    
      Problem 1
        - Root composer.json requires drupal/core == 11.9999999.9999999.9999999-dev (exact version match), it is satisfiable by drupal/core[11.x-dev] from composer repo (https://repo.packagist.org) but drupal/core[11.0-dev] from path repo (core) has higher repository priority. The packages from the higher priority repository do not match your constraint and are therefore not installable. That repository is canonical so the lower priority repo's packages are not installable. See https://getcomposer.org/repoprio for details and assistance.
    

    and

    > COMPOSER_ROOT_VERSION=11.9999999.9999999.9999999-dev composer update --lock

    works, but then the version set in composer.lock is all wrong.

  • πŸ‡ΊπŸ‡ΈUnited States Darren Oh Lakeland, Florida

    You have be in the 11.x branch to update the lock file.

  • πŸ‡¬πŸ‡§United Kingdom joachim

    So is the right way to do it:

    1. merge 11.x into feature branch
    2. Update lock file
    3. switch to feature branch, commit changes
    4. switch to 11.x and do a git reset --hard to put it back

    ?

  • πŸ‡¬πŸ‡§United Kingdom joachim

    Aha, that's worked. Thanks!!

  • Pipeline finished with Failed
    4 months ago
    Total: 170s
    #105924
  • πŸ‡¬πŸ‡§United Kingdom joachim

    > 2. Look at https://getcomposer.org/doc/04-schema.md#classmap instead of PSR autoload.

    That's not going to work. I've tried it, and any Composer operation which happens before the DrupalLocation.php is written produces this error, multiple times:

    > Could not scan for classes inside "DrupalLocation.php" which does not appear to be a file nor a folder

    That happens when doing `composer drupal:scaffold` and probably will happen when first installing Drupal with Composer.

    The problem is that 'classmap' autoloading looks in the given list of files to find classes to register. That's unlike 'psr-4' autoloading, which merely declares directories to Composer, and it will only look for files when a class is about to be loaded.

    So with psr-4 it's fine to declare a file that doesn't exist yet, but with classmap it's not.

    @alexpott - your concern was that the namespace could clash with other things. What if make it more specific?

    Instead of:

    > namespace Drupal\Locations;

    we have:

    > namespace Drupal\Composer\Locations;

    The Drupal\Composer namespace is used by things in our /composer folder, nobody else should be using that namespace.

  • Pipeline finished with Failed
    4 months ago
    Total: 171s
    #106093
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Moved all the code to the scaffold plugin.

    I've started trying to get the gitignore to work and can't quite get it working :/

  • Pipeline finished with Failed
    4 months ago
    Total: 130s
    #110614
  • πŸ‡¬πŸ‡§United Kingdom joachim

    joachim β†’ changed the visibility of the branch 1792310-locations-class-writer-plugin-11 to hidden.

  • Status changed to Needs review 27 days ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Figured out gitignore.

    Made a new branch & MR as the old branch name wasn't accurate any more now that the new functionality is being added to the existing drupal/core-composer-scaffold Composer plugin.

    I've hidden the old branch.

    New MR is: https://git.drupalcode.org/project/drupal/-/merge_requests/8216

  • Pipeline finished with Failed
    27 days ago
    Total: 133s
    #185230
  • Pipeline finished with Failed
    25 days ago
    Total: 132s
    #187441
  • Pipeline finished with Failed
    25 days ago
    Total: 133s
    #187566
  • Status changed to Needs work 23 days ago
  • The Needs Review Queue Bot β†’ tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • Status changed to Needs review 23 days ago
  • πŸ‡¬πŸ‡§United Kingdom joachim

    Rebased.

  • Pipeline finished with Failed
    23 days ago
    Total: 135s
    #189000
  • Pipeline finished with Failed
    23 days ago
    Total: 101s
    #189648
  • Pipeline finished with Failed
    22 days ago
    Total: 179s
    #189833
  • Pipeline finished with Failed
    22 days ago
    Total: 169s
    #189835
  • Status changed to Needs work 22 days ago
  • The Needs Review Queue Bot β†’ tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • Pipeline finished with Failed
    22 days ago
    Total: 190s
    #189879
  • Pipeline finished with Failed
    22 days ago
    Total: 512s
    #189913
  • Pipeline finished with Failed
    22 days ago
    Total: 197s
    #189979
Production build 0.69.0 2024