acme_tracking:AC, registra la lógica de validación en el Factory de validación. Así, desde los form requests de la aplicación consumidora se puede usar con la misma notación que las reglas integradas.
Esta página se basa en la implementación oficial de Laravel Framework v13.40.0. Complementa el ejemplo básico de Reglas de validación personalizadas y trata el registro, la traducción y la vida útil del estado cuando se mantienen como API pública de un paquete.
Distinguirlas de los objetos de regla
Los objetosValidationRule que presenta la documentación oficial también se pueden usar en paquetes. Si poder especificar la regla como cadena no es en sí un requisito, considera un objeto que reciba argumentos tipados en su constructor.
Aunque el método tenga el mismo nombre
validate(), el contrato de llamada es distinto. Si pasas directamente una clase que implementa ValidationRule como cadena de clase a Factory::extend(), el tipo del tercer argumento y el valor de retorno no coinciden.
Separar registro, creación y validación
Elextend() de la fachada Validator es una llamada a Illuminate\Validation\Factory. Es un objeto distinto de la instancia de Illuminate\Validation\Validator que valida la entrada.
El diagrama muestra el recorrido cuando se ejecuta una regla de cadena. Las omisiones por entrada vacía o por nullable, entre otras, se explican más adelante.
El Factory se registra como singleton con la clave validator, pero el Validator que devuelve make() en la implementación estándar se crea nuevo cada vez. Los registros posteriores en el Factory no actualizan el array de extensiones de los Validator ya creados.
Registrar una regla propia del paquete
Como ejemplo, validamos un código de seguimiento ficticio compuesto por un prefijo en mayúsculas, un guion y 8 dígitos. Es un ejemplo de formato comoAC-00000001, no la especificación de numeración de una empresa de transporte real.
Se supone que el espacio de nombres del paquete Acme\Courier\ corresponde a src/ mediante PSR-4 de Composer y que se carga un service provider normal no diferido. Para la carga de proveedores, consulta Autodescubrimiento de paquetes.
Separar la lógica de validación en una clase
Prepara ensrc/Validation/TrackingCode.php un manejador para la regla de cadena. Esta clase no implementa la interfaz ValidationRule.
acme_tracking:AC, el array de parámetros es ['AC']. La regla también comprueba el tipo del valor para no provocar un error de tipo aunque se use de forma aislada.
Un prefijo o un número de parámetros no válidos se tratan como una excepción, ya que son un error de configuración en la definición de la regla y no un error de entrada del usuario. Cuando la regla se omite por un valor vacío, esta comprobación de configuración tampoco se ejecuta, por lo que también se necesitan pruebas con valores no vacíos válidos.
Registrar después de resolver el Factory
Desdeboot() de src/CourierServiceProvider.php, registra la regla y el proceso de reemplazo del mensaje.
callAfterResolving() espera a que se cree el Factory si aún no se ha resuelto, y registra en el acto si ya está resuelto. Los detalles del hook se explican en Hooks posteriores a la resolución de servicios en paquetes.
La cadena de clase TrackingCode::class.'@validate' resuelve la clase en el contenedor durante la validación y llama al método. Aunque solo indiques el nombre de la clase sin @validate, el nombre de método predeterminado es validate. Si hay dependencias en el constructor, vincúlalas para que el contenedor pueda resolverlas.
Este ejemplo no registra el manejador como singleton ni tiene propiedades que guarden la entrada. Los datos de la validación se manejan a partir de los argumentos, y el diseño evita incorporar estado específico de la petición en los closures que conserva el Factory.
Usarla desde la aplicación consumidora
bail detiene las reglas siguientes cuando falla una regla anterior de ese atributo. Se reparten los papeles: la obligatoriedad con required y el formato con acme_tracking.
Unifica los nombres públicos en snake case propio, como acme_tracking. Si se registra el mismo nombre en el mismo tipo de extensión, el registro posterior sobrescribe al anterior. Además, cuando existe un método integrado como validateRequired(), se llama directamente a ese método, por lo que no se puede reemplazar una validación integrada con una extensión del mismo nombre.
Conservar los mensajes de error del usuario
Los mensajes de las reglas de cadena normales se seleccionan aproximadamente en el siguiente orden de prioridad. Las ramas especiales, como las de las reglas de tamaño, quedan fuera de este análisis.
El replacer personalizado se llama después de que Laravel reemplace
:attribute y otros placeholders. Los argumentos del replacer son cinco: el mensaje seleccionado, el nombre del atributo, el nombre de la regla, el array de parámetros y el Validator. En el ejemplo anterior se omite el quinto argumento, que no se usa.
Para definir un texto predeterminado en japonés en la aplicación, añade acme_tracking al array que devuelve el lang/ja/validation.php existente. El ejemplo siguiente muestra la entrada que se añade; no elimina los demás mensajes integrados.
__('...') durante el registro y lo guardas como fallback, en el Factory queda la cadena del locale de ese momento. En aplicaciones que cambian de locale, usa la traducción en el momento de generar el error.
Cuando se usan traducciones con espacio de nombres del paquete
Si llamas aloadTranslationsFrom(__DIR__.'/../lang', 'acme-courier') en boot(), podrás referenciar lang/ja/validation.php y otros archivos del paquete con acme-courier::validation.tracking_code.
Sin embargo, pasar una clave de traducción como tercer argumento de extend() no hace que se traduzca automáticamente. El Factory la conserva como una simple cadena fallback. Si sustituyes extend() y replacer() dentro del hook anterior por el siguiente registro, podrás obtener la traducción del paquete solo cuando se seleccione el fallback.
tracking_code en el validation.php de cada locale del paquete. Para el texto en japonés puedes usar el mismo que la entrada acme_tracking anterior.
Como aquí se obtiene una nueva cadena de traducción dentro del replacer, también se pasa :attribute explícitamente a la traducción. El reemplazo estándar de Laravel no se vuelve a aplicar a este valor de retorno. Si se selecciona un mensaje propio del usuario, se mantiene ese texto. Para la sobrescritura de las traducciones distribuidas, consulta Sobrescritura y actualización de traducciones de paquetes.
Comprender las condiciones de ejecución de Implicit y nullable
extendImplicit() registra una regla que se convierte en candidata a validación incluso con atributos ausentes o cadenas vacías. No significa “hacer que falle siempre” ni “ignorar todas las demás condiciones de omisión”.
La tabla compara las condiciones de ejecución de cada regla. Los casos en los que el procesamiento se detiene antes por
bail, la exclusión de atributos, stopOnFirstFailure(), etc., son aparte. En las entradas HTTP, un middleware puede convertir las cadenas vacías en null, así que prueba por separado la cadena vacía y null.
Para la regla de formato de este ejemplo basta con combinar el extend() normal con required. Cambiar a Implicit una regla ya publicada puede hacer que fallen entradas que antes no se validaban, así que trátalo como un cambio de compatibilidad.
Resolver referencias dentro de arrays con reglas Dependent
Para reglas que reciben como parámetro el nombre de otro campo, puedes usarextendDependent(). Al validar items.*.confirmed_tracking, el parámetro items.*.tracking se convierte en el de la misma fila, como items.0.tracking. Con el extend() normal no se realiza este reemplazo.
A continuación se muestra una regla de coincidencia para comprobar este mecanismo. Si solo necesitas validar una coincidencia simple, en una aplicación real puedes usar la regla integrada same. Tómalo como un ejemplo para aplicarlo a validaciones entre campos propias del paquete.
Añádela al Factory recibido desde el hook.
getData() del cuarto argumento devuelve los datos sujetos a validación, no solo los datos validados. Define las reglas necesarias tanto en el campo que referencia como en el referenciado.
Además, hacerla Dependent no la convierte en Implicit. La obligatoriedad frente a valores ausentes o vacíos se especifica por separado. La implementación aplica a los parámetros el escape de puntos y el reemplazo de *, así que evita APIs que mezclen cadenas arbitrarias que no sean nombres de campo.
Probar desde el registro hasta la validación en la aplicación
En una aplicación Laravel 13 que cargue el paquete y su service provider, creatests/Feature/TrackingRuleTest.php. Además de las pruebas unitarias de la clase, se verifican el registro y el procesamiento de mensajes a través del Factory real.
Qué comprobar para el mantenimiento a largo plazo
- Documenta como API pública el nombre de la regla, el significado y el número de parámetros, la obligatoriedad y los tipos de valor aceptados.
- Verifica parámetros no válidos, valores ausentes, cadenas vacías y
null, y las combinaciones denullable,sometimesybail. - Comprueba
messages()del usuario, los nombres de atributos mostrados, el cambio de locale y la sobrescritura de las traducciones del paquete. - En las reglas Dependent, comprueba con varias filas y arrays anidados que no se referencien valores de otras filas.
- Comprueba el registro en Factory sin resolver y ya resuelto, y no esperes que se aplique retroactivamente a los Validator ya creados.
- Evita conflictos de nombres con otros paquetes y no mezcles el mismo nombre en registros normales, Implicit y Dependent.
- Para aplicar cambios en workers de larga duración, reinicia los procesos y no dejes entradas ni información de usuario en manejadores compartidos.
Fuentes primarias consultadas
- Documentación oficial de Laravel: reglas personalizadas y reglas Implicit
- Documentación oficial de Laravel: service providers y traducciones de paquetes
- Factory: make, extend, extendImplicit, extendDependent y replacer
- Validator: condiciones de ejecución, registro de extensiones y resolución de clases
- ValidationRuleParser: análisis de reglas de cadena
- FormatsMessages: prioridad y reemplazo de mensajes
- ValidationServiceProvider: registro singleton de validator
- ServiceProvider: callAfterResolving
- Pruebas oficiales: transferencia de extensiones y fallback desde el Factory
- Pruebas oficiales: reglas de cadena, de clase, Implicit y Dependent