Meilleure pratique pour une bibliothèque basée sur une classe PHP tierce

17

Je travaille actuellement sur un module qui nécessite une bibliothèque PHP tierce, qui est essentiellement une seule classe PHP. Normalement, je le placerais dans un sous-répertoire includes / et ajouterais

files[] = includes/Foo.php

à mon fichier .info et laissez le chargeur automatique de classe Drupal 7 faire son travail quand je fais un $foo = new Foo().

J'ai cependant la permission de publier ce module au public et je préfère ne pas inclure la bibliothèque avec le module. Je suis bien conscient des complications liées à l'octroi de licences, mais pour cette question, je voudrais l'ignorer.

Il y a une question similaire, comment puis-je inclure une bibliothèque PHP? , mais je ne pense pas vraiment que cela réponde à mon dilema.

Cette réponse à cette question dit essentiellement d'utiliser l' API des bibliothèques , mais chaque module que j'ai trouvé qui utilise cela fait juste un libraries_get_path()pour obtenir le chemin de base (et inclut le chemin de secours lorsqu'il n'est pas disponible), puis fait un requireou includeavec certains vérification des erreurs (ou non). Tous font quelque chose comme:

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
  }
}

Dans ce cas, l'API Libraries ne fait vraiment rien. Je ne vois pas l'avantage d'utiliser cela, par rapport à l'ancienne méthode consistant à demander aux utilisateurs de télécharger une copie et de la placer dans le dossier du module lui-même. Et, il y a toujours le problème que le développeur du module doit encore effectuer manuellement le chargement avec include/ require. Par exemple, le module Facebook charge simplement la bibliothèque dans un hook_initet le module HTML Purifier a une fonction interne pour vérifier et charger chaque fois que la bibliothèque est nécessaire.

Cela peut être une pratique répandue , mais cela ne semble pas être une meilleure pratique.

Mon module doit-il prendre l'initiative et en déclarer un hook_libraries_infopour que je puisse l'utiliser libraries_load('foo')? Cela aussi semble étrange.

mpdonadio
la source
Un autre problème est de savoir si la licence de la bibliothèque tierce correspond à celle de Drupal. Si c'est le cas, et ce n'est pas énorme, je l'inclurais simplement. Si ce n'est pas le cas, vous ne pouvez pas / ne devriez pas l'inclure pour commencer, donc l'approche de la bibliothèque semble meilleure et demandez à vos utilisateurs finaux de la télécharger eux-mêmes.
Jimajamma
L'un des objectifs de if (libraries_load($name)) {..}est d'éviter un WSOD au cas où la bibliothèque ne serait pas présente.
donquixote

Réponses:

7

La branche 2.x du module API Libraries permet aux développeurs de définir, via hook_libraries_info () , ou un fichier .info pour la bibliothèque, les informations suivantes (voir library.api ):

  • Les dépendances de la bibliothèque
  • La version avec laquelle la bibliothèque est compatible, pour chacune des dépendances
  • La liste des fichiers à charger (fichiers CSS, JavaScript ou PHP)

La liste des fichiers à charger est utilisée pour charger ces fichiers, lorsque la bibliothèque est requise. Cela signifie que votre module n'a pas besoin de charger des fichiers CSS et JavaScript avec drupal_add_css()ou drupal_add_js(), comme cela est déjà fait à partir du module API des bibliothèques. Le chargement des dépendances est une tâche effectuée à partir du module API des bibliothèques, sans que le module appelant ne fasse quoi que ce soit.

Tout le module utilise le code suivant pour charger une bibliothèque. (Voir Utilisation de l'API des bibliothèques 2.x (en tant que développeur de module) .)

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

Si vous avez juste besoin de détecter si une bibliothèque est présente, le module doit utiliser un code similaire au suivant.

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

Entre les propriétés hook_libraries_info()peuvent revenir, il y en a aussi 'download url', qui n'est pas réellement utilisé, pas même dans la branche 3.x. Il sera probablement utilisé à l'avenir, ou un module tiers pourrait se connecter au module API des bibliothèques et télécharger les bibliothèques demandées, mais manquantes.

kiamlaluno
la source
Pouvez-vous signaler des modules populaires qui le font avec les bibliothèques PHP? Une partie de la motivation de la question était pour que je puisse suivre les meilleures pratiques pour un module public, alors j'ai commencé à chercher ceux qui utilisent l'API des bibliothèques. Je n'en ai trouvé aucun qui implémentait hook_libraries_info () et utilisait bibliothèque_load () en interne.
mpdonadio
Le module zencorderapi (partie du module vidéo) utilise hook_libraries_info ()
AyeshK
@MPD Il existe une liste partielle des exemples de modules contribués utilisant l'API Libraries .
kiamlaluno
@kiamlaluno, merci, c'est le premier endroit où j'ai regardé. Sur les six, seulement deux de ces bibliothèques implémentent hook_libraries_info. Je ne pense pas que votre réponse soit fausse, mais je ne suis pas convaincu que ce soit une pratique exemplaire répandue à l'heure actuelle. L'une des bibliothèques avait une technique intéressante que je vais tester et éventuellement publier plus tard.
mpdonadio
@MPD version 7.x-2.0 a été publiée le 29 juillet; il est probable que la plupart des modules utilisent encore l'approche 7.x-1.
kiamlaluno
5

Après avoir creusé suffisamment, je ne suis toujours pas convaincu de la meilleure pratique. Inspiré du module PHPMailer , je propose ceci pour les bibliothèques PHP basées sur les classes:

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,
    );
  }
}

