Cargando…

Esto está tardando más de lo esperado.

Estándares de código

Livewire

Estos son los estándares que aplica el analizador. Cada regla dice qué revisa y por qué existe.

Todos los estándares

Orden de Estructura Obligatorio

Mantén los componentes de Livewire en este orden, usando solo las partes que existan:

  1. sentencias use / traits
  2. Propiedades
  3. mount() cuando esté presente
  4. Métodos de reglas de validación (rules(), validationAttributes(), validationMessages())
  5. Otros métodos, incluidos los hooks del ciclo de vida de Livewire como updated*()
  6. render() cuando esté presente

Si no hay mount(), los métodos de reglas de validación pueden ir inmediatamente después de las propiedades. Un método de validación antes de updated*() u otro método que no sea de validación está correctamente ordenado.
Los comentarios de sección son opcionales. Cuando se usen, deben usar el formato de varias líneas:

/*
 * Rules
 */

Reporta esta regla solo para problemas objetivos de orden, no por la ausencia de comentarios de sección opcionales ni por componentes compactos con pocos métodos.

Autorización en componentes Livewire

Los componentes Livewire que leen o modifican recursos protegidos deben aplicar un límite de autorización del lado del servidor.
Usa, siempre que sea posible, una sola protección a nivel de componente en mount() para la pantalla o recurso. No repitas la misma autorización en cada acción cuando mount() ya protege el componente. Añade autorización a nivel de acción solo cuando esa acción interactúe con un recurso o capacidad diferente.
Prefiere la autorización respaldada por políticas para los recursos protegidos. Cuando exista un sujeto de política útil, llama a la política mediante $this->authorize(...), $this->user()->can(...) o el helper de autorización equivalente del proyecto en lugar de crear helpers de acceso específicos del componente que dupliquen la lógica de la política.
Cuando mount() recibe un modelo Eloquent que es un sujeto de política útil, autoriza ese modelo directamente en mount(). Un helper específico del componente que contenga abort_unless(...), abort_if(...), comprobaciones de permisos, comprobaciones de tenant o comprobaciones de funcionalidades no sustituye la política del modelo. Los guards estrictos pueden aplicar esas condiciones adicionales no relacionadas con políticas, pero el componente todavía debe llamar a la política para el modelo protegido.
No exijas $this->authorize(...) solo porque un componente Livewire esté autenticado o se renderice dentro del área admin/app. La autorización es obligatoria cuando el componente lee o modifica un recurso protegido con un sujeto de política útil, como una instancia de modelo, una clase de modelo, un recurso de tenant/team/company o un recurso admin respaldado por permisos.
Las páginas generales autenticadas como dashboards, pantallas de inicio, pantallas de cuenta/perfil del usuario actual, configuraciones a las que cualquier usuario autenticado puede acceder y páginas agregadas sin un recurso protegido específico no necesitan una política ni $this->authorize(...), a menos que el proyecto ya tenga una política o permiso explícito para esa pantalla exacta o que el componente exponga datos protegidos entre usuarios o entre tenants.
No crees una nueva política, permiso o helper específico del componente solo para cumplir esta regla. Si no existe un sujeto de política útil, deja el componente sin autorización por política o usa la ruta, el middleware o el auth guard existentes como límite. Usa abort_unless(...) solo para una condición real no basada en políticas que deba aplicarse del lado del servidor.
Una protección estricta como abort_unless(...) es aceptable cuando no existe un sujeto de política útil. Los flujos públicos de autenticación y los formularios públicos de captación/marketing no requieren autorización por política, a menos que accedan a recursos solo para usuarios autenticados.

// Correct — model resource has a policy subject
public function mount(BillingPlan $billing_plan)
{
    $this->authorize('view', $billing_plan);
    $this->billingPlan = $billing_plan;
}

// Incorrecto — un guard estricto específico del componente sustituye la política del modelo
public function mount(BillingPlan $billing_plan)
{
    $this->authorizeBillingPlanAccess($billing_plan);
}

