Prática recomendada para biblioteca baseada em classe PHP de terceiros

17

Atualmente, estou trabalhando em um módulo que requer uma biblioteca PHP de terceiros, que é essencialmente uma única classe PHP. Normalmente, eu o colocaria em um subdiretório include / e adicionaria

files[] = includes/Foo.php

no meu arquivo .info e deixe o carregador automático da classe Drupal 7 fazer o seu trabalho quando eu fizer um $foo = new Foo().

Porém, tenho permissão para liberar este módulo ao público e preferiria não incluir a biblioteca no módulo. Estou ciente das complicações relacionadas ao licenciamento, mas, para o bem desta pergunta, eu gostaria de ignorá-la.

Há uma pergunta semelhante: como faço para incluir uma biblioteca PHP? , mas acho que isso não responde ao meu dilema.

As respostas a esta pergunta dizem essencialmente para usar a API de bibliotecas , mas cada módulo que eu descobri que usa isso apenas faz um libraries_get_path()para obter o caminho base (e inclui o caminho de fallback quando não está disponível) e, em seguida, faz um requireou includecom alguns verificação de erro (ou não). Todos fazem 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
  }
}

Nesse caso, a API de bibliotecas não está realmente fazendo nada. Não vejo a vantagem de usar isso, sobre o antigo método de solicitar aos usuários que baixem uma cópia e a coloquem na própria pasta do módulo. E ainda há o problema de que o desenvolvedor do módulo ainda precisa fazer o carregamento manualmente com include/ require. Por exemplo, o módulo do Facebook apenas carrega a biblioteca em um hook_inite o módulo Purificador de HTML tem uma função interna para verificar e carregar toda vez que a biblioteca é necessária.

Esta pode ser uma generalizada prática, mas não parece ser a melhor prática.

Meu módulo deve tomar a iniciativa e declarar um hook_libraries_infopara que eu possa usar libraries_load('foo')? Isso também parece estranho.

mpdonadio
fonte
Outra questão é se a licença da biblioteca de terceiros corresponde ou não à do drupal. Se isso acontecer, e não for enorme, eu apenas o incluiria. Caso contrário, você não pode / não deve incluí-lo, portanto, a abordagem da biblioteca parece melhor e os seus usuários finais podem fazer o download deles mesmos.
Jimajamma
Um dos objetivos if (libraries_load($name)) {..}é evitar um WSOD, caso a biblioteca não esteja presente.
Donquixote

Respostas:

7

A ramificação 2.x do módulo API de bibliotecas permite que os desenvolvedores definam, por meio de hook_libraries_info () ou um arquivo .info da biblioteca, as seguintes informações (consulte libraries.api ):

  • As dependências da biblioteca
  • A versão com a qual a biblioteca é compatível, para cada uma das dependências
  • A lista dos arquivos que precisam ser carregados (arquivos CSS, JavaScript ou PHP)

A lista de arquivos que precisa ser carregada é usada para carregar esses arquivos, quando a biblioteca é necessária. Isso significa que seu módulo não precisa carregar arquivos CSS e JavaScript com drupal_add_css(), ou drupal_add_js(), como já foi feito no módulo da API de bibliotecas. Carregar as dependências é uma tarefa realizada no módulo API de bibliotecas, sem que o módulo de chamada faça nada.

Tudo o que o módulo faz é usar o código a seguir, para carregar uma biblioteca. (Consulte Usando a API de bibliotecas 2.x (como módulo-desenvolvedor) .)

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

Se você apenas precisar detectar se uma biblioteca está presente, o módulo deve usar código semelhante ao seguinte.

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

Entre as propriedades hook_libraries_info()pode retornar, há também 'download url', o que não é realmente usado, nem mesmo na ramificação 3.x. Provavelmente será usado no futuro, ou módulos de terceiros poderão se conectar ao módulo da API de bibliotecas e fazer o download das bibliotecas solicitadas, mas ausentes.

kiamlaluno
fonte
Você pode apontar algum módulo popular que faça isso com as bibliotecas PHP? Parte da motivação para a pergunta era para que eu pudesse seguir as práticas recomendadas para um módulo público, então comecei a procurar aquelas que usavam a API de bibliotecas. Não encontrei nenhum que implementasse hook_libraries_info () e usasse libraries_load () internamente.
mpdonadio
O módulo zencorderapi (parte do módulo de vídeo) usa hook_libraries_info ()
AyeshK
@MPD Há uma lista parcial de exemplos de módulos contribuídos usando a API de bibliotecas .
kiamlaluno
@kiamlaluno, obrigado, esse foi o primeiro lugar que procurei. Das seis, apenas duas dessas bibliotecas implementam hook_libraries_info. Não acho que sua resposta esteja errada, mas não estou convencido de que essa seja uma prática recomendada amplamente difundida no momento. Uma das bibliotecas tinha uma técnica interessante que vou testar e possivelmente publicar mais tarde.
mpdonadio
O @MPD versão 7.x-2.0 foi lançado em 29 de julho; é provável que a maioria dos módulos ainda esteja usando a abordagem 7.x-1.
kiamlaluno
5

