Problem/Motivation

PHP is looking at deprecating usage of uniqid because it doesn't provide good uniqueness. Which means we're probably not getting the uniqueness we expect in the handful of places we're using it across core.

Deprecation RFC: https://wiki.php.net/rfc/deprecate-uniqid
Old discussion on uniqueness. https://marc.info/?l=php-internals&m=148314015430338&w=2

Proposed resolution

I propose we get ahead of any decision there and clean up usage of a potentially problematic function.

Remaining tasks

Decide on how we remove.
1. Inline some hash'd random implementation?
2. Provide a drupal_uniqid() implementation?
3. other?

User interface changes

n/a

API changes

New method.

Hashes will be calculated different but this should not be breaking. There is "randomness" in uniqid so there should be no expectation about the predictability of that randomness built in to our API.

Data model changes

n/a?

Comments

neclimdul created an issue. See original summary.

neclimdul’s picture

Here are the current matches in core for review:

core/includes/bootstrap.inc:  $salt = uniqid('', TRUE);
core/lib/Drupal/Core/Archiver/ArchiveTar.php:                $this->_temp_tarname = uniqid('tar') . '.tmp';
core/lib/Drupal/Core/Database/Query/Query.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Database/Query/Query.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Database/Query/SelectExtender.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Database/Query/SelectExtender.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Database/Schema.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Database/Schema.php:    $this->uniqueIdentifier = uniqid('', TRUE);
core/lib/Drupal/Core/Lock/LockBackendAbstract.php:      $this->lockId = uniqid(mt_rand(), TRUE);
core/lib/Drupal/Core/Lock/NullLockBackend.php:      $this->lockId = uniqid(mt_rand(), TRUE);
core/lib/Drupal/Core/Template/TwigEnvironment.php:          'twig_cache_prefix' => uniqid(),
core/modules/migrate/src/Plugin/MigrationPluginManager.php:    $id = isset($definition['id']) ? $definition['id'] : uniqid();
core/modules/migrate/src/Plugin/migrate/source/DummyQueryTrait.php:    $query = $this->select(uniqid(), 's')
core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php:      ->fields([TestSqlIdMap::SOURCE_IDS_HASH => uniqid()])
core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php:    $migration_plugin->id()->willReturn(uniqid());
core/modules/migrate/tests/src/Unit/process/MigrationLookupTest.php:    $migration_plugin->id()->willReturn(uniqid());
core/modules/migrate/tests/src/Unit/process/MigrationTest.php:    $this->migration_plugin->id()->willReturn(uniqid());
core/modules/migrate/tests/src/Unit/process/MigrationTest.php:    $this->migration_plugin->id()->willReturn(uniqid());
core/modules/simpletest/src/WebTestBase.php:    // time and a new uniqid.

Mostly in migrate with some outliers in the DB, Lock, Twig and that one testing salt in bootstrap.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

neclimdul’s picture

looks like they're talking about removing uniqid again on the mailing list. Lets see if anyone is working on that in the drupal queue.
Oh hey the exact issue!
Oh... hey... opened by me 2 years ago when it came up the first time... :(

I guess I'll just take a stab at something and see what happens.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

mfb’s picture

tldr I found that Crypt::randomBytesBase64(16) is 3x faster than uniqid(), because uniqid() sleeps for a full microsecond on many platforms.

But if Crypt::randomBytesBase64() is considered too much of a CPU hog, PHP 7.3+ function hrtime() could be used to build a much faster time-based "uniqid" (to which extra entropy could be added).

mondrake’s picture

Opened https://github.com/mglaman/phpstan-drupal/issues/604 for adding a rule to prevent usage of uniqid(), so that if we do this then we’re not regressing later.

mglaman’s picture

I do not think this code should go into phpstan-drupal. It seems like a candidate for a third-party library like https://github.com/ekino/phpstan-banned-code which allows banning particular functions and other code to be used.

Drupal core does not need to add that library, though. It could provide the rule itself and register it within the phpstan.neon

So we could have \Drupal\Core\Phpstan\Rules\NoUniqidRule that checks FuncCall for that function.

Then the phpstan.neon just needs the following added:

rules:
	- Drupal\Core\Phpstan\Rules\NoUniqidRule
mondrake’s picture

Well if a PHPStan extension covers the use case already, then why reinvent the wheel? Will open a spin-off to experiment.

mondrake’s picture

mglaman’s picture

then why reinvent the wheel

The suggestion was to avoid adding a dependency for a pretty simplistic rule.

And the package has other things in it not quite needed. And it makes assumptions:

Major - Should not use node with type "Expr_Exit", please change the code.
in core/assets/scaffold/files/ht.router.php:30
mondrake’s picture

#20 yes hit that, see the child issue.

Still I think if we do sth Drupal, we should still make it configurable so that one rule class, many blocked functions, as opposed to as many rule classes as the possible functions we will ever want to block.

catch’s picture

We could potentially use the cspell banned word list for this, see #3274896: Add some words we'd prefer to avoid to Drupal core's flagWords for a different use case.

It would mean you can't explain anywhere why you're not using uniqid() (at least not without using cspell:ignore) but no dependency and no code involved to use that.

I could see us using this this md5() and sha1() once #3307718: Implement xxHash for non-cryptographic use-cases is in too.

mondrake’s picture

Mmm IMHO mixing spell checking with static analysis is no good. And I'm no purist :)

Just adding a single ignoreErrors entry could make the library do what we need ATM, see https://www.drupal.org/pift-ci-job/2774106 which also bans md5() and sha1().

The good things - 1) it explains it's the use of the function that is no good (vs spellcheck); 2) we can baseline the errors now, like anything PHPStan, stop the bleeding, and fix the existing usage at different pace.

mfb’s picture

In case anyone was curious, I did some benchmarking of various unique ID methods, although just on one Linux system so this is very anecdotal.

Crypt::randomBytesBase64(16) was faster than symfony/uid package's Uuid::v4() (which outputs basically the same thing in a different format).

My homemade function hrtime(TRUE) . '-' . mt_rand() is faster still. The hrtime() counter wraps around when the system reboots.

Method:                        Time:       Sample:
hrtime(TRUE) . '-' . mt_rand()   93.606364 84465946443474-977886000
Crypt::randomBytesBase64(16)    464.163955 JE9NYPHi5GnozpnSY2D5ew
(string) Uuid::v4()             696.964993 56e1832f-50f7-4fe8-8f0e-d2a1457c35a4
uniqid('', TRUE)               1000.533745 652496918083f3.85240928

(Edited to remove some wrong info re: cross-platform behavior, that was just due to the systems rebooting.)

kim.pepper’s picture

In #3408045: [PP-1] Replace using mt_rand() to generate a unique file upload progress ID with bin2hex(random_bytes(16) we a proposing to use bin2hex(random_bytes(16) I would be interested to see how that compares to the other options.

mfb’s picture

In my testing, bin2hex(random_bytes(16)) was about 22% faster than Crypt::randomBytesBase64(16), so it would be in second place in the chart above. of course it generates a 45% longer string (32 characters instead of 22 characters).

kim.pepper’s picture

Ah good to know. I had to look up the reason we had Crypt::randomBytesBase64() and makes sense if you need it to be URL-safe.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

godotislate’s picture

#3581442: Replace usage of uniqid() in the Database system is in and replaced uniqid with bin2hex(random_bytes(12)).