Issue title pretty much sums it up. Let's use the PHP function designed for detecting extensions, rather than checking to see if a function exists.

Alternatively, if there's a reason for not doing that, I'd love to hear it :)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

VSKor’s picture

Issue summary: View changes

Hi!

Below few results of some manual testing.


$functionExistsStart = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    function_exists('this_function_doesnt_exist');
}
$functionExistsEnd = microtime(true);

$extenstionLoadedStart = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    extension_loaded('this_extension_is_not_loaded');
}
$extenstionLoadedEnd = microtime(true);

echo 'function_exists ' .
    ($functionExistsEnd - $functionExistsStart) .
    PHP_EOL;

echo 'extension_loaded ' .
    ($extenstionLoadedEnd - $extenstionLoadedStart) .
    PHP_EOL;

function_exists 0.35822796821594
extension_loaded 0.40138292312622

Taken from http://www.merkushin.com/function_exists-vs-extension_loaded

I get little different results (i use real functions to check)

function_exists 0.26105785369873
extension_loaded 0.27371501922607 

There is no big deal between those functions, but anyway according this testing "extension_loaded" work slower.
Check please.

VSKor’s picture

Status: Active » Needs review
pounard’s picture

You have 1,000,000 calls of each running with a difference of 0.05 second, which is an actual difference of 0,00000005 (10^-7) second for a single call, this is absolutely not significant (even with 1000 calls it's still not significant) and I bet that the 0.05 second is more an artefact of something else interfering with the actual run than an actual delta between both. Performance will not be a concern for this issue :)

tstoeckler’s picture

Note that the existance of an extension is being checked just to be able to use functions that that extension provides it might actually be safer to use function_exists() because functions provided by an extension can be disabled with the disable_functions PHP ini setting, even if an extension is enabled. Thus, extension_loaded() will return TRUE but the function call will still fatal in that case. function_exists() will return FALSE.

Doesn't mean we shouldn't do this, just wanted to bring this up, because this has bitten people before. I can't find the issue ATM, however.

pounard’s picture

disable_function setting is a very dangerous thing to use and admins knows it, so I guess that only conscientious people will use it, but yes in theory you are right, but you won't do 10 function_exists calls for the same extension whereas a theoretically only one extension_exists call would be enough ?

tstoeckler’s picture

@pounard I personally actually prefer doing extension_loaded() alltogether, because it's much more intuitive and readable. I just wanted to bring this up as a possible argument against it. I don't feel strongly either way.

pounard’s picture

Fair enough, I have no strong opinion either way too. I guess all this issue need is someone willing to take the time to propose a patch! I'm sorry but I probably won't myself.

cweagans’s picture

Version: 8.0.x-dev » 8.1.x-dev
Assigned: Unassigned » cweagans
Priority: Normal » Minor
Status: Needs review » Active

There's no patch here, and this is a pretty minor thing. Moving to 8.1.x and then this can likely be cherry picked back to 8.0.x later.

I'll look at this later today or Friday, I hope. If you haven't heard from me by then, send in the search parties :P

cweagans’s picture

Priority: Minor » Normal
Status: Active » Needs review
FileSize
18.46 KB

Status: Needs review » Needs work

The last submitted patch, 9: 1942432_9-extension-loaded.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.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.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.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.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.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.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.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.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.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.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.

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.

quietone’s picture

Issue tags: +Bug Smash Initiative

In #bugsmash longwave suggested using Symfony polyfills for missing extensions.

So I did some grepping ($ egrep -r "function_exists\([^\\$]" core | awk -F'function_exists' '{print $2}' | grep -v \(\) | grep -v module_ | grep -vi drupal | grep -v \'t\' | grep -v theme | awk -F\' '{ print $2 }' | sort -u ) and hand editing to create a list of parameters in function_exists that are not test or drupal specific to see if there are polyfill for them.

apcu_fetch
com_create_guid
fastcgi_finish_request
ftp_connect
gd_info
gzopen
imagecreatefrompng
imagefilter
imagegd2
imagerotate
mb_ereg
opcache_invalidate
pcntl_fork
phpinfo
set_time_limit
ssh2_connect
system_test_help
uuid_create

It is too late to do more here.

quietone’s picture

Assigned: cweagans » Unassigned

From the above, the extensions are

apcu - apcu_fetch
com_create_guid - ?
fastcgi_finish_request - ?
ftp - ftp_connect
gd - gd_info, imagecreatefrompng, imagefilter, imagegd2, imagerotate
mbstring - mb_ereg
opcache - opcache_invalidate
pcntl - pcntl_fork
ssh2_connect - ssh2
zlib - gzopen

Polyfills available for apcu, mbstring

And unassigning since it has been over 5 years.

apaderno’s picture

Checking an extension is loaded instead of checking the necessary function exists assumes the function is always present for every PHP or extension version, which isn't necessarily true; extension functions can also be deprecated and removed.

IMO, the code should:

  • Use function_exists() when the purpose is verifying a function can be called before calling it
  • Use extension_loaded() when the purpose is telling the users an extension is required for a module to work

For example, the following code should be changed.

  $has_curl = function_exists('curl_init');
  $requirements = [];
  $requirements['curl'] = [
    'title' => t('cURL'),
    'value' => $has_curl ? t('Enabled') : t('Not found'),
  ];
  if (!$has_curl) {
    $requirements['curl']['severity'] = REQUIREMENT_ERROR;
    $requirements['curl']['description'] = t('The Aggregator module requires the <a href="https://secure.php.net/manual/en/curl.setup.php">PHP cURL library</a>. For more information, see the <a href="https://www.drupal.org/requirements/php/curl">online information on installing the PHP cURL extension</a>.');
  }

curl_init() could also be deprecated, and replaced by another function. That is irrelevant, to verify if the extension is loaded.

Vice versa, code like the following one would be wrong.

if (extension_loaded('zlib')) {
  if (!function_exists('gzopen') && function_exists('gzopen64')) {
    function gzopen($filename, $mode, $use_include_path = 0) {
      return gzopen64($filename, $mode, $use_include_path);
    }
  }
}
apaderno’s picture

To add two more examples, I would not change this code too.

  public function getRequirements() {
    $requirements = [];
    $info = gd_info();
    $requirements['version'] = [
      'title' => t('GD library'),
      'value' => $info['GD Version'],
    ];

    // Check for filter and rotate support.
    if (!function_exists('imagefilter') || !function_exists('imagerotate')) {
      $requirements['version']['severity'] = REQUIREMENT_WARNING;
      $requirements['version']['description'] = t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from http://www.libgd.org instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See <a href="http://php.net/manual/book.image.php">the PHP manual</a>.');
    }
    return $requirements;
  }
  public static function isAvailable() {
    // GD2 support is available.
    return function_exists('imagegd2');
  }
longwave’s picture

Unless any of the existing uses of function_exists() or extension_loaded() are known to be causing a problem e.g. in an edge case where disable_functions is used or a missing extension is polyfilled, I suggest we just leave this alone - there is no bug to fix here.

quietone’s picture

Category: Bug report » Task
Status: Needs work » Closed (won't fix)

I'm with longwave on this, "there is no bug to fix here.". Changing to a task.

As well as to leaving this alone. And as apaderno pointed out that "checking an extension is loaded instead of checking the necessary function exists assumes the function is always present for every PHP or extension version, which isn't necessarily true; extension functions can also be deprecated and removed."

Thanks to everyone who worked on resolving this issue.