Problem/Motivation
When installing Drupal 8 on a system that does not have mbstring support, a fatal error is thrown after the database has been created, as the cache module uses mb_check_encoding in the normalizeCid function, without checking for existence of that function.
Fatal error: Call to undefined function Drupal\Core\Cache\mb_check_encoding() in core/lib/Drupal/Core/Cache/DatabaseBackend.php on line 410
As mbstring is not a requirement (the installer system check gives a warning if mbstring is not present, not an error), the module should either check to see if it's present, or not use it at all.
Steps to reproduce:
- make sure mbstring is disabled
- follow install.php
- after entering the database details, the error is thrown
Proposed resolution
Add a method in DatabaseBackend to check for the existence of mbstring, and use a regular expression match to determine if the CID is ASCII instead. This allows for the non-mbstring functionality to be tested on testing infrastructure with mbstring installed. The DatabaseBackend unit tests should be expanded to cover this case.
Remaining tasks
- Write patch with tests in DatabaseBackendUnitTest
- Review patch
Beta Summary(done)
User interface changes
None
API changes
An internal method is added to the cache DatabaseBackend.
Data model changes
None.
Beta phase evaluation
| Issue category | Bug because an error is thrown during installation. |
|---|---|
| Issue priority | Major because fatal error and some people don't have mbstring | Unfrozen changes | Unfrozen because it only changes CSS/markup/strings/documentation/tests. (Which? Specify.) |
| Comment | File | Size | Author |
|---|---|---|---|
| #53 | cache_databasebackend-2560123-53.patch | 986 bytes | sboden |
| #31 | mbstring.php_.txt | 1.08 KB | berdir |
| #21 | cache_databasebackend-2560123-21.patch | 5.38 KB | borisson_ |
| #19 | drupal-cachebackend-with-tests-2560123-12.d8.patch | 6.23 KB | borisson_ |
Comments
Comment #2
TomCan commentedComment #3
TomCan commentedI've provided 2 patches, one that add the check for mb_check_encoding with fallback to preg_match, the other simply replaces the mb_check_encoding call with preg_match.
Comment #4
TomCan commentedComment #5
TomCan commentedComment #6
oadaeh commentedThe
else {needs to be on a new line.Edit: here's the reference: https://www.drupal.org/coding-standards#controlstruct
Comment #7
TomCan commentedNew patch, following the Drupal coding standards
Comment #8
othermachines commentedThis error cropped up for me on a fresh install after switching to a new PHP version (mbstring disabled by default). Patch in #7 resolved it.
Comment #9
TomCan commentedUpdated version, also present in beta15 and dev.
Comment #10
borisson_Comment #11
stefan.r commentedHaving written that original line of code, I think the patch is fine - mb_check_encoding() is faster so we default to that, and fall back to a regular expression (as promised in the installer)
This should have a unit test though - we can put function_exists('mb_check_encoding') in a separate method and mock that.
Comment #12
stefan.r commentedComment #13
TomCan commentedMaybe a stupid question, but should we actually be testing this? Both the normalizeCid as the newly to be defined method to check the presence of mbstring are non-public functions. Such functionality would be implicitly tested if tests existed for the public methods (but in this case don't).
In any case, it would indeed be better to move the check to it's own method, and call that once from the constructor and store the result in a variable. There's no need to call it on every call of the normalizeCid function. Also, this way you can simply set the value through reflection rather than having to mock the method. I've added a patch for this.
I had a go at adding a test. The test will check to see if the correct method was selected. Obviously, the result of that test is dependent on the config of the system that is running the test. I also wrote a few test cids to test. The expected results were 'manualy' composed, using the Crypt::hashBase64 function. The tests probably could use some work (duplicate code etc), but it's a start.
Comment #14
stefan.r commentedLet's stick to adding a method that wraps function_exists('mb_check_encoding') rather than adding another property?
We start comments with a capital and end with a full stop.
Capital/full stop.
Missing newline before long description.
Capital/full stop.
Capital/full stop.
if ($this->mbStringExists())?
{ goes on the same line as the class
indentation is by 2 spaces
chosen.
Capital/full stop.
Short description should be 80 chars max.
Comment #15
TomCan commentedBy looking at the TestResults of the two patches I submitted, I noticed that appearantly there is already a test class for the DatabaseBackend class, located in Drupal\system\Tests\Cache\DatabaseBackendUnitTest (core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php).
I'll create a new patch that takes in your comments, as well as moves the tests to the appropriate file.
Comment #16
mradcliffeI tried to summarize from the comments the recommended approach based off of TomCan's and stefan_r's review.
I think the correct status is currently Needs work.
Comment #17
borisson_Fixed remarks in #14 and moved tests as suggested in #15.
Comment #18
stefan.r commentedDiscussed this in person with @borisson_ at the sprint in BCN. Tests will be added and we'll get rid of useMbstring.
Comment #19
borisson_Discussed this yesterday with dawehner, instead of using reflection we're now using a subclass. Test currently fails though.
Interdiff is bigger than the patch so only uploading a patch.
Comment #20
mradcliffeIs that the correct patch? It still has a reflection class and has useMbstring?
Comment #21
borisson_@mradcliffe, thanks! This should be the correct patch.
Comment #24
mradcliffeThis is different from the "@return boolean" above. It may have the
The database system has the precedent of using the Drupal\Tests\Core\Database\Stub namespace. Is that something that we want to replicate instead of having the stub in the test file? Would other tests find it useful? The MockedDatabaseBackend class in the test is fairly localized in its requirements to test the specific return values required for testing the existence of mbString so it may not have value enough on its own.
Comment #25
dawehnerAccording to the test failure in https://www.drupal.org/pift-ci-job/53967 the mb_check_encoding doesn't help anway, so we should maybe reconsider to hash it always.
Comment #26
stefan.r commented@dawehner what do you mean, doesn't help?
Hashing always would be a simpler solution, if I remember correctly the conditional hashing was to help with debugging or perhaps for performance reasons, maybe @catch would remember?
Comment #27
dawehnerWell, it seems to not hash it, see the error message, even https://3v4l.org/N9u3X indicates it should
Comment #28
catchNot hashing all the time was for debugging I think, but if it's unreliable, then we can skip the debugging aid - there are other ways to debug cache items.
Comment #29
athakural commented#7 worked for me.. Thanks!
Comment #30
Pete B commentedThere's also a reference to mb_check_encoding in normalizeLockName in core/lib/Drupal/Core/Cache/CacheCollector.php
So we probably need a generic function to take care of checking whether mb_check_encoding is available.
I haven't spent enough time with D8 to suggest the best place for such a function though.
Comment #31
berdirSo based on some simple benchmarks, preg_match() seems to be about twice as slow as mb_check_encoding(). But as @dawehner pointed out, mb_check_encoding() is actually wrong in the example string from him. So what's the point of being faster if the result is wrong? ;)
What's interesting is that on PHP 5, hashing is slower than preg_match, but on PHP7, that's actually not the case. So from a performance perspective, on PHP7, it almost isn't worth checking, the only argument to do so is making debugging easier.
Comment #32
catchSo I personally use cids in the database backend for debugging at least against Drupal 7 - makes it very easy to see which cache entries are being created and how many etc.
You can get that from writing out somewhere but a lot of extra work compared to running some queries. So for me I'd go for the preg_match().
Once we require PHP 7 we might want to revisit, but to get very good performance, you shouldn't be using database caching anyway.
Comment #33
tstoecklerNot sure per the above whether this should be using
mb_check_encoding()anyway, but if we are providing a polyfill for that function in PHP (which at least the latest patch is doing) then I would have expected that to live in\Drupal\Component\Utility\Unicodeas a newcheckEncoding()methodComment #34
dawehnerWe already pull in the polyfill component of symfony, so we could leverage https://github.com/symfony/polyfill/blob/master/src/Mbstring/Mbstring.php even I still think, its pointless to do so, when it doesn't work in the first place.
Comment #35
stefan.r commented@dawehner/@Berdir I still dont understand why this is wrong?
mb_check_encoding is merely supposed to check if the string is an ascii string, which it does a good job of in the 3v4l example -- it's not supposed to hash anything. Only if the string is not ascii do we need to hash it, but that happens elsewhere.
Comment #38
gnugetI'm Agreed with @stefan the 3v4l example seems correct to me as well.
So having
vendor/symfony/polyfill-mbstring/Mbstring.phpwe should change the approach to fix this problem? we would need to test if the extension is loaded and if it is not the case then include this class?Comment #39
dawehner@stefan.r
Well, we somehow have to explain the weird test failure above, don't we?
Comment #49
catchPer #2547419: Drupal requires mbstring PHP extension during install phase we now ship Symfony's mbstring polyfill with core, so this should no-longer fatal. But if the check itself isn't working we probably still need to do something here.
Comment #52
sboden commentedI just upgraded from Drupal 10 to Drupal 11.2.3, and I still get the error:
Comment #53
sboden commentedThe patch for Drupal 11.2.3, possible Drupal 11.x
And the snippet to include it in composer.json "cweagans" patching under "drupal/core" section:
"2560123: Fix mb_check_encoding deprecation in DatabaseBackend.php": "https://www.drupal.org/files/issues/2025-08-10/cache_databasebackend-2560123-53.patch",Comment #54
catchmb_check_encoding() shouldn't be called without arguments - that suggests NULL is being passed as a cache ID?