
En este artículo quiero recoger los conceptos básicos para comenzar a desarrollar nuestros propios módulos bajo Drupal 8. Es sólo un punto de partida, y dejaré enlaces para profundizar en más conceptos.
Serán necesarios conocimientos de PHP y Symfony antes de continuar.
Configuración XAMP
Ver las notas en el artículo Drupal 8: Guía de referencia.
Drush
Drush es un script de línea de comandos que nos permite administrar un sitio Drupal.
Si hemos realizado la instalación de nuestro sitio Drupal con Composer nos lo encontraremos instalado directamente. Incluye la siguiente ruta en la variable de entorno PATH para utilizarlo cómodamente.
/proyecto_composer/vendor/bin
Debemos utilizarlo desde la carpeta "web" del proyecto en la siguiente ruta:
/proyecto_composer/web$ drush version
Drupal Console
Drupal Console es un script de línea de comandos que nos permite administrar un sitio Drupal y crear plantillas de diferentes componentes mientras estamos desarrollando nuestros propios módulos.
Si hemos realizado la instalación de nuestro sitio Drupal con Composer nos lo encontraremos instalado directamente. Incluye la siguiente ruta en la variable de entorno PATH para utilizarlo cómodamente.
/proyecto_composer/vendor/bin
Debemos utilizarlo desde la carpeta "web" del proyecto en la siguiente ruta:
/proyecto_composer/web$ drupal about
API de Drupal
La API de Drupal la podemos encontrar en el siguiente enlace:
https://api.drupal.org/api/drupal
Y mucha más información en profundidad en el siguiente enlace:
https://www.drupal.org/developing/api
Estándar de codificación en Drupal
Todos los estándares recomendados para desarrollar bajo Drupal se pueden encontrar en el siguiente enlace:
https://www.drupal.org/developing/api
En concreto, el código debe cumplir los siguientes requisitos:
https://www.drupal.org/docs/develop/standards/coding-standards
En concreto, la documentación y los comentarios deben cumplir los siguientes requisitos:
https://www.drupal.org/docs/develop/standards/api-documentation-and-comment-standards
Coder
Coder es un conjunto de "sniffs" que permiten a PHP Code Sniffer analizar el código fuente de módulo y comprobar si cumple los estándares de codificación de Drupal. Por tanto, los "sniffs" son dichos estándares de codificación.
Instalar Coder y PHP Code Sniffer:
composer require dealerdirect/phpcodesniffer-composer-installer composer require drupal/coder
Incluye la siguiente ruta en la variable de entorno PATH para utilizarlo cómodamente.
/proyecto_composer/vendor/bin
Podemos analizar los estándares de codificación de un módulo especificando las extensiones a analizar y la carpeta de dicho módulo:
phpcs --standard=Drupal --extensions=php,module,inc,install,test,profile,theme,twig,css,json,info,yml,txt,md /proyecto_composer/web/modules/contrib/admin_toolbar/
Podemos analizar las buenas prácticas de codificación de un módulo especificando las extensiones a analizar y la carpeta de dicho módulo:
phpcs --standard=DrupalPractice --extensions=php,module,inc,install,test,profile,theme,twig,css,json,info,yml,txt,md /proyecto_composer/web/modules/contrib/admin_toolbar/
Podemos solucionar los errores de codificación de un módulo especificando las extensiones a analizar y la carpeta de dicho módulo:
phpcbf --standard=Drupal --extensions=php,module,inc,install,test,profile,theme,twig,css,json,info,yml,txt,md /proyecto_composer/web/modules/contrib/admin_toolbar/
Devel
[Contrib] Devel: Funcionalidades para dsarrolladores
Incluye cuatro módulos muy interesantes orientados al desarrollo de módulos propios.
- Devel. Añade varias funcionalidades para desarrolladores.
- Devel generate. Permite crear contenidos de prueba para utilizardurante el desarrollo del sitio.
- Devel Kint. Integra Kint en Devel para depuración.
- Web Profiler. Añade una barra inferior con información de depuración de cada página cargada.
Podemos configurar las opciones en:
- Configuración -> Desarrollo -> Opciones de desarrollo.
En el Diseño de Bloques podemos incluir el bloque Development en el pie de página que incluirá una serie de enlaces interesantes durante el desarrollo.
Si tenemos instalado Admin Toolbar nos creará un menú Desarrollo también con todas las opciones disponibles.
El módulo nos permite utilizar una serie de funciones de depuración nuevas durante el desarrollo del módulo (más información).
Posee funciones de generación de contenido aleatorio.
E incluye una poderosa barra de herramientas en la parte inferior de la página con toda muchísima información de depuración.
Registro y errores
Por defecto, Drupal oculta los errores PHP. Para activarlos en nuestro sitio de desarrollo debemos activarlos en:
- Configuración -> Desarrollo -> Registro y errores.
Examples for Developers
[Contrib] Examples for Developers
Es un módulo que incluye numerosos módulos de ejemplo expresamente para desarrolladores. No es necesario activarlo, su fin es simplemente leer su código.
Definición del módulo
Comenzaremos definiendo el nombre del módulo real y el nombre de módulo máquina (más información).
Continuaremos generando la estructura básica del nuevo módulo mediante el comando drupal, el cual nos irá preguntando la información mínima necesaria para ello:
drupal generate:module // Welcome to the Drupal module generator Enter the new module name: > TicArte Enter the module machine name [ticarte]: > Enter the module Path [modules/custom]: > Enter module description [My Awesome Module]: > Example custom module Enter package name [Custom]: > TicArte Enter Drupal Core version [8.x]: > Do you want to generate a .module file? (yes/no) [yes]: > Define module as feature (yes/no) [no]: > Do you want to add a composer.json file to your module? (yes/no) [yes]: > no Would you like to add module dependencies? (yes/no) [no]: > yes Module dependencies separated by commas (i.e. context, panels): > ctools, token Do you want to generate a unit test class? (yes/no) [yes]: > Do you want to generate a themeable template? (yes/no) [yes]: > Do you want proceed with the operation? (yes/no) [yes]: > Generated or updated files Generation path: /opt/lampp/htdocs/drupal8/web 1 - /modules/custom/ticarte/ticarte.info.yml 2 - /modules/custom/ticarte/ticarte.module 3 - /modules/custom/ticarte/tests/src/Functional/LoadTest.php 4 - /modules/custom/ticarte/ticarte.module 5 - /modules/custom/ticarte/templates/ticarte.html.twig Generated lines: 90
Fichero .info.yml
Define la información general del módulo: nombre, descripción, versión, dependencias, etc. Único fichero obligatorio de la estructura.
Fichero .module
Define los hooks del sistema, que son estructuras que modifican el mismo y que continuan su funcionamiento desde Drupal 7 y aún no han evolucionado, por ejemplo, la ayuda del módulo, la definición de plantillas Twig, etc.
Instalación y desinstalación del módulo
Para instalar y desinstalar el módulo podemos hacerlo en un sólo paso gracias al módulo Devel:
- Development -> Reinstalar módulos
Desde línea de comandos de la siguiente manera:
drush pm:enable ticarte drush pm:uninstall ticarte
Reconstrucción de las cachés
Ante cualquier cambio de código que hagamos podremos necesitar reconstruir las cachés y que se vuelva a cargar el nuevo código:
drush cache:rebuild
Definición del controlador y su ruta
Como en Symfony, Drupal utiliza Controladores para gestionar la información de presentación, al cual se le asocia una ruta por la que se accede y, de manera opcional, una plantilla que renderice los resultados.
drupal generate:controller // Welcome to the Drupal Controller generator Enter the module name [admin_toolbar]: > ticarte Enter the Controller class name [DefaultController]: > HelloController Enter the Controller method title (to stop adding more methods, leave this empty) []: > Hello TicArte Enter the action method name [hello]: > hello Enter the route path [/ticarte/hello/{name}]: > Enter the Controller method title (to stop adding more methods, leave this empty) []: > Do you want to generate a unit test class? (yes/no) [yes]: > no Do you want to load services from the container? (yes/no) [no]: > Do you want proceed with the operation? (yes/no) [yes]: > // router:rebuild Rebuilding routes, wait a moment please [OK] Done rebuilding route(s). Generated or updated files Generation path: /opt/lampp/htdocs/drupal8/web 1 - modules/custom/ticarte/src/Controller/HelloController.php 2 - modules/custom/ticarte/ticarte.routing.yml Generated lines: 33
El controlador se creará dentro de la carpeta src/Controller.
Más información sobre controladores.
El controlador puede devolver directamente la salida formateándola:
return [ '#type' => 'markup', '#markup' => $this->t('Hello, World!'), ];
La ruta se creará dentro del archivo ticarte.routing.yml.
Renderización de una plantilla Twig desde un controlador
Un controlador también puede renderizar una plantilla Twig, para ello necesitamos codificar los siguientes pasos.
Más información sobre renderización de plantillas Twig
a) Definir hook_theme que indique el nombre de la plantilla, en nuestro caso ticarte (/ticarte.module).
function ticarte_theme($existing, $type, $theme, $path) { return [ 'ticarte' => [ 'render element' => 'children', 'template' => 'ticarte', 'variables' => [ 'node_title' => NULL, ], ], ]; }
b) Llamar a la plantilla desde el controlador (/src/Controller/HelloController.php).
return [ '#theme' => 'ticarte', '#node_title' => $this->t('Value'), ];
c) Crear la plantilla Twig, que debe llamarse al igual que la hemos definido anteriormente (/templates/ticarte.twig.html).
<p>Test twig template!</p> <p>Title: {{ node_title }}</p>
d) Reconstruir la caché.
Definición de formularios de configuración
Los modulos poseen configuración que se puede modificar desde formularios que normalmente están en la ruta /admin/config/. A continuación se desarrolla un ejemplo en el que el formulario posee un campo del tipo select con dos opciones.
drupal generate:form:config // Welcome to the Drupal Form Config generator Enter the module name [admin_toolbar]: > ticarte Enter the Form Class name [DefaultForm]: > SettingsForm Enter the Form id [settings_form]: > ticarte_settings_form Do you want to load services from the container? (yes/no) [no]: > no Do you want to generate a config file? (yes/no) [yes]: > yes Do you want to generate a form structure? (yes/no) [yes]: > yes Available types: button, checkbox, checkboxes, color, date, datelist, datetime, email, entity_autocomplete, field_ui_table, fieldset, file, hidden, image_button, item, language_configuration, language_select, machine_name, managed_file, number, password, password_confirm, path, radio, radios, range, search, select, submit, table, tableselect, tel, text_format, textarea, textfield, token, token_tree_table, url, value, weight Enter a new field properties New field type (press <return> to stop adding fields) []: > select Input label: > Source Input machine name [source]: > source Size of multiselect box (in lines) [5]: > 1 Input options separated by comma: > local, server Description []: > Source data Default value []: > local Weight for input item [0]: > 0 Enter a new field properties New field type (press <return> to stop adding fields) []: > Enter the route path [/admin/config/ticarte/settings]: > /admin/config/ticarte/settings Generate a menu link (yes/no) [yes]: > yes A title for the menu link [SettingsForm]: > TicArte Menu parent [system.admin_config_system]: > system.admin_config_system A description for the menu link [A description for the menu entry]: > Ticarte settings // router:rebuild Rebuilding routes, wait a moment please [OK] Done rebuilding route(s). Generated or updated files Generation path: /opt/lampp/htdocs/drupal8/web 1 - modules/custom/ticarte/ticarte.routing.yml 2 - modules/custom/ticarte/src/Form/SettingsForm.php 3 - modules/custom/ticarte/config/install/ticarte.settings.yml 4 - modules/custom/ticarte/ticarte.links.menu.yml Generated lines: 73
A continuación se entra en detalle de los ficheros generados.
a) Definir el formulario de configuración (/src/Form/SettingsForm.php).
La función getEditableConfigNames() define la clave donde se almacenarán los valores de configuración
La función buildForm() define los campos del formulario y recupera el valor almacenado de cada campo (más información sobre el elemento Form y sobre los elementos inputs del Form).
La función submitForm() almacena los valores del formulario.
b) Definir los valores por defecto que se cargarán en el formulario la primera vez que se instale el módulo (/config/install/ticarte.settings.yml). Deben aparecer diretactamente los nombre de los identificadores de los campos creados anteriormente. El fichero generado por Drupal console puede que nos lo haya incluido en otro valor superior, por lo que tenemos que repasarlo y modificarlo.
source: "local"
c) Definir la ruta de acceso al formulario, el título y sus permisos (/ticarte.routing.yml).
d) Definir el enlace del menú (/ticarte.links.menu.yml).
e) Acceso a las variables de configuración desde otras partes del módulo:
$source = \Drupal::config('ticarte.settings')->get('source');
Definición de tipos de campos
Las entidades permiten tener asociados campos en los que almacenar información su información. Los campos están divididos en tres secciones:
- FieldType: Define la información que almacena el campo y la configuración previa del campo.
- WidgetType: Define el formulario de introducción de datos del campo.
- FormatterType: Define la presentación de los datos de campo.
Un campo puede tener varios WidgetType y FormatterType.
A continuación vamos a crear un tipo de campo para almacenar el Color de una entidad.
drupal generate:plugin:field // Welcome to the Drupal Field Plugin generator Enter the module name [admin_toolbar]: > ticarte Enter field type plugin class name [ExampleFieldType]: > ColorFieldType Enter the field type plugin label [Color field type]: > Color Enter the field type plugin id [color_field_type]: > color_field_type Enter the field type plugin description [My Field Type]: > Color field Enter the field widget plugin class name [ExampleWidgetType]: > ColorWidgetType Enter the field widget plugin label [Color widget type]: > Color Enter the field widget plugin id [color_widget_type]: > color_widget_type Enter the field formatter plugin class name [ExampleFormatterType]: > ColorFormatterType Enter the field formatter plugin label [Color formatter type]: > Color Enter the field formatter plugin id [color_formatter_type]: > color_formatter_type Enter the field type the formatter and widget plugin can be used with [color_field_type]: > color_field_type Enter the default field widget of the field type plugin [color_widget_type]: > color_widget_type Enter the default field formatter of field type plugin [color_formatter_type]: > color_formatter_type Do you want proceed with the operation? (yes/no) [yes]: > yes // generate:plugin:fieldtype // generate:plugin:fieldwidget // generate:plugin:fieldformatter // cache:rebuild Rebuilding cache(s), wait a moment please. [OK] Done clearing cache(s). Generated or updated files Generation path: /opt/lampp/htdocs/drupal8/web 1 - modules/custom/ticarte/src/Plugin/Field/FieldType/ColorFieldType.php 2 - modules/custom/ticarte/src/Plugin/Field/FieldWidget/ColorWidgetType.php 3 - modules/custom/ticarte/ticarte.schema.yml 4 - modules/custom/ticarte/src/Plugin/Field/FieldFormatter/ColorFormatterType.php Generated lines: 302
A continuación se entra en detalle de los ficheros generados.
a) Definir la configuración de almacenamiento del campo (/src/Plugin/Field/FieldType/ColorFieldType.php).
La función propertyDefinitions() define los datos que va a almacenar el campo.
La función schema() define cómo los datos que va a almacenar el campo se almacenan en la base de datos.
La función getConstraints() aplica restricciones a los datos que se van a almacenar en la base de datos.
La función isEmpty() define cuándo se considera vacío dicho campo.
Si el campo lleva asociado algún valor de configuración al crearlo, debemos utilizar las funciones defaultStorageSettings() y storageSettingsForm() para definir y almacenar dichas configuraciones.
b) Definir la configuración por defecto del formulario de configuración del campo (/ticarte.schema.yml).
Sólo es necesario en caso de que el campo lleve asociado algún valor de configuración al crearlo.
c) Definir el fomulario de introducción de datos del campo (/src/Plugin/Field/FieldWidget/ColorWidgetType.php).
La función formElement() define el fomulario de entrada de datos al campo.
La función settingsForm() define el formulario de configuración de la entrada de datos al campo.
La función settingsSummary() define el resumen que se muestra al usuario con respecto al formulario de configuración de la entrada de datos al campo.
d) Definir la visualización de datos del campo (/src/Plugin/Field/FieldFormatter/ColorFormatterType.php).
La función viewElements() define la visualización de datos del campo.
La función settingsForm() define el formulario de configuración de la visualización de datos del campo.
La función settingsSummary() define el resumen que se muestra al usuario con respecto al formulario de configuración de la visualización de datos del campo.
Acceso a bases de datos
Podemos utilizar la API de Drupal para acceder tanto a la propia base de datos como a base de datos externas.
Más información sobre API Database
Para realizar una consulta puntual y estática se recomienda seguir los siguientes pasos (más información):
$database = \Drupal::database(); $query = $database->query("SELECT id, example FROM {mytable} WHERE created > :created", [ ':created' => REQUEST_TIME - 3600,]); $result = $query->fetchAll();
Publicación de tu módulo en Drupal.org
Para contribuir con la comunidad de Drupal, puedes publicar tu módulo y compartirlo para que se beneficien el resto de usuarios de Drupal.
Drupal 8: Desarrollo de módulos escrito por Rafa Morales está protegido por una licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional