Sin $fillable
Los modelos de Eloquent usan $guarded, nunca $fillable.
Cargando…
Esto está tardando más de lo esperado.
Estos son los estándares que aplica el analizador. Cada regla dice qué revisa y por qué existe.
$fillableLos modelos de Eloquent usan $guarded, nunca $fillable.
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.
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();
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;
}
getPricesByInterval.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.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.
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.
$casts los atributos de fecha, booleanos, arrays/JSON y enums que pertenezcan al modelo.// Wrong
'expires_at' => Carbon::parse($value)->format('Y-m-d'),
// Correct
protected $casts = [
'expires_at' => 'datetime',
];
Usa enums para atributos persistidos con múltiples estados.
Enum, por ejemplo AddonStatus, no AddonStatusEnum.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';
}
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).
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().
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);
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:
Las relaciones y los scopes no son ni una cosa ni la otra, y se dejan en paz.
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.