La mejor práctica para la biblioteca basada en clases PHP de terceros

17

Actualmente estoy trabajando en un módulo que requiere una biblioteca PHP de terceros, que es esencialmente una sola clase PHP. Normalmente, lo colocaría en un subdirectorio incluye / y agregaría

files[] = includes/Foo.php

a mi archivo .info y dejar que el automóvil de clase Drupal 7 Loader haga su trabajo cuando hago una $foo = new Foo().

Sin embargo, tengo permiso para lanzar este módulo al público y preferiría no incluir la biblioteca con el módulo. Soy muy consciente de las complicaciones relacionadas con la licencia, pero por el bien de esta pregunta, me gustaría ignorarla.

Hay una pregunta similar, ¿Cómo incluyo una biblioteca PHP? , pero realmente no creo que esto responda a mi dilema.

Estas respuestas a esta pregunta esencialmente dicen que use la API de bibliotecas , pero cada módulo que he encontrado que usa esto solo hace libraries_get_path()para obtener la ruta base (e incluye la ruta de respaldo cuando no está disponible) y luego hace un requireo includecon algunos comprobación de errores (o no) Todos hacen algo como:

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

En este caso, la API de Bibliotecas realmente no está haciendo nada. No veo la ventaja de usar esto, sobre el antiguo método de pedir a los usuarios que descarguen una copia y la coloquen en la carpeta del módulo. Y, todavía existe el problema de que el desarrollador del módulo aún necesita hacer la carga manualmente con include/ require. Por ejemplo, el módulo de Facebook simplemente carga la biblioteca en hook_initay el módulo Purificador de HTML tiene una función interna para verificar y cargar cada vez que se necesita la biblioteca.

Esta puede ser una práctica generalizada , pero no parece una mejor práctica.

¿Debería mi módulo tomar la iniciativa y declarar un hook_libraries_infopara que pueda usar libraries_load('foo')? Esto también parece extraño.

mpdonadio
fuente
Otro problema es si la licencia de la biblioteca de terceros coincide o no con la de drupal. Si lo hace, y no es enorme, simplemente lo incluiría. Si no lo hace, no puede / no debe incluirlo para comenzar, por lo que el enfoque de la biblioteca parece mejor, y haga que sus usuarios finales lo descarguen ellos mismos.
Jimajamma
Uno de los propósitos if (libraries_load($name)) {..}es evitar un WSOD en caso de que la biblioteca no esté presente.
donquixote

Respuestas:

7

La rama 2.x del módulo API de bibliotecas permite a los desarrolladores definir, a través de hook_libraries_info () , o un archivo .info para la biblioteca, la siguiente información (ver bibliotecas.api ):

  • Las dependencias de la biblioteca.
  • La versión con la que la biblioteca es compatible, para cada una de las dependencias.
  • La lista de los archivos que deben cargarse (archivos CSS, JavaScript o PHP)

La lista de archivos que debe cargarse se usa para cargar esos archivos, cuando se requiere la biblioteca. Esto significa que su módulo no necesita cargar archivos CSS y JavaScript con drupal_add_css(), o drupal_add_js(), como ya se hizo desde el módulo API de Bibliotecas. Cargar las dependencias es una tarea realizada desde el módulo API de Bibliotecas, sin que el módulo de llamada haga nada.

Todo lo que hace el módulo es usar el siguiente código, para cargar una biblioteca. (Consulte Uso de bibliotecas API 2.x (como desarrollador de módulos)) .

// 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 solo necesita detectar si hay una biblioteca presente, el módulo debe usar un código similar al siguiente.

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

Entre las propiedades hook_libraries_info()puede volver, también hay 'download url', que no se utiliza realmente, ni siquiera en la rama 3.x. Probablemente se usará en el futuro, o los módulos de terceros podrían conectarse al módulo API de Bibliotecas y descargar las bibliotecas que se solicitan, pero que faltan.

kiamlaluno
fuente
¿Puedes señalar algún módulo popular que haga esto con las bibliotecas PHP? Parte de la motivación para la pregunta era poder seguir las mejores prácticas para un módulo público, así que comencé a buscar las que usan la API de bibliotecas. No encontré ninguno que implementara hook_libraries_info () y utilizara bibliotecas_load () internamente.
mpdonadio
El módulo zencorderapi (parte del módulo de video) usa hook_libraries_info ()
AyeshK
@kiamlaluno, gracias, ese fue el primer lugar que busqué. De las seis, solo dos de esas bibliotecas implementan hook_libraries_info. No creo que su respuesta sea incorrecta, pero no estoy convencido de que esta sea una práctica recomendada en este momento. Una de las bibliotecas tenía una técnica interesante que voy a probar y posiblemente publicar más tarde.
mpdonadio
@MPD Versión 7.x-2.0 se lanzó el 29 de julio; Es probable que la mayoría de los módulos sigan utilizando el enfoque 7.x-1.
kiamlaluno
5