Após uma quantidade razoável de escavações, ainda não estou convencido sobre qual é a melhor prática. Inspirado no módulo PHPMailer , estou oferecendo isso para bibliotecas PHP baseadas em classe:

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

Isso usa hook_registry_files_alter para verificar a existência de uma classe e, se não for encontrado, adicione um arquivo ao registro de classes (o equivalente a uma files[] = ...linha em um arquivo .info dos módulos). Então, as classes definidas no foo.php estarão disponíveis com o carregador automático, portanto, não há necessidade de carregar explicitamente o arquivo antes de usar a classe.

Isso também cria um requisito flexível na API de bibliotecas e o utilizará se disponível, caso contrário, use um padrão razoável.

Adicionar algumas verificações por meio de um hook_requirements para garantir que o arquivo exista, que o carregador automático encontre a classe, a verificação da versão etc. também é uma boa idéia.

Também é importante notar que uma abordagem de carregamento automático para a API de bibliotecas está sendo discutida na fila de problemas.

mpdonadio
fonte
Não se esqueça de limpar o cache após a implementação hook_registry_files_alter, caso contrário ele não vai gatilho;)
saadlulu
2

Resumindo: se você planeja liberar o módulo para público e a biblioteca (de terceiros) não é da GPL, será necessário usar as Bibliotecas como uma dependência ou solicitar aos usuários que baixem esses arquivos manualmente (mas você não poderá carregá-lo automaticamente do arquivo .info)

Em pouco mais tempo:

A razão pela qual precisamos do módulo Bibliotecas é basicamente o licenciamento. Não importa se você usa esse módulo ou não, você está incluindo esse arquivo de alguma forma.

Bem, acho que você não encontrou bons exemplos para esses casos de bibliotecas enviadas com o módulo. Confira o módulo SMTP e ele vem com as classes necessárias, como na GPL. ( blob de arquivo .info ).

Veja também módulo simplehtmldom , que inclui apenas o arquivo, mas nada mais.

Onde o módulo Bibliotecas é útil, é possível solicitar aos usuários que enviem o arquivo para onde quiserem. Não é óbvio que os usuários o enviem para a pasta sites / all / libraries. Pode ser sites / example.com / bibliotecas ou algo assim. O módulo Bibliotecas pode ajudar você a se concentrar no seu trabalho real, fazendo o material de descoberta de diretório para você.

Para módulos personalizados que desenvolvo para meus clientes, geralmente incluo arquivos na pasta module e uso a entrada de arquivo require_once ou .info, dependendo do uso da biblioteca.

Além disso, problemas de licenciamento não são a única razão para usar o módulo Bibliotecas. E se a biblioteca de terceiros tiver ciclos rápidos de liberação e seu módulo for minimamente desenvolvido? Se você incluí-lo no módulo, precisará fazer uma nova versão toda vez. Você não vai querer ter um lançamento 7.x-1.99, que é muito semelhante ao 7.x-1.0, eu acho.

AyeshK
fonte
Obrigado por reservar um tempo para responder. Eu editei minha pergunta um pouco para esclarecer. A questão não é realmente sobre as complicações dos agendamentos de licenciamento e lançamento, e como a API de bibliotecas ajuda nisso. Estou mais curioso sobre as práticas recomendadas sobre o carregamento de bibliotecas de terceiros.
mpdonadio
2

Parece que o principal problema é o carregamento automático.

Você pode usar o módulo de bibliotecas mais o módulo xautoload .

Então, no seu próprio módulo, você faz

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

Isso é explicado em mais detalhes aqui:
xautoload.api.php
Mais sobre o argumento $ api.

Nota: Você também pode escrever seus próprios "manipuladores", para implementar padrões mais exóticos da velha escola além do PSR-0 ou PEAR. Se precisar de ajuda com isso, publique um problema na fila xautoload.

Nota: Há mais de uma maneira de registrar os namespaces da sua biblioteca. Este é o mais fácil, se você desejar que os namespaces sejam registrados em todas as solicitações.

Don Quixote
fonte
1
Devo acrescentar que isso não ajuda no carregamento de arquivos procedimentais. Isso precisa ser feito manualmente, assim que você precisar da biblioteca em uma solicitação.
donquixote
Além disso, algumas bibliotecas têm suas próprias soluções de carregamento de classe. Ainda assim, pode ser mais conveniente usar um carregador já disponível no Drupal / contrib.
donquixote