Coleção de dicas para desenvolver módulos para Drupal, nada que substitua a documentação oficial.
Criando uma instância Drupal para desenvolvimento com Debian 10
Pacotes básicos para subirmos uma instância de drupal com sqlite3 no debian 10:
Instalação do composer globalmente:
Criando uma instalação limpa para começar a desenvolver. Será criado um diretório chamado drupal-dev, sendo usuário/senha igual a admin/admin:
Normalmente, eu ignoro as pastas vendor, web e drush no gitignore.
Subindo um server local para desenvolvimento:
Caso precise zerar o banco e começar tudo novamente:
Criando um módulo
Todos exemplos serão baseados em um módulo fictício chamado tofu. Para o drupal reconhecer nosso módulo, isto é, o mesmo aparecer na lista de módulos para serem habilitados, necessitamos criar uma pasta chamada tofu com o arquivo tofu.info.yml, o qual contém informações básicas do módulo. O comando abaixo se encarrega de criar o módulo tofu:
Rotas
Criando rota e controller
As entradas de rotas são definidas em tofu.routing.yml. O comando a seguir vai gerar o controller TofuController com um método chamado index(), assim como uma rota /tofu apontando para esse método:
A entrada criada em tofu.routing.yml tem a forma:
Rota com parâmetros
Se no método index() do controller quisermos receber um parâmetro, por exemplo, index($parametro), modificaríamos nosso arquivo de rota assim:
Carregando node automaticamente a partir do nid na rota
O Drupal vai muito além. Suponha que esse $parametro, por algum motivo, seja o nid de nodes do seu site. Poderíamos, dentro do controller, carregar o node baseado nos id recebido, mas podemos fazer essa injeção diretamente no arquivo de rotas, assim, a variável $parametro será diretamente um objeto do tipo node:
Controllers
Exemplo básico de um controller:
Services
Criando e utilizando services
Vamos criar a classe UteisService.php e veremos como utilizá-la no controller.
Note que foi criada uma entrada em tofu.services.yml que define nossa classe como um serviço para o drupal.
Na classe UteisService.php, como exemplo, vamos criar um método que dada uma string, a devolve invertida e com todas letras em maisculá:
Injetando um serviço no controller
Queremos usar no nosso controller o método inverte($string) que está em UteisService.php, mas carregado como serviço. Isso significa que ao chamarmos $this->tofuUteis->inverte(‘Maria’) recebemos como resposta AIRAM.
Usando o mesmo comando do drupal console para criar a rota /tofu e o controller TofuControler podemos passar a flag services e especificar o serviço tofu.uteis:
A saída será como abaixo, criando uma váriável $tofuUteis, objeto instanciado do nosso serviço.
Eu costumo também fazer de outra maneira, não sei qual é a melhor forma de injetar o serviço no controller, mas ambas funcionam. Forma manual:
1 - No controller, declarar ContainerInterface e a classe do serviço:
2 - No __construct do controller receber a classe do serviço como paramêtro em atribuir numa variável local:
3 - Por fim, no método create(), que é chamado antes do controller, carregar o $container com o serviço:
Sempre olhar o __contruct() e create() da classe mãe da qual esteja injetando o service, pois neste caso, você deve injetar os services que a classe mãe também injeta. Assim, supondo que sua classe mãe injete mais dois serviços, $a e $b, para injetar o nosso tofu.uteis faríamos assimo no controller:
E no método create retornamos todos serviços que já eram carregados, acrescentando o nosso:
DAQUI PARA BAIXO FALTA REVISAR
Injetando service config.factory em classes do seu sistema
Suponha que sua classe src/Service/Uteis.php precise carregar configurações do site.
Na declaração de tofu.services.yml:
Em src/Service/Uteis.php declare ConfigFactoryInterface:
E por fim, injete $config_factory no __construct:
Agora é possível carregar configurações em qualquer métodos de Uteis.php assim:
Formulário de configuração do módulo
A seguir estão os passos para criamos um formulário de configuração de um módulo, delegando para o sistema de configuração, o armazenamento dos dados.
1 - Criando rota que aponta para ao classe do tipo Form:
2 - Se quiser uma entrada na área de configurações do site para esse módulo, em tofu.links.menu.yml inserir seguinte conteúdo:
3 - Criar a classe do formulário em src/Form estendendo ConfigFormBase, olhe cada método, eles são bem intuitivos. O formulário é construído no buildForm, veja uma lista de tipos de campos possíveis em https://api.drupal.org/api/drupal/elements. Em validateForm, adivinhe, validamos o formulário. Em submitForm salvamos, mas podemos processar os valores antes de salvar. E em getEditableConfigNames carregamos o serviço de configuração.
Não precisamos necessariamente apontar uma rota para o nosso formulário. Podemos redenderizar o formulário de dentro do controller injetando o serviço form_builder:
A vantagem nesse caso é que a variável $form é um render array que pode ser manipulado antes de ser retornado.
alter ID form: exemplo modificando a página de informações do site
Temos que saber o ID do formulário, aquele definido em getFormId(). Um caminho é identificar a rota do formulário:
E sabendo-se a rota, podemos ver qual é a classe do formulário:
Encontramos assim que o formulário está em core/modules/system/src/Form/SiteInformationForm.php identificamos o id retornado no método getFormId(): system_site_information_settings.
Em tofu.module podemos implementar o hook_form_ID_alter. No nosso exemplo, vamos: colocar um campo de texto a mais na página de configuração, validar e salvar:
Plugin: Bloco customizado
Vamos criar um plugin e esse plugin será um bloco customizado dentro de src/Plugin/Block.
1 - Criar classe TofuBlock (src/Plugin/Block/TofuBlock.php) estendendo BlockBase. Basta criarmos uma annotation com o id e título do bloclo. O único método que precisamos é o build() que deve retornar um render array com o markup do texto que será mostrado no bloco.
Mas e se queremos manipular configurações dentro do nosso bloco? Neste caso, ao invés de injetar o config.factory, vamos implementar uma interface. Se for apenas configuração que precisamos injetar o mais fácil é implementar BlockPluginInterface, e usar a configuração relacionada ao bloco com $this->getConfiguration(). Vamos aproveitar e implementar o método blockForm para mostrar um formulário na configuração do bloco, com apenas um campo, e blockSubmit para salvar a configuração e blockValidate para validar os campos.
Injetando services em Plugins
Tudo muito bonito. E se precisarmos injetar outro serviço que não a configuração? Por exemplo, o tofu.uteis? Neste caso devemos implementar ContainerFactoryPluginInterface, o que nos obriga a declarar __construct e create(), levemente diferente dos que que já vimos até agora, pois estamos no contexto de plugins, onde temos que passar o id e plugin definition no create e no __construct. O interessante é que ganhamos de graça a configuração, pois ainda temos acesso $this->setConfigurationValue('nome','valor') e $this->getConfiguration().
Assim, particularmente, eu prefiro implementar ContainerFactoryPluginInterface do que BlockPluginInterface, pois fica genérico para qualquer plugin.
phpunit
Para usar o phpunit no contexto do módulo eu tive que inserir na minha instalação do drupal de desenvolvimento as seguintes linhas no composer.json (pode ser na seção dev):