Cargando…

Esto está tardando más de lo esperado.

Estándares de código

Laravel Models

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

Todos los estándares

Sin $fillable

Los modelos de Eloquent usan $guarded, nunca $fillable.

Orden de secciones del modelo

Mantén las secciones del modelo en este orden relativo cuando esas secciones existan: Funciones estáticas → Funciones → Scopes → Relaciones → Atributos.
Las propiedades se declaran al principio de la clase, antes de cualquier método (incluidas las Funciones estáticas).
Cuando un modelo contiene métodos de más de una sección, los comentarios de sección deben estar presentes (la división). Los comentarios son opcionales solo cuando el modelo tiene una única sección. Reporta los métodos fuera de orden y las divisiones faltantes.
La redacción de un comentario de sección es libre: lo que se comprueba es que haya un comentario encima del primer método de cada sección, no lo que dice, así que Factory helpers divide igual que Static Functions. La redacción libre sigue siendo un nombre, no una frase: como máximo cuatro palabras, sin punto final, que es lo que Comments Earn Their Place reconoce como una división. Un docblock nunca divide, porque documenta el método de debajo. Escribe la división en la forma multilínea /* */ que usan el resto de estos estándares. Un comentario con esa forma que nombre Static Functions, Functions, Scopes, Relationships o Attributes también se interpreta como una promesa sobre lo que le sigue, y se reporta un método de otra sección escrito debajo de él.

Scopes significativos

Un scope local debe encapsular una restricción de dominio reutilizable. No crees un scope cuyo único cuerpo reenvía un argumento a una sola llamada a where(); escribe ese where() directamente en el punto de llamada.
La regla automatizada solo reporta un scope con exactamente una sentencia return en la forma return $query->where('columna', $valor) o return $query->where('columna', '=', $valor). No reporta scopes que normalizan un valor, leen una propiedad de un objeto de dominio, combinan condiciones o agregan otro comportamiento.

// Incorrecto
public function scopeForInterval($query, string $interval)
{
    return $query->where('interval', $interval);
}

// Correcto
return BillingPriceVariation::where('interval', $interval)->get();

Atributos

Los accessors y mutators usan la forma moderna Attribute::get() / Attribute::set(). Los métodos heredados getFooAttribute() / setFooAttribute() están obsoletos en favor de esta: conviértelos cuando se toque el archivo. Los accessors y mutators pertenecen a la sección Attributes.

// Correct — modern attribute form
protected function fullName(): Attribute
{
    return Attribute::get(function () {
        return $this->first_name . ' ' . $this->last_name;
    });
}

// Incorrect — legacy accessor method
public function getFullNameAttribute()
{
    return $this->first_name . ' ' . $this->last_name;
}

Nomenclatura y organización de modelos

  • Los nombres de las relaciones deben describir exactamente lo que devuelven. Añade calificadores solo cuando haya varias relaciones del mismo tipo.
  • Los métodos estáticos de consulta/búsqueda deben nombrar el sujeto devuelto. Los filtros pueden aparecer después del sujeto, por ejemplo getPricesByInterval.
  • Dentro del modelo, un comentario es una etiqueta de cuatro palabras o menos — una división de sección, o un marcador de paso dentro de un cuerpo — o un bloque que contiene solo una anotación que una herramienta lee. Un comentario que describe lo que hace un método es reportado por Comments Earn Their Place; cuando la razón de dominio deba quedar registrada, escríbela en el documento docs/{project} que sea responsable del tema.

Modelos de Dominio Enriquecidos

Cuando una acción de dominio pertenece a un modelo, expónla mediante un método del modelo. Los componentes de Livewire y los controladores llaman al método del modelo, no directamente al service/job.
Patrón: Livewire / Controller → Model Method → Service / Job

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

// Incorrect
AddOnService::toggle(auth()->user(), $addOn);

Extrae un trait cuando el mismo comportamiento del modelo se reutiliza en varios modelos, o cuando un modelo contiene dos o más métodos sobre una funcionalidad que el modelo tiene pero no es. Nombra el trait según la funcionalidad — HasReferrals para la funcionalidad de referidos — y úsalo en el modelo.
Una funcionalidad es separable cuando también puede pertenecer a otro modelo: User tiene referidos, así que los referidos se convierten en HasReferrals. No extraigas un trait cuando los métodos forman parte del propio dominio del modelo: Subscription es la suscripción, así que sus métodos se quedan en el modelo; un trait HasSubscriptions movería la lógica propia del modelo a otro lugar sin reutilización.
Un único método relacionado es vocabulario y se queda en el modelo.

Qué pertenece en un Model

Un método o accessor de un Model describe qué es el registro, o qué puede la aplicación preguntarle: $user->markLoggedIn(), User::emailExists($email), $plan->can_be_deleted.
Las utilidades genéricas no pertenecen ahí, aunque hoy el Model sea su único consumidor. Dar formato a una cadena, normalizar la entrada del usuario, parsear una fecha o construir un identificador para un servicio de terceros describe un valor, no el registro.
La prueba es esta: ¿el método seguiría teniendo sentido si el Model no existiera? normalizePhoneNumber() sí — trata sobre números de teléfono, no sobre usuarios, y nadie le pediría a un usuario que normalice un teléfono. phoneExists() no — es una pregunta sobre usuarios almacenados, así que pertenece al Model.