private function authorizeBillingPlanAccess(BillingPlan $billing_plan): void
{
    abort_unless(auth()->user()->hasPermissionTo('retrieve billing plans'), 403);
}

// Correct — current-user profile screen; route/auth middleware is enough unless the project has a specific policy
public function mount()
{
    $this->user = auth()->user();
}

// Incorrect — invents authorization for a general dashboard with no protected policy subject
public function mount()
{
    $this->authorize('viewDashboard');
}

// Incorrect — protected component has no server-side guard
public function deletePlan()
{
    $this->billingPlan->delete();
}

render() Debe Mantenerse Limpio

En los componentes Livewire, render() solo debe devolver la vista con una configuración de layout opcional. Construye los datos para la vista en métodos #[Computed] en lugar de usar variables locales dentro de render().
Los componentes de vista Blade bajo app/View/Components o las clases que extienden Illuminate\View\Component quedan fuera de esta regla de Livewire.

use Livewire\Attributes\Computed;

// Correct
#[Computed]
public function currencyOptions(): array
{
    return BillingPlan::currencyOptions();
}

public function render()
{
    return view('livewire.admin.plan-edition')
        ->layout('layouts.app', ['title' => __('Edit Plan')]);
}

// Incorrect
public function render()
{
    $currencyOptions = BillingPlan::currencyOptions();
    return view('livewire.admin.plan-edition', compact('currencyOptions'));
}

En Blade, referencia las propiedades computadas como $this->propertyName. Al aplicar esta regla moviendo un valor de los datos de render() a un método #[Computed], actualiza la vista Blade emparejada en el mismo cambio. Una referencia de Blade a la variable simple anterior no es válida porque el valor ya no se pasa desde render().

{{-- Correct --}}
:options="$this->currencyOptions"

{{-- Incorrect --}}
:options="$currencyOptions"

Cuando un método computado devuelve modelos Eloquent, pasa los modelos a Blade en lugar de transformarlos en arrays planos.

Evita #[Computed] para Comprobaciones Internas de un Solo Uso

No crees un método #[Computed] para un booleano o valor derivado que solo se use una vez y únicamente dentro de la lógica del componente (no en la vista Blade). Exprésalo en línea en su lugar.