Después de una buena cantidad de excavación, todavía no estoy convencido de cuál es la mejor práctica. Inspirado por el módulo PHPMailer , estoy ofreciendo esto para bibliotecas PHP basadas en clases:

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

Esto utiliza hook_registry_files_alter para verificar la existencia de una clase y, si no se encuentra, agregar un archivo al registro de clase (el equivalente a una files[] = ...línea en un archivo .info de módulos). Luego, las clases definidas en foo.php estarán disponibles con el cargador automático, por lo que no es necesario cargar explícitamente el archivo antes de usar la clase.

Esto también crea un requisito flexible en la API de bibliotecas, y lo usará si está disponible; de ​​lo contrario, usará un valor predeterminado razonable.

También es una buena idea agregar algunas comprobaciones a través de un hook_requirements para asegurarse de que el archivo existe, de que el autocargador encuentre la clase, la comprobación de versión, etc.

También vale la pena señalar que un enfoque de carga automática para la API de bibliotecas se está discutiendo en la cola de problemas.

mpdonadio
fuente
No olvide borrar su caché después de implementar hook_registry_files_alter, de lo contrario no se disparará;)
saadlulu
2

En resumen: si planea lanzar el módulo al público y la biblioteca (de terceros) no tiene GPL, deberá usar las bibliotecas como una dependencia o solicitar a los usuarios que descarguen estos archivos manualmente (pero no podrá cargarlo automáticamente desde el archivo .info)

En poco más de tiempo:

La razón por la que necesitamos el módulo Bibliotecas es básicamente la licencia. No importa si usa ese módulo o no, está incluyendo ese archivo de alguna manera.

Bueno, creo que no encontraste buenos ejemplos para estos casos de bibliotecas enviadas con el módulo. Consulte el módulo SMTP y viene con las clases necesarias, ya que está en GPL. ( archivo de información blob ).

También vea el módulo simplehtmldom que solo incluye el archivo pero nada más.

Donde el módulo de Bibliotecas es útil es que puede pedirles a los usuarios que carguen el archivo donde quieran. No es obvio que los usuarios lo subirán a la carpeta sitios / todos / bibliotecas. Puede ser sitios / ejemplo.com / bibliotecas o algo así. El módulo de bibliotecas puede ayudarlo a concentrarse en su trabajo real haciendo el descubrimiento de directorios por usted.

Para los módulos personalizados que desarrollo para mis clientes, generalmente incluyo archivos en la carpeta del módulo y uso require_once o la entrada de archivo .info dependiendo del uso de la biblioteca.

Además, los problemas de licencia no son la única razón para usar el módulo Bibliotecas. ¿Qué sucede si la biblioteca de terceros tiene ciclos de lanzamiento rápidos y su módulo está mínimamente desarrollado? Si lo incluye en el módulo, deberá realizar una nueva versión cada vez. No querrás tener una versión 7.x-1.99 que es muy similar a 7.x-1.0, supongo.

AyeshK
fuente
Gracias por tomarte el tiempo de responder. Edité mi pregunta un poco para aclarar. La pregunta no es realmente sobre las complicaciones de las licencias y los horarios de lanzamiento, y cómo la API de Bibliotecas ayuda con esto. Tengo más curiosidad sobre las mejores prácticas sobre la carga de bibliotecas de terceros.
mpdonadio
2

Parece que el principal problema es la carga automática.

Puede usar el módulo de bibliotecas más el módulo xautoload .

Luego, en tu propio módulo, lo haces

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

Esto se explica con más detalle aquí:
xautoload.api.php
Más sobre el argumento $ api.

Nota: También puede escribir sus propios "controladores" para implementar patrones más exóticos de la vieja escuela más allá de PSR-0 o PEAR. Si necesita ayuda con eso, publique un problema en la cola de xautoload.

Nota: Hay más de una forma de registrar los espacios de nombres de su biblioteca. Este es el más fácil, si desea que los espacios de nombres se registren en cada solicitud.

Don Quijote
fuente
1
Debo agregar, esto no ayuda con la carga de archivos de procedimiento. Esto debe hacerse manualmente, tan pronto como necesite la biblioteca en una solicitud.
donquixote
Además, algunas bibliotecas tienen sus propias soluciones de carga de clases. Aún así, puede ser más conveniente usar un cargador ya disponible en Drupal / contrib.
donquixote