Cargando…

Esto está tardando más de lo esperado.

Estándares de código

Services

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

Todos los estándares

Propósito

Los servicios existen para comunicarse con sistemas externos (Stripe, APIs, integraciones de terceros). Cuando existe un servicio, no debe contener lógica de dominio que pertenezca a un modelo ni gestionar directamente el estado del modelo.
Los servicios de pago pueden llamar métodos SDK del framework que realizan flujos de facturación externos, como los builders de suscripciones de Laravel Cashier, los métodos del ciclo de vida de suscripciones (newSubscription(), checkout(), cancelNow(), addPriceAndInvoice(), removePrice(), swap()), y los métodos de formas de pago (addPaymentMethod(), updateDefaultPaymentMethod(), deletePaymentMethod()). Trátalos como operaciones externas de Stripe, no como escrituras ordinarias del modelo de dominio, siempre que el servicio no esté asignando manualmente atributos de Eloquent no relacionados ni llamando métodos genéricos de persistencia para estado de la aplicación.

Los servicios son de solo lectura con respecto a los modelos

Cuando un servicio recibe un modelo, no debe llamar a save(), update(), updateQuietly(), ni asignar atributos en ese modelo. Los servicios leen modelos y comunican resultados mediante valores de retorno; el observer o quien hace la llamada persiste los datos devueltos cuando se necesita persistencia.
No hagas fallar llamadas del ciclo de vida o de métodos de pago de Cashier o SDK únicamente porque el SDK también sincroniza registros locales de facturación. Haz fallar escrituras genéricas de Eloquent (save, update, asignación de atributos) sobre estado de dominio propiedad de la app; permite operaciones de SDK de sistemas externos dentro de servicios cuyo propósito sea esa integración.

// Correct — service returns the Stripe ID, observer assigns it before INSERT
public function creating(BillingPrice $price): void
{
    $price->stripe_price_id = app(StripeSyncService::class)->syncPrice($price);
}

// Incorrect — service writes directly to the model
public function syncPrice(BillingPrice $price)
{
    $result = $this->stripe->prices->create([...]);
    $price->updateQuietly(['stripe_price_id' => $result->id]); // wrong
}

Esta regla trata sobre persistencia, no sobre lo que un servicio puede leer. Un servicio puede leer la request, la sesión o una cookie cuando esa es su entrada. CountryDetectionService lee request()->ip() y session(), llama a una API externa de geolocalización y devuelve un código de país que UserObserver asigna a $user->country_code. Eso cumple la regla: lee libremente y nunca escribe en un modelo.

Quién persiste cuando no hay un observer

El patrón anterior asume que un modelo ya se está guardando: el observer completa un campo antes de que termine la escritura. Algunas operaciones no tienen ningún guardado en curso: la operación es la creación o actualización de un registro. Un middleware que resuelve la sesión de tracking actual es un ejemplo: no se está guardando nada, así que no hay ningún observer al que pasarle un valor.
En ese caso, el registro pertenece al modelo, mediante un método con nombre específico. Eloquent ya se encarga de create(), firstOrCreate(), y updateOrCreate(); una versión con nombre de dominio de eso es el lugar natural. El servicio se limita a producir los datos que necesita el modelo.

// Service: reads the request, normalizes the input, returns data
$attributes = static::attributesFromRequest($request);

// Model: owns the record
$trackingSession = TrackingSession::resolveFor($attributes, $storedId, $cookieId);

No lleves la persistencia al llamador —un middleware o un controller— para cumplir la regla. Eso dispersa más las escrituras que dejarlas dentro del servicio.

Los servicios no deben contener lógica de dominio del modelo

Si un método solo consulta u opera sobre modelos Eloquent sin interactuar con un sistema externo, no pertenece a un servicio: pertenece al modelo o a un método del modelo.
La normalización de entrada no es lógica de dominio del modelo. Los helpers que limpian una cadena de user agent o construyen un identificador de clic a partir de un parámetro de consulta describen la propia entrada del servicio, no el modelo, y se quedan en el servicio.
Un método del modelo recibe primitivos — un array de atributos, un id, un value object — nunca un request del framework. Si un método del modelo necesita Request, el límite está en el lugar equivocado.

// Correct — model owns the logic
public function cancelSubscriptions()
{
    $this->subscriptions()->each(function ($subscription) {
        $subscription->cancel();
    });
}

// Incorrect — no external system involved, should be a model method
public function cancelSubscriptionsByPrice(BillingPrice $price)
{
    Subscription::whereHas('items', ...)->get()->each->cancel(); // wrong place
}

Patrón de llamada

Cuando los componentes de Livewire o los controladores necesitan comportamiento de un servicio de sistema externo, llaman a un método del modelo o a un flujo propiedad del observer en lugar de llamar al servicio directamente.
Patrón: Livewire / Controller → Model Method → Observer → Service

// Correct — Livewire calls the model method
$price->archive();

// Incorrect — Livewire calls the service directly
app(StripeSyncService::class)->archivePrice($price);

Los servicios comunican resultados mediante valores de retorno

Cuando un servicio crea un recurso en un sistema externo y se debe persistir un ID, el servicio devuelve ese ID. Quien llama al servicio (observer o model) lo persiste.
Si no es necesario persistir ningún ID o campo propiedad de quien llama, el servicio puede devolver una pequeña carga útil de resultado/DTO como success, message, action o url para describir el resultado del flujo de trabajo externo. No exijas devolver un ID para redirecciones de checkout, toggles, cancelaciones u otras operaciones en las que el SDK gestiona la persistencia internamente.

// Correct
public function syncPlan(BillingPlan $plan)
{
    $product = $this->stripe->products->create([...]);
    return $product->id;
}

// Caller persists the returned ID
private function assignStripeProductId(BillingPlan $plan): void
{
    $stripeProductId = app(StripeSyncService::class)->syncPlan($plan);
    if ($stripeProductId) {
        $plan->stripe_product_id = $stripeProductId;
    }
}
Weblabor Logo Desarrollado por Weblabor