17

I am currently working on a module that requires a third-party PHP library, which is essentially a single PHP class. Normally, I would place it in an includes/ subdirectory and add

files[] = includes/Foo.php

to my .info file and let the Drupal 7 class auto loader do its thing when I do a $foo = new Foo().

I have permission, though, to release this module to the public and would rather not include the library with the module. I am well aware about the complications regarding licensing, but for the sake of this question, I would like to ignore it.

There is a similar question, How do I include a PHP library?, but I don't really think this answers my dilema.

This answers to this question essentially say to use the Libraries API, but every single module that I have found that uses this just does alibraries_get_path() to get the basepath (and includes fallback path when it isn't available) and then does a require or include with some error checking (or not). All do something like:

if (!class_exists('Foo')) {
  $path = function_exists('libraries_get_path') ?
    libraries_get_path('foo') : 'sites/all/libraries/foo';
  if (!include($path . '/Foo.php')) {
      // handle this error
  }
}

In this case, the Libraries API isn't really doing anything. I don't see the advantage to using this, over the old method of asking users to download a copy and place it in the module folder itself. And, there is still the issue that the module developer still needs to manually do the load with include/require. For example, the Facebook module just loads the library in a hook_init and the HTML Purifier module has an internal function to check-and-load every time the library is needed.

This may be a widespread practice, but it doesn't seem like a best practice.

Should my module take the initiative and declare a hook_libraries_info so I can use libraries_load('foo')? This, too, seems odd.

  • Another issue is whether or not the third party library's license matches drupal's. If it does, and it's not huge, I'd just include it. If it doesn't, you can't/shouldn't include it to begin with, so the library approach seems better, and have your eventual end users download it themselves. – Jimajamma Sep 10 '12 at 21:37
  • One purpose of if (libraries_load($name)) {..} is to avoid a WSOD in case the library is not present. – donquixote Jun 16 '14 at 22:40
7

Branch 2.x of the Libraries API module allows developers to define, through hook_libraries_info(), or a .info file for the library, the following information (see libraries.api):

  • The dependencies of the library
  • The version with which the library is compatible, for each of the dependencies
  • The list of the files that needs to be loaded (CSS, JavaScript, or PHP files)

The list of files that needs to be loaded is used to load those files, when the library is required. This means that your module doesn't need to load CSS, and JavaScript files with drupal_add_css(), or drupal_add_js(), as that is already done from the Libraries API module. Loading the dependencies is a task done from the Libraries API module, without the calling module doing anything.

All the module does is using the following code, for loading a library. (See Using Libraries API 2.x (as module-developer).)

// Try to load the library and check if that worked.
if (($library = libraries_load($name)) && !empty($library['loaded'])) {
  // Do something with the library here.
}

If you just need to detect if a library is present, the module should use code similar to the following one.

if (($library = libraries_detect($name)) && !empty($library['installed'])) {
  // The library is installed.
}
else {
  $error = $library['error'];
  $error_message = $library['error message'];
}

Between the properties hook_libraries_info() can return, there is also 'download url', which is not actually used, not even in the branch 3.x. Probably it will be used in future, or a third-party modules could hook into the Libraries API module, and download the libraries that are requested, but missing.

  • Can you point out any popular modules that do this with PHP libraries? Part of the motivation for the question was so that I could follow best practices for a public module, so I started to seek out ones that use the libraries API. I did not find any that implemented hook_libraries_info() and used libraries_load() internally. – mpdonadio Sep 11 '12 at 0:45
  • zencorderapi module (part of Video module) uses hook_libraries_info() – AyeshK Sep 11 '12 at 0:57
  • @MPD There is a partial list on Examples of contributed modules using Libraries API. – kiamlaluno Sep 11 '12 at 0:59
  • @kiamlaluno, thanks, that was the first place I looked. Of the six, only two of those libraries implement hook_libraries_info. I don't think your answer is wrong, but I am not convinced that this is a widespread best practice right now. One of the libraries did have an interesting technique that I am going to test out and possibly post about later. – mpdonadio Sep 11 '12 at 13:32
  • @MPD Version 7.x-2.0 has been released on July 29; it is probable that most of the modules are still using the 7.x-1 approach. – kiamlaluno Sep 11 '12 at 14:41
5

After a decent amount of digging, I am still unconvinced about what the best practice is. Inspired by the PHPMailer module, I am offering this for class based PHP libraries:

function foo_registry_files_alter (&$files, $modules)
{
  if (!class_exists('Foo')) {
    $library_path = function_exists('libraries_get_path') ?
      libraries_get_path('foo') : 'sites/all/libraries/foo';

    $files[$library_path . '/Foo.php'] = array(
      'module' => 'foo',
      'weight' => 0,
    );
  }
}

This uses hook_registry_files_alter to check for a class's existence, and if not found, add a file to the class registry (the equivalent to a files[] = ... line in a modules .info file). Then, classes defined in foo.php will be available with the autoloader, so there is no need to explicitly load the file before using the class.

This also creates a soft requirement on the Libraries API, and will use it if available, otherwise use a reasonable default.

Adding some checks via a hook_requirements to make sure the file exists, that the autoloader finds the class, version check, etc, is also be a good idea.

It is also worth noting that an autoload approach for the Libraries API is being discussed in the issue queue.

  • Dont forget to clear your cache after implementing hook_registry_files_alter, otherwise it wont trigger ;) – saadlulu Sep 7 '13 at 8:48
