Helpers en Laravel: ¿Un archivo global o una clase? Mi veredicto

Muchos desarrolladores caen en la trampa del archivo 'helpers.php' en Laravel. Te explico por qué es una mala idea en casi todos los casos y cuál es la solución que yo prefiero.

Cuando necesitamos crear funciones de ayuda en Laravel para no repetir código, especialmente para formatear texto o cosas así, la primera idea que se le viene a la cabeza a mucha gente, y de hecho es lo que más votos tiene en Stack Overflow, es crear un archivo helpers.php y cargarlo con Composer. Y sí, funciona. Laravel mismo usa algo parecido para algunas funciones globales. Pero, para ser directo, en mi experiencia, esta es una solución que raramente recomiendo y que, en la mayoría de los proyectos modernos, te traerá más dolores de cabeza que beneficios.

Imagínate que necesitas una función para formatear fechas de una manera específica, o para sanear una cadena de texto antes de mostrarla en una vista. Lo “obvio” sería hacer algo como esto, siguiendo la respuesta más votada:

// app/helpers.php

if (!function_exists('formatDateForView')) {
    function formatDateForView($date) {
        // Lógica de formateo
        return \Carbon\Carbon::parse($date)->format('d/m/Y H:i');
    }
}

if (!function_exists('sanitizeText')) {
    function sanitizeText($text) {
        // Lógica de saneamiento
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
}

Luego, tendrías que ir a tu composer.json y añadir esto:

"autoload": {
    "psr-4": {
        "App\\": "app/"
    },
    "files": [
        "app/helpers.php"
    ]
},

Y finalmente, ejecutar composer dump-autoload. Con eso, ya tienes tus funciones disponibles globalmente en cualquier parte de tu aplicación: en controladores, modelos, y por supuesto, en tus vistas de Blade.

Pero aquí viene el problema que a mí me frustra: esto, aunque simple al principio, va en contra de varias buenas prácticas en PHP y Laravel. Estás llenando el namespace global con funciones que pueden colisionar con otras librerías o con funciones que podrías definir más adelante. No hay una forma clara de saber de dónde vienen estas funciones con solo ver su nombre. Además, son difíciles de testear de forma aislada, porque no tienen un contexto de clase ni dependencias que puedas inyectar fácilmente.

La Solución que Prefiero: Clases de Ayuda con Métodos Estáticos

En lugar de esparcir funciones globales, lo que yo hago y lo que recomiendo siempre es usar clases con métodos estáticos. Esto está mucho más alineado con el paradigma de programación orientada a objetos de Laravel y de PHP moderno (PSR-4). La segunda respuesta de Stack Overflow, la que tiene menos votos pero es, en mi opinión, la correcta, apunta precisamente a esto.

Así es como yo lo haría para el ejemplo de formateo de texto:

// app/Helpers/TextFormatter.php

namespace App\Helpers;

use Carbon\Carbon;

class TextFormatter
{
    public static function formatDateForView($date):
    {
        return Carbon::parse($date)->format('d/m/Y H:i');
    }

    public static function sanitizeText(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }

    public static function truncate(string $text, int $limit = 100, string $end = '...'): string
    {
        if (strlen($text) > $limit) {
            return substr($text, 0, $limit - strlen($end)) . $end;
        }
        return $text;
    }
}

Con esto, no necesitas tocar tu composer.json para el autoload de archivos, porque Composer ya se encarga de las clases con el estándar PSR-4. Luego, ¿cómo las usas?

  • En controladores o servicios: Simplemente importas la clase con use App\Helpers\TextFormatter; y luego la llamas: TextFormatter::formatDateForView($myDate);. Es claro, explícito y fácil de rastrear.
  • En vistas de Blade: Puedes usarla directamente con su Fully Qualified Class Name (FQCN) si no te importa la verbosidad: {{ \App\Helpers\TextFormatter::formatDateForView($post->created_at) }}. O, si quieres que sea más conciso en Blade, puedes crear un alias en config/app.php como sugiere la respuesta de SO:
// config/app.php
'aliases' => [
    // ... otras aliases
    'TextFormatter' => App\Helpers\TextFormatter::class,
],

Después de ejecutar composer dump-autoload (solo si modificaste el config/app.php y para que el alias se cargue), puedes usarla en Blade así: {{ TextFormatter::formatDateForView($post->created_at) }}. A mí, en particular, no me convence mucho la idea de crear aliases globales para estas clases, prefiero importarlas explícitamente cuando las necesito, pero reconozco que para Blade puede ser más cómodo para algunos.

¿Por qué esta aproximación es superior?

  1. Claridad y organización: Todas tus funciones relacionadas con, por ejemplo, el formateo de texto, están en un solo lugar, dentro de una clase que describe su propósito. Es mucho más fácil encontrar y entender dónde está cada cosa.

  2. Evita colisiones: Al estar dentro de un namespace (App\Helpers), no hay riesgo de que el nombre de tu función colisione con otra función global o de alguna librería externa.

  3. Testabilidad: Puedes testear tus métodos estáticos de forma aislada, lo cual es un plus enorme para la calidad del código. Es mucho más difícil (y a veces imposible) testear funciones globales sin efectos secundarios o sin un framework de mocking.

  4. Autocompletado: Tu IDE (VS Code, PHPStorm) puede autocompletar y navegar a la definición de la clase y sus métodos fácilmente.

Con esto, también evitas lo que a mí me parece sobreingeniería en otros contextos, donde se crean servicios o inyecciones de dependencia para funciones que son puramente utilitarias y no tienen estado. Un método estático en una clase de ayuda es una solución simple y efectiva para estas situaciones.

Ojo, que en Laravel existen otras formas de manejar esto. Si tu “helper” necesita interactuar con el contenedor de servicios de Laravel (por ejemplo, acceder a la base de datos o usar una dependencia inyectada), entonces probablemente estás hablando de un servicio, no de un simple helper, y deberías usar la inyección de dependencias. Para lógica específica de Blade, las Directivas Blade personalizadas (Blade::directive()) son el camino correcto, como para crear un @markdown($text) propio.

Pero para las funciones simples, puramente utilitarias, que no necesitan acceso al estado de la aplicación ni dependencias complejas, la clase con métodos estáticos es la solución que yo elijo.

Mi recomendación concreta

En este caso específico de la pregunta de Stack Overflow, que habla de funciones de formateo de texto para vistas, mi recomendación es clara: usa una clase con métodos estáticos dentro de un namespace adecuado (por ejemplo, App\Helpers\TextFormatter). Olvídate del archivo helpers.php global a menos que estés creando algo extremadamente básico y verdaderamente global, como un dd() personalizado, que de hecho, Laravel ya te da. Para cualquier cosa que tenga un mínimo de lógica de negocio o formato específico, una clase es siempre la mejor práctica. Es más limpio, más mantenible y más profesional.

Jorge RequenaDeveloper full-stack · Chile