// Correct — vocabulary of the record
User::phoneExists($phone);

// Incorrect — a phone formatting utility parked in the model
User::normalizePhoneNumber($phone);

Dónde debe ir en su lugar una utilidad así se explica en "Las utilidades compartidas pertenecen en Helpers" en el estándar de PHP.

Fechas, zonas horarias y casts

  • Almacena las fechas en UTC.
  • Define en $casts los atributos de fecha, booleanos, arrays/JSON y enums que pertenezcan al modelo.
  • No sustituyas los casts por accessors ni transformaciones manuales.
  • Reporta casts faltantes solo para atributos que claramente pertenezcan al modelo revisado.
  • Reporta el manejo manual de fechas solo cuando el código analice, formatee o convierta la zona horaria de una fecha propia del modelo en lugar de apoyarse en el cast.
// Wrong
'expires_at' => Carbon::parse($value)->format('Y-m-d'),

// Correct
protected $casts = [
    'expires_at' => 'datetime',
];

Enums

Usa enums para atributos persistidos con múltiples estados.

  • Los nombres de las clases enum no deben incluir Enum, por ejemplo AddonStatus, no AddonStatusEnum.
  • Los nombres de los casos enum usan PascalCase.
  • Esta regla automatizada no valida los valores de los casos enum; se permiten valores de tipo string, integer y expression.

Esta regla se aplica al estado respaldado por enum, no a booleanos simples, comprobaciones de null ni etiquetas solo de presentación.

// Correct
enum AddonStatus: string
{
    case Active = 'ACTIVE';
    case SuspendedAccount = 'account-suspended';
}

// Incorrect
enum AddonStatusEnum: string
{
    case active = 'active';
}

Orden de secciones en enums

Los archivos de enums siguen el mismo orden de secciones que los models, menos las secciones que un enum no puede tener: Static Functions → Functions → Attributes. Cuando un archivo de enum contiene métodos de más de una sección, los comentarios de sección deben estar presentes, con la misma libertad de redacción que en los modelos. Esto aplica a las declaraciones de enum y a los archivos de soporte de enum, como un trait IsEnum que viva en app/Enums/ (o en src/Enums/ de un paquete).

Colecciones pendientes — Patrón de Mutator + Observer

Cuando un formulario edita una colección relacionada mediante arrays y esa colección debe sincronizarse al guardar, usa un mutator del modelo junto con un observer. No pongas el bucle de sincronización en Livewire ni en un controller.
Usa este patrón solo para flujos de escritura que inserten, actualicen o eliminen filas relacionadas a partir de arrays de estado entrantes.
Modelo: declara el estado pendiente, captúralo con un mutator y expón datos listos para el formulario con un accessor.

public array $pendingItems = [];

public function setItemsDataAttribute(array $items)
{
    $this->pendingItems = $items;
}

public function getItemsDataAttribute()
{
    return $this->items->map(fn ($item) => [
        'id' => $item->id,
        'key' => $item->key,
        'value' => $item->value,
    ])->values()->all();
}

Observer: en saved, lee $pendingItems y sincroniza eliminaciones, actualizaciones e inserciones.
Llamador: asigna el array y llama a save().

Reutilización de la lógica de dominio (fuente única de verdad)

Cada cálculo vive en el modelo que posee los datos. Los demás modelos y servicios reutilizan ese método.

// Correct
$total = $project->tasks->sum('cost');

// Incorrect — duplicates the formula
$total = $project->tasks->sum(fn ($task) => $task->hours * $task->rate);

Accessor o método

Un método de un modelo que no recibe argumentos y solo calcula un valor a partir del registro es un atributo, y en el punto de llamada se lee como tal. Decláralo como accessor.

// Correcto
protected function fullName(): Attribute
{
    return Attribute::get(function () {
        return $this->first_name . ' ' . $this->last_name;
    });
}

// Incorrecto
public function getFullName()
{
    return $this->first_name . ' ' . $this->last_name;
}

Cuatro cosas mantienen a un método como método, y ninguna es cuestión de estilo:

  • Recibe argumentos, así que responde una pregunta sobre otra cosa.
  • Persiste o cambia estado, así que llamarlo dos veces no es lo mismo que llamarlo una.
  • Devuelve una consulta que el llamador sigue moldeando, así que el llamador decide cuándo corre.
  • Cumple un contrato — una clase padre, una interfaz o un predicado del framework que debe seguir siendo método.

Las relaciones y los scopes no son ni una cosa ni la otra, y se dejan en paz.

Las relaciones son consultas puras

Un método de relación devuelve una query que el caller sigue moldeando; no debe persistir nada. No llames a firstOrCreate(), create(), updateOrCreate() ni save() dentro de un método de relación. Cuando un registro deba existir antes de poder leer la relación, materialízalo en un método explícito con nombre (p. ej. ensureBillingAccount()) que los callers invoquen cuando estén por escribir, y deja la relación como una query pura. Cuando un paquete no está, devuelve una relación vacía neutra en lugar de escribir un registro fallback.

Weblabor Logo Desarrollado por Weblabor