Esquema de configuración / metadatos
Drupal 8 incluye soporte para un lenguaje de esquemas/metadatos creado con Kwalify (http://www.kuwata-lab.com/kwalify/) para archivos de configuración YAML. Kwalify está escrito en Ruby y se requirieron algunos ajustes en el formato, por lo que no todos los detalles de Kwalify aplican directamente, pero es bastante cercano.
Hoja de referencia rápida
Para una comprensión rápida y algunos ejemplos útiles, consulte esta hoja de referencia y luego continúe leyendo si todavía tiene preguntas:
/sites/default/files/config-schema-cheat-sheet1.5.pdf
Ejemplo introductorio
El módulo system tiene dos parámetros de configuración relacionados con el modo de mantenimiento (independientemente de si el sitio está en modo offline para visitantes normales):
<?php $config = \Drupal::config('system.maintenance'); $message = $config->get('message'); $langcode = $config->get('langcode'); ?>
(El estado de activación del mantenimiento se guarda en el sistema de estado, no en la configuración.)
Los valores predeterminados para este objeto de configuración se almacenan en el archivo core/modules/system/config/install/system.maintenance.yml como:
message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.' langcode: en
Cada módulo puede tener tantos objetos de configuración como necesite. Todo esto se explica en uno o más archivos de esquema que se entregan con el módulo. En el caso del módulo system, los archivos están en core/modules/system/config/schema. La sección correspondiente del esquema en el archivo system.schema.yml se ve así:
system.maintenance: type: config_object label: 'Maintenance mode' mapping: message: type: text label: 'Message to display when in maintenance mode'
La clave de nivel superior ("system.maintenance") en el archivo se refiere al nombre base del archivo .yml ("system.maintenance.yml") y al nombre del objeto de configuración (config('system.maintenance')). Los niveles anidados describen lo que hay dentro del archivo. El esquema de configuración define dos tipos de archivo principales: config_object para archivos de configuración global y config_entity para entidades. El tipo config_object está definido en core.data_types.schema.yml de la siguiente manera:
# Root of a configuration object. _core_config_info: type: mapping mapping: default_config_hash: type: string label: 'Default configuration hash' config_object: type: mapping mapping: langcode: type: string label: 'Language code' _core: type: _core_config_info
El mapping es un tipo base para pares clave-valor. Usando el tipo config_object, la definición del modo de mantenimiento reutiliza las claves langcode y _core y añade otra clave para el mensaje mismo. Volviendo a la definición de system.maintenance, la etiqueta del esquema label:'Maintenance mode' describe el contenido del esquema. Los elementos reales se listan bajo la clave mapping, donde la clave message está definida heredando langcode y _core del tipo base. Cada elemento tiene un tipo y una etiqueta label que describen el tipo de dato y dan una descripción para la UI. La etiqueta normalmente coincide o es similar a la etiqueta del formulario de configuración donde el valor puede ser editado por el administrador del sistema.
En todos los casos soportados por el núcleo, el elemento de nivel superior en el archivo .yml será un mapping con los elementos descritos en la lista mapping. Debe usar uno de los dos subtipos de mapping definidos: config_object o config_entity. Los elementos individuales en el mapping pueden ser de cualquier tipo dependiendo de cómo haya definido sus datos. La clave _core y todas las claves dentro de _core están reservadas para el núcleo de Drupal.
¿Para qué sirven los archivos de esquema?
- Los archivos principales se introdujeron para dar soporte multilingüe. Se necesita una herramienta para identificar todas las cadenas traducibles en la configuración suministrada, para que cuando se envíen sus propias configuraciones, vistas predeterminadas, roles adicionales, menús, etc., puedan ofrecerse para traducción en la liberación de su módulo/tema en https://localize.drupal.org. Para este caso de uso, los niveles y tipos de anidamiento son suficientes.
- También usamos esquemas para proporcionar formularios reales de traducción para la configuración basada en sus datos. En este caso, los tipos cobran más importancia y las etiquetas se vuelven cruciales. El módulo de traducción de configuración base usa esquemas para generar formularios de traducción y guardar traducciones. Los dos tipos incorporados más importantes traducibles son “label” para entradas de texto de una línea y “text” para entradas de texto multilínea.
- Usando el conocimiento incorporado en los esquemas de configuración sobre lo que se almacena en el objeto de configuración, la persistencia predeterminada de los objetos de configuración requiere un esquema de configuración para el objeto, de modo que las propiedades correctas se exporten con tipos específicos. Aunque es mejor proveer esquemas de configuración, si realmente no desea hacerlo, implemente el método toArray() en su implementación de entidad de configuración para no requerir un esquema para guardar sus entidades de configuración personalizadas.
- El esquema de configuración también se usa para vincular automáticamente valores a los tipos esperados. Esto asegura que aunque PHP y las webforms prefieren cadenas sobre otros tipos, al guardar la configuración se usen los tipos correctos. Esto es importante para que, al desplegar configuraciones, las diferencias muestren solo cambios reales y no cambios accidentales de tipo.
- En PHPUnit, todas las pruebas derivadas de TestBase hacen cumplir estrictamente los esquemas de configuración por defecto. Esto causará errores de esquema si falta o es inválido el archivo de esquema. Aunque no se recomienda, se puede desactivar poniendo en su prueba:
protected $strictConfigSchema = FALSE;
Vea https://drupal.org/project/config_inspector para un módulo que ayuda a depurar sus esquemas. Este módulo ayuda a encontrar esquemas faltantes y elementos de esquema con diferentes vistas de sus datos y esquemas.
También existen otras ideas para esquemas que pueden proveer módulos, por ejemplo, crear interfaces web services basadas en algunos de ellos. Seguramente existen otros casos de uso que la gente descubrirá y que aún no hemos imaginado.
Propiedades
- type: el tipo del valor; puede ser un tipo básico o derivado (vea ejemplos abajo).
- label: etiqueta para la interfaz de usuario para el valor. No tiene que coincidir con la etiqueta del formulario de configuración correspondiente, pero una correspondencia mejora la claridad.
- translatable: indica si un tipo específico es traducible; Nota: puede usar
type: label
como abreviación de:
type: string translatable: true
- nullable: si el valor puede ser nulo; si no se especifica, se usa el valor por defecto.
- class: usado solo para tipos básicos para asignar la clase que implementa el parseo (los ejemplos abajo son para TypedData y tipos de configuración definidos por el sistema).
- Propiedades específicas de tipo:
- mapping: propiedad para el tipo mapping que se usa para describir elementos base en el mapping. Las claves y tipos de valores en el mapping deben describirse en el esquema. Sólo se permiten claves string en mappings.
- sequence: propiedad para el tipo sequence que se usa para enumerar elementos base en la secuencia. Las claves pueden ser números enteros o strings, sin importancia.
Tipos soportados en archivos de metadatos
Como se mencionó antes, los tipos más básicos y algunos tipos complejos interesantes están definidos en core.data_types.schema.yml.
# Undefined type used by the system to assign to elements at any level where # configuration schema is not defined. Using explicitly has the same effect as # not defining schema, so there is no point in doing that. undefined: label: 'Undefined' class: '\Drupal\Core\Config\Schema\Undefined' # Explicit type to use when no data typing is possible. Instead of using this # type, we strongly suggest you use configuration structures that can be # described with other structural elements of schema, and describe your schema # with those elements. ignore: label: 'Ignore' class: '\Drupal\Core\Config\Schema\Ignore' # Basic scalar data types from typed data. boolean: label: 'Boolean' class: '\Drupal\Core\TypedData\Plugin\DataType\BooleanData' email: label: 'Email' class: '\Drupal\Core\TypedData\Plugin\DataType\Email' integer: label: 'Integer' class: '\Drupal\Core\TypedData\Plugin\DataType\IntegerData' float: label: 'Float' class: '\Drupal\Core\TypedData\Plugin\DataType\FloatData' string: label: 'String' class: '\Drupal\Core\TypedData\Plugin\DataType\StringData' uri: label: 'Uri' class: '\Drupal\Core\TypedData\Plugin\DataType\Uri'
Como puede verse, la mayoría de tipos básicos se asignan a sus equivalentes en la API TypedData. Este ejemplo también muestra lo fácil que es definir sus propios tipos. Simplemente defina una clase que coincida con el tipo. Los dos últimos (más complejos) tipos de datos están basados en implementaciones de clases:
# Container data types for lists with known and unknown keys. mapping: label: Mapping class: '\Drupal\Core\Config\Schema\Mapping' definition_class: '\Drupal\Core\TypedData\MapDataDefinition' sequence: label: Sequence class: '\Drupal\Core\Config\Schema\Sequence' definition_class: '\Drupal\Core\TypedData\ListDataDefinition'
Mapping, como se muestra arriba, es un tipo de lista de pares clave-valor ("array asociativo" o "hash") donde cada elemento puede tener un tipo diferente, mientras que Sequence es una lista indexada simple ("array indexado") donde los elementos son del mismo tipo o basados en el mismo nombre de tipo dinámico (ver más abajo) y las claves no importan. En otras palabras, la diferencia clave entre secuencias y mappings es que en secuencias no conoce los nombres o cantidad de claves, mientras que en mappings todas las claves están explícitamente definidas. Las secuencias pueden usar claves de tipo string.
Todos los demás tipos definidos en los esquemas de configuración (incluido el propio system.maintenance) simplemente heredan de otros tipos, por ejemplo, "label", "path", "text", "date_format" y "color_hex" están definidos como strings. La distinción de estos tipos puede ayudar a las herramientas de análisis de esquemas a identificar tipos de texto para diversos propósitos.
# Human readable string that must be plain text and editable with a text field. label: type: string label: 'Label' translatable: true # Internal Drupal path path: type: string label: 'Path' # Human readable string that can contain multiple lines of text or HTML. text: type: string label: 'Text' translatable: true # PHP Date format string that is translatable. date_format: type: string label: 'Date format' translatable: true translation context: 'PHP date format' # HTML color value. color_hex: type: string label: 'Color'
Tenga en cuenta que los tipos label, text y date_format también están marcados como traducibles. Esto significa que el módulo de traducción de la interfaz principal identifica elementos con estos tipos y los traduce basándose en traducciones proporcionadas por la comunidad o el administrador en la base de datos, creando archivos de sobreescritura de traducción. Observe que las cadenas traducibles pueden recibir contexto con la clave translation context, como se muestra aquí para formatos de fecha. Así, cadenas como "Y" reciben el contexto adicional "PHP date format", para que los traductores sepan que no es la abreviatura de "Sí", sino el formato de fecha PHP durante años.
De manera similar, puede definir tipos complejos reutilizables encima de tipos básicos usando el formato descrito arriba para el modo de mantenimiento:
# Mail text with subject and body parts. mail: type: mapping label: 'Mail' mapping: subject: type: label label: 'Subject' body: type: text label: 'Body'
Esto le da un tipo reutilizable "mail" para configuraciones de texto de correo electrónico donde el asunto y el cuerpo están en un mapping. Esto es igual a definir un esquema para una clave de configuración, pero ha elegido un nombre que no es una clave de configuración existente, por lo que no entrará en conflicto con otras definiciones de esquema. Basándose en esta definición, "mail" puede usarse como tipo en otro lugar (como en el esquema de opciones de correo electrónico del módulo usuario en user.schema.yml):
user.mail: type: config_object label: 'Email settings' mapping: cancel_confirm: type: mail label: 'Account cancellation confirmation' password_reset: type: mail label: 'Password recovery' [....]
Finalmente, otros dos tipos complejos importantes para definir archivos de configuración también están definidos aquí en core.data_types.schema.yml:
config_object: type: mapping mapping: langcode: type: string label: 'Language code' _core: type: _core_config_info config_entity: type: mapping mapping: uuid: type: string label: 'UUID' langcode: type: string label: 'Language code' status: type: boolean label: 'Status' dependencies: type: config_dependencies label: 'Dependencies' third_party_settings: type: sequence label: 'Third party settings' sequence: type: '[%parent.%parent.%type].third_party.[%key]' _core: type: _core_config_info
Referencias dinámicas a tipos
Como se mostró arriba, incluso los tipos simples son esencialmente referencias, y los tipos complejos como "mail" se usan comúnmente para referirse a tipos complejos. A veces el tipo de valor no es estático y puede depender de datos, por ejemplo, para estilos de imagen con varios efectos, o vistas con varios plugins. Puede referirse a claves dentro de los datos como parte del nombre del tipo para referirse a tipos dinámicos.
Los valores variables en los tipos deben estar encerrados en [] (corchetes), y los valores variables pueden combinarse con componentes conocidos. Hay tres tipos de referencias:
1. Referencia a clave de elemento: por ejemplo, type:book.[%key], donde %key se reemplaza con la clave del elemento.
2. Referencia a clave anidada: por ejemplo, type: 'views.field.[Table]-[field]', donde el tipo se calcula en base a los valores de claves table y field dentro de la estructura anidada.
3. Referencia a clave padre: por ejemplo, type: 'views.display.[%parent.display_plugin]', donde la clave display_plugin del padre se usa para determinar el tipo del elemento.
Hay muchos ejemplos de esto en estilos de imagen y vistas que usan ampliamente plugins. Un ejemplo del estilo de imagen con base en core/modules/image/config/install/image.style.medium.yml que tiene esta estructura YAML:
name: medium label: 'Medium (220x220)' effects: bddf0d06-42f9-4c75-a700-a33cafa25ea0: id: image_scale data: width: 220 height: 220 upscale: true weight: 0 uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0 langcode: en
Aquí la estructura clave de datos depende del tipo de efecto indicado en la propiedad id del efecto. Por lo tanto, el tipo usado depende de los datos y no puede establecerse estáticamente. Los estilos de imagen configurados de manera diferente usarán diferentes efectos. Por eso necesitamos incluir una referencia a la especificación del tipo. La sección correspondiente del esquema en image.schema.yml se ve así:
image.style.*: type: config_entity label: 'Image style' mapping: name: type: string label: type: label label: 'Label' effects: type: sequence sequence: type: mapping mapping: id: type: string data: type: image.effect.[%parent.id] weight: type: integer uuid: type: string
Esto define metadatos para todos los estilos de imagen (image.style.*) como mapping de nombres, etiquetas y claves de efectos. Luego los efectos son una secuencia (puede haber cualquier número de efectos), donde cada elemento en la lista es un mapping con información detallada del efecto. La clave de la secuencia es el uuid del efecto, pero esto no importa ya que las secuencias no se preocupan por sus claves, por lo que sólo definimos el tipo de los elementos. Los valores comunes para los efectos son id, data y weight, pero el contenido de data depende del valor id del padre (en el ejemplo anterior "image_scale" es el nombre del efecto usado). Así, cuando este esquema se aplica a los datos, image.effect.image_scale es un tipo de referencia válido.
Note que también puede encontrarse una definición un poco diferente para sequence donde el tipo de elementos en la secuencia está definido como una lista estricta de elementos. Este formato está obsoleto y será eliminado en Drupal 9:
deprecated.sequence.definition.format: type: sequence sequence: - type: string label: 'DO NOT COPY, THIS IS DEPRECATED'
Nombres de sus archivos de esquema
Sus archivos de esquema deben tener un nombre globalmente único. Si el nombre de su archivo de esquema coincide con el nombre de otro módulo, su archivo o el otro no será encontrado, lo que puede llevar a errores difíciles de diagnosticar. Por eso es recomendable agregar un prefijo del nombre de su módulo a los archivos de esquema.
Estilo de código para archivos de esquema
Siga simplemente el estilo .yml usado en otras partes del núcleo de Drupal. Consulte los ejemplos mencionados anteriormente para el enfoque a seguir. Puntos clave:
- Incluya un comentario de nivel superior explicando qué contiene el archivo. Si sólo tiene un archivo de esquema para todo su módulo, un comentario como "# Schema for the configuration files of the Contact module." es suficiente.
- Evite comentarios que no añadan claridad adicional. Por ejemplo, "Configuración de comentarios" sobre la sección que define el esquema para comment.settings es redundante. En cualquier caso, los elementos de esquema deben tener etiquetas que los describan bien. Añada comentarios sólo si es necesario.
- No use comillas dobles para cadenas, use comillas simples.
- Use comillas simples para los valores de las etiquetas, incluso si es una sola palabra, para consistencia.
- Nunca use comillas para definiciones y tipos de claves (en Drupal, los nombres y tipos de clave son cadenas por definición y no deben contener espacios).
- En Drupal, los valores enteros en los archivos YAML de configuración se convierten en cadenas y por tanto están entre comillas simples.
- Agregue etiquetas al menos a los valores que necesiten traducción (y también a los contenedores que los envuelven). Consulte la herramienta Config Inspector explicada más abajo en la sección de depuración para verificar si puede generar un formulario útil a partir de su esquema.
- Cuidado con el nivel de indentación. No es una recomendación de estilo, sino un requisito de YAML para obtener la estructura correcta del esquema.
Nota: El estilo usual de archivos de datos de configuración .yml requiere usar comillas simples sólo cuando hay más de una palabra, porque la serialización .yml hace esto por defecto, lo que facilita cambiar la configuración. Vea los estándares de codificación de archivos de configuración. Sin embargo, las recomendaciones de esquema arriba son diferentes porque los archivos de esquema siempre se escriben a mano y es mejor usar siempre comillas en los valores de etiqueta para consistencia.
API PHP #
Puede obtener la configuración con metadatos usando la función \Drupal::service('config.typed') (por ejemplo, para el modo mantenimiento del sistema):
$definition = \Drupal::service('config.typed')->getDefinition('system.maintenance');
La estructura del array será la siguiente:
array(5) { ["label"]=> string(16) "Maintenance mode" ["class"]=> string(34) "\Drupal\Core\Config\Schema\Mapping" ["definition_class"]=> string(40) "\Drupal\Core\TypedData\MapDataDefinition" ["mapping"]=> array(2) { ["langcode"]=> array(2) { ["type"]=> string(6) "string" ["label"]=> string(13) "Language code" } ["message"]=> array(2) { ["type"]=> string(4) "text" ["label"]=> string(43) "Message to display when in maintenance mode" } } ["type"]=> string(18) "system.maintenance" }
Un ejemplo más complejo para obtener datos tipados relacionados con los datos del primer efecto del estilo de imagen medio, como se mencionó arriba en la sección de referencias parentales:
// Get typed configuration from under the the image.style.medium config // key's effects children. Take the uuid key shown above in the example config // file (corresponding to the first effect in the style) and the data children's elements. $effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects.bddf0d06-42f9-4c75-a700-a33cafa25ea0.data')->getDataDefinition();
Esto resultará en el tipo image.effect.image_scale descrito arriba, y devolverá una definición de mapa, por ejemplo:
object(Drupal\Core\TypedData\MapDataDefinition)#1061 (3) { ["mainPropertyName":protected]=> NULL ["propertyDefinitions":protected]=> NULL ["definition":protected]=> array(5) { ["type"]=> string(24) "image.effect.image_scale" ["label"]=> string(11) "Image scale" ["class"]=> string(34) "\Drupal\Core\Config\Schema\Mapping" ["definition_class"]=> string(40) "\Drupal\Core\TypedData\MapDataDefinition" ["mapping"]=> array(3) { ["width"]=> array(2) { ["type"]=> string(7) "integer" ["label"]=> string(5) "Width" } ["height"]=> array(2) { ["type"]=> string(7) "integer" ["label"]=> string(6) "Height" } ["upscale"]=> array(2) { ["type"]=> string(7) "boolean" ["label"]=> string(7) "Upscale" } } } }
Se puede usar completamente la API TypedData para elementos, por ejemplo:
// Get the effects sequence object from the medium image style. $effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects'); // $effects represents the sequence keyed by uuids as shown above in the parent reference // example. Use the getValue() TypedData method to retrieve the value. $first_uuid = key($effects->getValue()); // Take the data keys for this first effect. $data = $effects->get($first_uuid)->get('data'); // Examine values and types for width. $data->get('width')->getPluginId(); // will return 'integer' $data->get('width')->getValue(); // will return 220
Consulte más ejemplos de código para navegar la configuración basada en esquemas y generar formularios basados en esquemas en https://drupal.org/project/config_inspector
Depuración de su esquema
El módulo Config Inspector ofrece una interfaz de usuario para comparar esquemas con datos y ver cómo la generación y transformación de formularios (cuando está disponible) funciona con el esquema aplicado a los datos. Esto puede usarse para encontrar problemas en el esquema, vea https://drupal.org/node/1910624#comment-7088154 para consejos sobre cómo usarlo para depurar esquemas.
El módulo principal de traducción de configuración crea una UI real encima de los esquemas y permite a las personas traducir la configuración. Puede usar este módulo para depurar si su configuración se traduce correctamente y si las traducciones aparecen en los lugares correctos (en el front-end) y no aparecen en otros (por ejemplo, en el back-end, donde la gente puede editar su configuración original).
Aún más referencias
Revise #1866610: Introducir formato de esquema inspirado en Kwalify para configuración y #1648930: Introducir esquema de configuración y usarlo para traducción, además de cientos de comentarios donde se discutieron varios enfoques y soluciones (y muchos problemas colaterales) antes de llegar a este formato. (Además de #1914366: Mover todos los archivos de esquema de configuración a un subdirectorio schema, para saber por qué están donde están). Vea también #1905152: Integrar esquema de configuración para que la configuración exportada sea traducible para información sobre cómo el sistema de esquemas se integra con el módulo de localización. #1952394: Módulo principal de traducción de configuración es donde se añadió el módulo de traducción en el núcleo.
#1602106: Documentar archivos de configuración por defecto fue el comienzo de documentar las reglas comunes de configuración yml.
Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.