Allow parsing and writing PHP constants and enums in YAML files

Created on 8 March 2018, over 6 years ago
Updated 11 July 2024, 1 day ago

Problem/Motivation

Since Symfony 3.2, PHP constants are allowed in YAML files, but the YAML parser needs to be executed with an explicit flag to make that happen. We are not using this flag in core.

With that set, we could use this notation inside YAML files:

foo: !php/const Drupal\my_module\Plugin\Foo\Bar\MyPlugin::MY_CLASS_CONSTANT_NAME
bar: !php/const BAR_GLOBAL_CONSTANT_NAME

More information:
https://symfony.com/blog/new-in-symfony-3-2-php-constants-in-yaml-files

Proposed resolution

Call the Symfony YAML parser with the Yaml::PARSE_CONSTANT flag.

Remaining tasks

Resolve constant resolution during module install and

User interface changes

API changes

Data model changes

📌 Task
Status

Needs work

Version

11.0 ðŸ”Ĩ

Component
Configuration  →

Last updated about 17 hours ago

Created by

🇊ðŸ‡ļSpain marcoscano Barcelona, Spain

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.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    This is lovely. It'd be great to add supported to enums at the same time.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    We can extend \Drupal\Tests\Component\Serialization\YamlTest::testYamlFiles() to add explicit test coverage.

  • Status changed to Needs review 8 months ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Rebased the MR on to 11.x

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    This will allow us to use enums as container arguments which would have been lovely for https://www.drupal.org/project/drupal/issues/3402168 🐛 Follow-up for #3361534: config validation errors can still occur for contrib modules, disrupting contrib Active

    I'm going to change this to a task because I don't really think this is a feature request. We need this change in order to support modern PHP in the container and config system.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍
  • Status changed to Needs work 8 months ago
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    This is looking great already! ðŸĪĐ

  • Status changed to Needs review 8 months ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Our test coverage of this change is not create until #3402448: Add PECL Yaml to PHP 8.1 and PHP 8.2 → is fixed.

  • ðŸ‡Ŧ🇷France andypost

    Created https://git.drupalcode.org/issue/drupal-3386474/-/pipelines/51765 to test new dev images with YAML extension enabled

  • ðŸ‡Ŧ🇷France andypost

    Core should be fixed before we can roll new image

    Status    Group      Filename          Line Function                            
    --------------------------------------------------------------------------------
    [33mException Other      phpunit-208.xml      0 Drupal\Tests\Core\DependencyInjecti
    [0m    PHPUnit Test failed to complete; Error: PHPUnit 9.6.13 by Sebastian
        Bergmann and contributors.
        
        Testing Drupal\Tests\Core\DependencyInjection\YamlFileLoaderTest
        .............E                                                    14 / 14
        (100%)
        
        Time: 00:00.074, Memory: 6.00 MB
        
        There was 1 error:
        
        1) Drupal\Tests\Core\DependencyInjection\YamlFileLoaderTest::testExceptions
        with data set "YAML must be valid" ('   do not:\n      do this for...o
        Bar!', 'The file "vfs://drupal/module...d YAML')
        Array to string conversion
        
        /builds/issue/drupal-3386474/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php:454
        /builds/issue/drupal-3386474/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php:423
        /builds/issue/drupal-3386474/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php:71
        /builds/issue/drupal-3386474/core/tests/Drupal/Tests/Core/DependencyInjection/YamlFileLoaderTest.php:78
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:651
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/TextUI/Command.php:144
        /builds/issue/drupal-3386474/vendor/phpunit/phpunit/src/TextUI/Command.php:97
        
        ERRORS!
        Tests: 14, Assertions: 32, Errors: 1.
    [31mFail      run-tests. Unknown              0 Unknown                            
    [0m    FATAL Drupal\Tests\Core\DependencyInjection\YamlFileLoaderTest: test runner
        returned a non-zero error code (2).
    
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    opened 🐛 YamlFileLoaderTest fails if you have PECL yaml installed Needs review to fix the HEAD fail with PECL yaml.

  • First commit to issue fork.
  • 🇚ðŸ‡ļUnited States smustgrave

    Just rebased since the issue in #38 was merged.

  • Status changed to Needs work 8 months ago
  • 🇚ðŸ‡ļUnited States smustgrave

    See it was tagged for a CR, so only moving to NW for that.

  • Pipeline finished with Success
    8 months ago
    Total: 842s
    #52912
  • 🇚ðŸ‡ļUnited States neclimdul Houston, TX

    Looking through the code, I expect this is probably only reading constants not writing them unless there's some magic I missed so the title/scope should probably be updated to avoid confusion.

    That makes this only useful to developers writing config directly like comments that also cannot be sync'd but that was probably always the only reasonable scope. I think that's still worthwhile for maintainability of core and contrib so +1

    Our CR should probably be clear about the side effects/impact of this though. If you write a constant in your install config, then at some point in the future change that constant's value (like bumping password strengths or something) it won't change installed config on sites. You'll need to provide a method for user's to get that updated value. This is also going to be hard to test for because your test site in CI will get the new value during install masking the impact.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @neclimdul Symfony yaml can write enums - which come out as constants... see the test added here. Also core only ever writes with Symfony yaml... you are not supposed to write with PECL yaml. It is not supported since forever.

  • 🇚ðŸ‡ļUnited States neclimdul Houston, TX

    I'm aware symphony does all the writing in drupal. We don't touch the encoder though and the test asserts the parsed yaml matches the value of the constant, not that the constant format is in the encoded yaml.

    I'll do some manual testing in a few days to make sure I'm not missing something though.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @neclimdul there is no value for
    $this->assertSame($symfony['foo'], EnumValue::Yes); - it's not backed... this is an instance of test. It's the same for the backed enum - it would have to be BackedEnumValue::Maybe->value and not BackedEnumValue::Maybe for a comparison of values. Which would fail....

  • Status changed to Needs review 8 months ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Added the CR...

    In doing that I've realised that we have a problem... due to config import. We read configuration prior to doing the import. At this point, if a module that is providing a enum that's in config is being installed everything will crash. Because the enum won't exist and be autoloadable yet. Fixing that would take a complicated changes to the ConfigImporter. I think in the long run we're going to have to make because if we want to validate config we're going to need to be able to load the custom constraints... but it is all very very awkward.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Re #46 even if we fix the config importer we'd still have the config comparing via the UI and Drush... so maybe we just have to say no to enums in config - until we stop futzing with the autoloader and just let composer do it all - which would be a massive massive change.

  • Status changed to Needs work 8 months ago
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    This looks very close, but I think we need a bit more test coverage to ship this with full confidence.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @Wim Leers
    1. That doesn't really matter... we're not storing the backed value FWIW.
    2. That's just a regular const - no different from another class constant. It's not a special case.
    3. When Symfony writes out an enum it uses php/const not php/enum - enums in PHP are really constants under the hood.

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚
    1. I know it doesn't really matter. Personally, for something as low-level and fundamental as this, I find it reassuring to see the whole spectrum explicitly tested. Especially when writing tests for it isn't painful. Anyway, no big deal, just a matter of preference 👍
    2. [See point 3 first.] That may be, but I'd be interested to see how it gets serialized into YAML: as !php/enum SomeEnum::ValueUsingAConstant or as !php/const TheConstant.
    3. Huh — then why does https://symfony.com/blog/new-in-symfony-6-2-improved-enum-support#enums-... literally show !php/enum SomeEnum::Foo? ðŸ˜ģ

      Did some investigating, and you're right! ðŸĪŠ

      Quoting @nicolas-grekas from https://github.com/symfony/symfony/pull/46771:

      This PR allows one to use the following Yaml together with enums:

      - !php/enum SomeEnum::Bar
      - !php/enum SomeEnum::Bar->value
      

      The first line gets the SomeEnum::Bar instance. It's the same as writing !php/const SomeEnum::Bar but with an additional check that this is really an enum.

      IOW:

      1. Symfony will only encode enums using ONLY !php/const
      2. Symfony will only decode enums using BOTH !php/const and !php/enum, and in the latter case it will run extra checks.

      I think it'd be very confusing to use Symfony's advanced YAML capabilities but then do it in a way that only a subset of what their public docs/announcements show. Even if in Drupal's configuration we cannot encode !php/enum, I think it would be valuable to support decoding it: to match what Symfony supports/its docs claim, as well as for the extra assurances.

      More importantly: otherwise the PECL and Symfony YAML decoders would behave differently on the same YAML. Because Yaml::PARSE_CONSTANT triggers support for both !php/const and !php/enum.

      Just pushed suggested additional test coverage: once that passes my concerns would be addressed. If you disagree Drupal should support Symfony's !php/enum even when using PECL, then â€Ķ that's fine, I defer to you, but then at least I've expressed my concern and demonstrated it to the best of my ability 😄

  • Pipeline finished with Failed
    8 months ago
    Total: 618s
    #56116
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Sure lets support Symfony's !php/enum this will be useful for services. As per #46 I'm not convinced that we can use this in Config :(

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    Right 😞 We'd need the config subsystem to special-case core.extension more than it already does. Not simple, but doable, and probably worthwhile. Once this issue is done we'll have at least one use case already — and I suspect more once we factor in Recipes' needs 😄ðŸĪ“

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @Wim Leers - I think we'll need a special autoloader for comparing configuration. It's going to be horrible! The better solution would be to completely remove Drupal's approach to autoloading. But this is a significant challenge.

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    📌 PECL YAML library supports YAML 1.1, but Symfony supports YAML 1.2 Active is RTBC and looks like it'll land soon! ðŸĪž

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    Wow, what a coincidence: minutes after posting that, 📌 PECL YAML library supports YAML 1.1, but Symfony supports YAML 1.2 Active actually landed! 😄

  • Status changed to Needs review 4 months ago
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    Merged in 3 months of upstream changes, most notably: resolved the conflict with 📌 PECL YAML library supports YAML 1.1, but Symfony supports YAML 1.2 Active (hopefully correctly).

    @alexpott's work on this MR was already demonstrating very clearly how much this helps improve the config validation DX. So tagging and bumping priority.

  • Pipeline finished with Failed
    4 months ago
    Total: 161s
    #113995
  • Pipeline finished with Failed
    4 months ago
    #113996
  • Status changed to Needs work 4 months 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.

  • Assigned to Wim Leers
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚
  • Pipeline finished with Failed
    4 months ago
    Total: 574s
    #114427
  • First commit to issue fork.
  • Issue was unassigned.
  • Status changed to Needs review 4 months ago
  • ðŸ‡ĻðŸ‡ĶCanada gapple

    Added some small changes:
    - Cleanup unused data provider
    - Don't require yaml extension for tests
    - Symfony doesn't accept !php/const for the value of a backed enum (BackedEnumValue::Maybe->value)

  • ðŸ‡ĻðŸ‡ĶCanada gapple

    Restoring priority

  • Pipeline finished with Success
    4 months ago
    Total: 758s
    #115273
  • Status changed to RTBC 4 months ago
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    Thanks for fixing my conflict resolution mistakes, @gapple! 😄🙏

  • Status changed to Needs work 4 months ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    I think we need to address #53 before we allow this. I think we need a special autoloader that can only autoload enums from uninstalled modules that we activate in the ConfigImporter. If we don't do this then we're going to give people an easy way to break a site.

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    I missed that 😎 ðŸŦĢ

    #46 provides a good description of the edge case. We should start by adding test coverage for the exact scenario described in #46, and that test will fail with the current MR.

  • 🇚ðŸ‡ļUnited States neclimdul Houston, TX

    Yeah, that seems pretty important since my understanding is that's the only place we'd be supporting this this right? Managing dependencies in the active store or exports sounds like an impossible task that would largely undo the consistency promises of config.

  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    This blocks âœĻ [PP-1] Enum cases stored in config Postponed , which blocks 📌 Add validation constraints to book.settings Needs review .

  • First commit to issue fork.
  • Pipeline finished with Success
    3 months ago
    Total: 990s
    #146669
  • ðŸ‡ŪðŸ‡ģIndia narendraR Jaipur, India

    I tried to add a test in previous commit, which should fail, but it is not failing. ðŸĪ·â€â™‚ïļ

  • Pipeline finished with Success
    3 months ago
    Total: 2883s
    #147033
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @narendraR the test autoloader is pure evil. It needs to be a functional test.

  • Pipeline finished with Success
    3 months ago
    Total: 987s
    #148419
  • Pipeline finished with Failed
    3 months ago
    Total: 1047s
    #148811
  • Pipeline finished with Failed
    3 months ago
    #149401
  • Pipeline finished with Success
    3 months ago
    Total: 991s
    #149574
  • Pipeline finished with Failed
    3 months ago
    Total: 1075s
    #149783
  • Pipeline finished with Failed
    3 months ago
    Total: 1083s
    #149872
  • Pipeline finished with Success
    3 months ago
    Total: 991s
    #150031
  • Pipeline finished with Success
    3 months ago
    Total: 989s
    #150042
  • Pipeline finished with Failed
    3 months ago
    Total: 1042s
    #150483
  • 🇧🇊Belgium Wim Leers Ghent 🇧🇊🇊🇚

    @narendraR Would be great if you could push this forward again 😇

  • Pipeline finished with Failed
    about 1 month ago
    Total: 546s
    #196921
  • Pipeline finished with Failed
    about 1 month ago
    Total: 183s
    #196947
  • Pipeline finished with Failed
    about 1 month ago
    Total: 619s
    #196971
  • Pipeline finished with Failed
    about 1 month ago
    Total: 958s
    #197148
  • ðŸ‡ŪðŸ‡ģIndia narendraR Jaipur, India

    Functional tests are failing in _install_get_version_info($version) for $version=11.0-dev in current MR and passing in 11.x ðŸĪŊ. I think it needs unblocking from @alexpott 🙏

  • First commit to issue fork.
  • Pipeline finished with Failed
    16 days ago
    Total: 633s
    #209624
  • Pipeline finished with Failed
    16 days ago
    Total: 569s
    #209654
  • Pipeline finished with Failed
    16 days ago
    Total: 581s
    #209697
  • Pipeline finished with Success
    16 days ago
    Total: 512s
    #209709
  • Status changed to Needs review 16 days ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    The tests are now fixed... sorry everyone I messed up implementing \Drupal\Core\Config\AutoloadingStorage::listAll() and didn't pass the prefix on to the underlying storage - which messed things up in a really confusing way.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    We have added the missing test coverage that @Wim Leers identifier - i.e. test coverage of config import containing enums for class in modules that are not yet installed.

  • 🇚ðŸ‡ļUnited States mikelutz Michigan, USA

    Lol, took forever to find the listAll() bug, and you fixed it before I could push :-)

  • Status changed to RTBC 16 days ago
  • 🇎🇧United Kingdom longwave UK

    This looks good to me and means our YAML can be more readable instead of copy-pasting constant values around.

  • Status changed to Needs review 16 days ago
  • 🇚ðŸ‡ļUnited States mikelutz Michigan, USA

    https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Co.... Found this too, while I was debugging the tests? Do we need to add the Autoloading storage here?

    in the Storage Comparer:

      if ($source_storage instanceof FileStorage) {
          // FileStorage has its own static cache so that multiple reads of the
          // same raw configuration object are not costly.
          $this->sourceCacheStorage = new NullBackend('storage_comparer');
          $this->sourceStorage = $source_storage;
        }
    
  • 🇚ðŸ‡ļUnited States neclimdul Houston, TX

    Not the decision maker but just going to re-iterate, I think this is a bad idea. The hacks I see for uninstalled modules seem like just the tip of the iceberg. What about removed modules. removed libraries. uninstalled themed. profiles.... it seems like a loosing battle for little benefit.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @mikelutz we already get an autoloading storage here because it's getting the storage from config.storage.sync and we have test coverage in \Drupal\Tests\config\Functional\ConfigImportUITest::testEnumViaConfigImporter()

  • 🇚ðŸ‡ļUnited States mikelutz Michigan, USA

    Right, that was my question. We now get an AutoloadingStorage here. Previously we got a FileStorage, and since FileStorage has it's own static cache, we were not setting a storage cache. Now we have an AutoloadingStorage, which is based on FileStorage and uses it behind the scenes, but we are setting the storage cache as well, since it's not a FileStorage. I was wondering if we needed to. As I was stepping through HEAD and the patch trying to find the difference that caused the bug in listAll it was one of the differences I noticed, as the importer was operating on a CachedStorage object in the patch versus a FileStorage object on HEAD. It wasn't the problem, but I'm wondering if we need the sourceCacheStorage with the Autoloading storage, or if we should carve out the same exception for AutoloadingStorage here that we have carved out for FileStorage.

  • Status changed to RTBC 15 days ago
  • 🇚ðŸ‡ļUnited States mikelutz Michigan, USA

    I don't want to block this, I just wanted to point it out and ask.

  • Status changed to Needs work 15 days ago
  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    @mikelutz oh I see what you are on about. We are undoing the work in 📌 StorageComparer should not wrap sourceStorage with a memory cache if it extends \Drupal\Core\Config\FileStorage Fixed - that's not good. Hmmm....

  • 🇚ðŸ‡ļUnited States phenaproxima Massachusetts

    Just now getting into this issue, but I'm pretty spooked by @neclimdul's comment in #78. This has serious implications because, with this change as proposed, config schema can indirectly influence the dependency tree. That's a real danger zone.

    If we're going to do that, then I think we need to have some kind of coherent fallback mechanism if a constant or enum cannot be loaded or parsed. It must fail gracefully.

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    It must fail gracefully.

    I beg to differ. It must fail loudly and obviously. If you have removed code that your configuration expects this is a big error that Drupal soldier's on from time and time again causing more harm.

  • 🇚ðŸ‡ļUnited States phenaproxima Massachusetts

    Then it has to be extremely strict, and exceptionally clear about what the rules are.

    So, like @neclimdul, stuff like this is giving me pause:

        // Ensure enums and constants from uninstalled modules can be autoloaded.
        $storage = new AutoloadingStorage($storage, [$name => \Drupal::root() . '/' . $this->extensionPathResolver->getPath($type, $name)]);
    

    What is the justification for allowing this kind of thing?

  • 🇎🇧United Kingdom alexpott 🇊🇚🌍

    Because we need to be able to load configuration during config import before a module has been installed. NOTE: the AutolaodingStorage only applies to sync storage and not to active storage - so in my mind the risk is actually minimal and correct because it is exactly the time you expect code to change. FWIW there is no difference here if you try to import a config entity for a module and the entity class is not present. Once you run the import things are not going to work as you expect or want. It would be great if #2628144: Ensure that ConfigImport is taking place against the same code base → and 🐛 Ensure that ConfigImport is taking place without outstanding updates Needs work had landed back when they were green years ago but people got far too used to being able to import incorrect versions config and began to see it as a feature rather than the bug that it is.

  • Pipeline finished with Success
    1 day ago
    Total: 607s
    #221908
  • 🇚ðŸ‡ļUnited States mikelutz Michigan, USA

    Not the decision maker but just going to re-iterate, I think this is a bad idea. The hacks I see for uninstalled modules seem like just the tip of the iceberg. What about removed modules. removed libraries. uninstalled themed. profiles.... it seems like a loosing battle for little benefit.

    I'm not fully seeing the issue here. What do you mean about removed modules, removed libraries, etc? If you uninstall/remove code providing a constant/enum used in your active configurations, you will have problems, as you should. The 'hack' here only adds a module to the classloader as it's config is read in preparation to install it. This seems appropriate to me, as the config you are installing should be able to use enums defined in the module you are installing. This isn't about loading enums and consts from uninstalled modules, this is to load enums and consts from a module that we are in the process of installing. I really think the inline comments are scarier than what the code is doing, and I made a couple suggestions on the comments. I also added a test that I think shows another issue, config referencing constants that are in the modules .module file. I think we will have to include the .module file before parsing the config as well.

    Also, I tried to find a more elegant way to prevent double caching of the autoloading storage, but I really couldn't, so I just added a second instanceof check in the Comparer. I don't like it.

  • Pipeline finished with Failed
    1 day ago
    Total: 542s
    #221940
Production build 0.69.0 2024