Cela utilise hook_registry_files_alter pour vérifier l'existence d'une classe, et s'il n'est pas trouvé, ajouter un fichier au registre de classe (l'équivalent d'une files[] = ...ligne dans un fichier .info des modules). Ensuite, les classes définies dans foo.php seront disponibles avec l'autochargeur, il n'est donc pas nécessaire de charger explicitement le fichier avant d'utiliser la classe.

Cela crée également une exigence souple sur l'API des bibliothèques, et l'utilisera si disponible, sinon utilisez une valeur par défaut raisonnable.

Ajouter quelques vérifications via un hook_requirements pour s'assurer que le fichier existe, que l'autochargeur trouve la classe, la vérification de version, etc., est également une bonne idée.

Il convient également de noter qu'une approche de chargement automatique pour l'API Libraries est en cours de discussion dans la file d'attente des problèmes.

mpdonadio
la source
N'oubliez pas de vider votre cache après avoir implémenté hook_registry_files_alter, sinon cela ne se déclenchera pas;)
saadlulu
2

En bref: si vous prévoyez de publier le module au public et que la bibliothèque (tierce) n'est pas sous GPL, vous devrez utiliser les bibliothèques comme dépendance ou demander aux utilisateurs de télécharger ces fichiers manuellement (mais vous ne pourrez pas chargement automatique à partir du fichier .info)

En un peu plus longtemps:

La raison pour laquelle nous avons besoin du module Bibliothèques est essentiellement la licence. Peu importe que vous utilisiez ce module ou non, vous incluez ce fichier d'une manière ou d'une autre.

Eh bien, je pense que vous n'avez pas trouvé de bons exemples pour ces cas de bibliothèques livrées avec le module. Consultez le module SMTP et il est livré avec les classes nécessaires car il est en GPL. ( Blob de fichier .info ).

Voir également le module simplehtmldom qui inclut simplement le fichier mais rien d'autre.

Lorsque le module Bibliothèques est pratique, c'est que vous pouvez demander aux utilisateurs de télécharger le fichier où ils le souhaitent. Il n'est pas évident que les utilisateurs le téléchargeront dans le dossier sites / all / bibliothèques. Il peut s'agir de sites / example.com / bibliothèques ou quelque chose comme ça. Le module Bibliothèques peut vous aider à vous concentrer sur votre travail réel en effectuant les tâches de découverte d'annuaire pour vous.

Pour les modules personnalisés que je développe pour mes clients, j'inclus généralement des fichiers dans le dossier du module et j'utilise l'entrée de fichier require_once ou .info selon l'utilisation de la bibliothèque.

De plus, les problèmes de licence ne sont pas la seule raison d'utiliser le module Bibliothèques. Que faire si la bibliothèque tierce a des cycles de publication rapides et que votre module est peu développé? Si vous l'incluez dans le module, vous devrez faire une nouvelle version à chaque fois. Vous ne voudrez pas avoir une version 7.x-1.99 qui est très similaire à 7.x-1.0 je suppose.

AyeshK
la source
Merci d'avoir pris le temps de répondre. J'ai modifié ma question un peu pour clarifier. La question ne porte pas vraiment sur les complications des licences et des calendriers de publication, ni sur la manière dont l'API des bibliothèques y contribue. Je suis plus curieux de connaître les meilleures pratiques concernant le chargement de bibliothèques tierces.
mpdonadio
2

Il semble que le problème majeur soit le chargement automatique.

Vous pouvez utiliser le module de bibliothèques plus le module xautoload .

Ensuite, dans votre propre module, vous faites

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');
      },
    ),
  );
}

Ceci est expliqué plus en détail ici:
xautoload.api.php
Plus d'informations sur l'argument $ api.

Remarque: Vous pouvez également écrire vos propres "gestionnaires", pour implémenter des modèles old school plus exotiques au-delà de PSR-0 ou PEAR. Si vous avez besoin d'aide, postez un problème dans la file d'attente xautoload.

Remarque: Il existe plusieurs façons d'enregistrer les espaces de noms de votre bibliothèque. Celui-ci est le plus simple, si vous souhaitez que les espaces de noms soient enregistrés dans chaque demande.

don Quichotte
la source
1
Je dois ajouter que cela n'aide pas au chargement des fichiers de procédure. Cela doit être fait manuellement, dès que vous avez besoin de la bibliothèque dans une demande.
donquixote
De plus, certaines bibliothèques ont leurs propres solutions de chargement de classe. Pourtant, il peut être plus pratique d'utiliser un chargeur déjà disponible dans Drupal / contrib.
donquixote