2

In short: If you are planning to release module to public and the (third party) library is not GPL'd, you will need to use Libraries as a dependency or ask users to download these files manually (but you will not be able to autoload it from .info file)

In little longer:

The reason why we need Libraries module is basically licensing. No matter you use that module or not, you are including that file in some way.

Well I think you didn't find good examples for such libraries-shipped-with-the -module cases. Check out SMTP module and it comes with the necessary classes as it's in GPL. (.info file blob).

Also see simplehtmldom module which just include the file but nothing else.

Where Libraries module comes handy is that, you can ask users to upload the file anywhere they want. It's not obvious that users will upload it to sites/all/libraries folder. It could be sites/example.com/libraries or something like that. Libraries module can help you to focus on your actual work by doing the directory discovery stuff for you.

For custom modules that I develop for my clients, I usually include files in module folder and use require_once or .info file entry depending on use of the library.

Also, licensing problems are not the only reason to use Libraries module. What if the third party library has rapid release cycles and your module is minimally developed ? If you include it in the module, you will have to make a new release everytime. You will not want to have a release 7.x-1.99 which is much similar to 7.x-1.0 i guess.

  • Thanks for taking the time to answer. I edited my question a bit to clarify. The question is not really about the complications of licensing and release schedules, and how the Libraries API helps with this. I am more curious about best practices about actually loading third-party libraries. – mpdonadio Sep 10 '12 at 23:01
2

It seems the major problem is the autoload.

You can use libraries module plus xautoload module.

Then in your own module, you do

function mymodule_libraries_info() {

  return array(
    'mymodule-test-lib' => array(
      'name' => 'My test library',
      ..
      'xautoload' => function($api) {
        // Register a namespace with PSR-0 root in <library dir>/lib/
        // Note: $api already knows the library directory.
        // Note: We could omit the 'lib', as this is the default value.
        $api->namespaceRoot('XALib\TestNamespace', 'lib');
      },
    ),
  );
}

This is explained in more detail here:
xautoload.api.php
More about the $api argument.

Note: You can also write your own "handlers", to implement more exotic old-school patterns beyond PSR-0 or PEAR. If you need help with that, post an issue on the xautoload queue.

Note: There is more than one way to register your library namespaces. This one is the easiest, if you want the namespaces to be registered in every request.

  • 1
    I should add, this does not help with loading procedural files. This needs to be done manually, as soon as you need the library in a request. – donquixote Nov 5 '12 at 17:44
  • Also, some libraries have their own class loading solutions. Still, it can be more convenient to use a loader already available in Drupal / contrib. – donquixote Nov 5 '12 at 17:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.