// Correct — inline, used in one place
if ($this->object?->hasSubscribers() ?? false) {

// Incorrect — unnecessary computed property for single internal use
#[Computed]
public function planHasSubscribers(): bool
{
    return $this->object?->hasSubscribers() ?? false;
}

if ($this->planHasSubscribers) {

Reserva #[Computed] para valores consumidos en la vista Blade o reutilizados en varios métodos.

Declaraciones de propiedades

  • No declares tipos en propiedades simples (que no sean objetos)
  • Las propiedades tipadas solo son necesarias para objetos de modelo Eloquent
  • Mantén las propiedades tipadas de Eloquent/objeto como declaraciones independientes.
  • Nunca inicialices propiedades de tipo string con una cadena vacía ('')
  • Se permite una cadena por defecto significativa y no vacía cuando representa intencionalmente un estado de UI o del flujo de trabajo. No marques esta regla como incumplida solo porque una propiedad simple tenga una cadena por defecto no vacía.
  • Se permiten valores por defecto null en propiedades públicas sin tipo para estados opcionales de UI/flujo de trabajo. No marques esta regla como incumplida para public $property = null;; márcala como incumplida solo cuando una propiedad simple tenga un tipo declarado explícitamente o cuando se use una cadena vacía por defecto.
// Correct
public Plan $object;
public $prices = [], $limits = [], $features = [];
public $newFeature, $otherVariable;
public $afterDeployCommands = 'composer install';
public $editingCommentPath = null;

// Incorrect
public string $object_id = '';
public array $prices = [];
public array $limits = [];
public string $newFeature = '';

Lo que un componente puede llamar

Un componente llama a un método del modelo, y el modelo llega al servicio o al job. Llamar a cualquiera de los dos directamente desde el componente se salta el modelo, que es donde se nombra la acción y donde el siguiente llamador esperaría encontrarla.
Este es el lado del componente de "Modelos de dominio enriquecidos" en el estándar de Laravel Models, expresado aquí porque es el componente el que tiene que cumplirlo.

// Correct
$user->toggleAddOn($addOn);

// Incorrect
AddOnService::toggle(auth()->user(), $addOn);
app(StripeSyncService::class)->archivePrice($price);
MigrateUsersToPlanJob::dispatch($variation);

El caso más claro es un servicio que recibe un modelo como argumento: el modelo estuvo en alcance todo el tiempo. Despachar un evento de Livewire no entra en esta regla: $this->dispatch('saved') es una llamada sobre el componente, no sobre un job.

Vinculación directa de modelos

Esta regla se aplica solo cuando un componente persiste directamente un modelo Eloquent con save(), create(), update(), fill(), o una llamada equivalente de persistencia directa del modelo.
Informa esta regla solo cuando todo lo siguiente sea cierto:

  1. El componente guarda directamente un modelo.
  2. Varias propiedades públicas reflejan atributos de ese mismo modelo.
  3. Esas propiedades se pasan manualmente a la llamada de persistencia del modelo.
  4. El componente todavía no está usando una propiedad pública del modelo como la superficie principal del formulario.

Si el código no está persistiendo directamente un modelo, esta regla no se aplica.

public User $user;

<input type="text" wire:model="user.name">

public function save(): void
{
    $this->user->save();
}

Feedback de WireUI

Usa las APIs de feedback de WireUI de WireUiActions solo para los mensajes que se muestran directamente al usuario. Se permiten tanto $this->notification() como $this->dialog() / $this->dialog()->show([...]).
Esta regla aplica cuando el código crea texto de feedback orientado al usuario o estado de UI para una alerta/mensaje. No aplica a eventos de Livewire como $this->dispatch(...), eventos del navegador, eventos de refresco del padre, eventos de cierre de modal u otras comunicaciones internas.
Usa notificaciones para feedback pasivo. Usa diálogos para confirmaciones, bloqueos, acciones destructivas o mensajes que el usuario deba reconocer. No sustituyas ninguno de los dos por flags de alerta personalizados, datos flash crudos de sesión (p. ej. session()->flash(...)) ni estado ad hoc en la vista cuando el código realmente está mostrando un mensaje al usuario.
La única excepción es una acción que también redirige o reemplaza la página en la misma llamada: los propios métodos de WireUI solo entregan el mensaje dentro de esa misma respuesta y lo pierden una vez que la página se reemplaza. En ese caso, usa el helper flash() de laravel-front, que conserva el mensaje para la página siguiente, manteniendo la misma presentación que habría usado la acción. Se muestra como notificación por defecto; encadena ->dialog() cuando la acción de otro modo habría usado un diálogo. No recurras a flash() cuando la acción no redirige.

$this->notification()->success(
    title: __('Saved'),
    description: __('The record has been saved successfully.')
);

$this->dialog()->show([
    'icon' => 'warning',
    'title' => __('No items'),
    'description' => __('Add at least one item before continuing.'),
]);

// Correct — the action redirects in the same call, so WireUI's own method
// would lose the message; flash() carries it to the destination page.
flash(__('Plan created'), __('The plan has been saved successfully.'))->success();
return redirect()->route('admin.front.billing-plans');

// Correct — same idea, but this action would otherwise have used a dialog.
flash(__('Success!'), __('Your password has been changed'))->success()->dialog();
return redirect(config('app.home_route'));

// Incorrect — nothing redirects here, so WireUI's own method already works.
flash(__('Plan updated'), __('The plan has been saved successfully.'))->success();

Los modales deben usar wire-elements/modal

Usa wire-elements/modal (LivewireUI\Modal\ModalComponent) para pantallas modales. No uses marcado WireUI <x-modal>.
Informa esta regla en Blade solo cuando aparezca <x-modal>. Infórmala en PHP solo cuando un componente implemente claramente un modal incrustado de estilo WireUI en lugar de usar un ModalComponent dedicado.
No informes esta regla para estado normal de formularios Livewire, wire:model, propiedades públicas, botones, closeModal(), save() o confirm() por sí solos.

use LivewireUI\Modal\ModalComponent;

class CreatePost extends ModalComponent
{
    public function save(): void
    {
        $this->validate();
        $this->closeModal();
    }
}

Reglas de validación

Mantén la validación alineada con el contrato persistido cuando el campo se corresponda claramente con datos almacenados:

  • Los campos almacenados no anulables necesitan required.
  • Los campos numéricos almacenados necesitan límites min / max razonables.
  • Las cadenas almacenadas necesitan validación de longitud.

No inventes restricciones de base de datos a partir de un fragmento. Si el código revisado no muestra que un campo se persiste, trátalo como estado del componente.
Para los nombres de validación visibles para el usuario, todo campo validado que pueda aparecer en un error de validación debe tener un nombre de atributo legible. Defínelo en el componente con validationAttributes() / attributes(), o verifica que ya exista en el array attributes del archivo lang/{locale}/validation.php correspondiente.
Usa messages() solo cuando el texto del mensaje sea realmente personalizado, no cuando sea simplemente el mensaje por defecto de Laravel con un nombre de campo traducido. No añadas mensajes personalizados solo para compensar la falta de nombres de atributos.
Al añadir o cambiar reglas de validación, valida que los nombres de campo mostrados al usuario se resuelvan correctamente en la configuración regional activa. No asumas que claves anidadas como object.name, items.*.amount o billing_plan.prices.*.name se mostrarán correctamente a menos que estén cubiertas por atributos del componente o atributos del idioma.

// Correct
protected function validationAttributes()
{
    return [
        'task.name' => __('name'),
    ];
}

// Incorrect
protected function messages()
{
    return [
        'task.name.required' => __('The name field is required.'),
    ];
}

Clases Rule personalizadas

Crea una clase Rule dedicada cuando la lógica de validación personalizada se reutiliza entre componentes o formularios. No dupliques en línea la validación personalizada reutilizable.
Esta regla no se aplica a $this->validate([...]), rules(), validateOnly(), cadenas de reglas estándar de Laravel ni pequeños bloques de validación condicional.
Reporta esta regla cuando aparezca en línea lógica de validación personalizada repetida, como reglas closure duplicadas, uso repetido de Validator::after(...), bloques manuales repetidos de $fail(...) o bloques repetidos a nivel de campo de ValidationException::withMessages(...).

// app/Rules/LimitValidator.php
class LimitValidator implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (collect($value)->pluck('limit_key')->duplicates()->isNotEmpty()) {
            $fail(__('Each limit key can only be used once.'));
        }
    }
}

// In rules()
'limits' => ['array', new LimitValidator],

La validación en el backend es obligatoria

Los formularios de Livewire deben aplicar la validación en el backend (rules(), $this->validate(), form objects o equivalente). Los atributos de validación HTML como required, min, max o pattern son solo sugerencias opcionales de UI y no deben ser la única validación.
Se prefiere no añadir nuevos atributos de validación HTML cuando la validación en el backend ya cubre la regla, porque duplican la validación del lado del servidor y pueden omitirse del lado del cliente. Sin embargo, no elimines atributos de validación HTML existentes solo por limpieza cuando ya están presentes y no rompen el flujo.

{{-- Correct — backend validation owns the rule --}}
<x-input wire:model="object.name" :label="__('Name')" />

{{-- Also acceptable when backend validation exists and the attribute is only a UI hint --}}
<x-input wire:model="object.name" :label="__('Name')" required />

El componente es donde se comprueba: un componente que escribe un registro sin declarar rules(), un atributo #[Validate] o una propiedad $rules, y sin llamar a validate(), está guardando lo que le haya llegado.

Weblabor Logo Desarrollado por Weblabor