php artisan boost:add-skill wm/wm-package
Save this content to: .cursor/rules/modelli.md
# Trappole: modelli e persistenza
Si applica quando tocchi modelli, trait, observer o enum del package.
- `HasPackageFactory` risolve la factory con `get_called_class()`: un modello figlio in un altro
namespace (es. `App\Models\UgcPoi`) cerca la factory nel **proprio** namespace e `::factory()`
non funziona. Va sovrascritto `newFactory()` nel figlio.
- `identifier` **non è in `$fillable`** di `TaxonomyWhere`: passarlo nell'array del costruttore lo
scarta in silenzio e l'observer lo rigenera. Va assegnato dopo il costruttore, prima di
`save()` (oc:8469).
- `$model->name` su un modello con Spatie `HasTranslations` restituisce **sempre** una stringa per
la locale corrente, mai l'array: per tutte le traduzioni serve `getTranslations('name')`
(oc:8241).
- `getTranslation('name', $locale)` può restituire stringa **vuota**, non `null`: serve una cascade
`it → en → locale` con `empty()`, non un `??` (oc:7648).
- Mai `env()` diretto in un modello, sempre `config()`: con il config caching in produzione
`env()` torna `null` (oc:8251).
- `env('X', 22)` restituisce una **stringa** appena la chiave è valorizzata in `.env` — il default
int vale solo a chiave assente. Dove serve un `int`, il cast è esplicito (oc:8251).
- `env()` è risolto una volta all'avvio: nessun fallback pre-calcolato in un file di config se i
test sovrascrivono a runtime con `config([...])` (oc:8464).
- `EcPoi` non ha il trait `Searchable`, a differenza di `EcTrack`: per reindicizzarlo si dispatcha
`BuildAppPoisGeojsonJob`, non `->searchable()` (oc:8043).
- `scopeByWhereProperty()` fa early return **senza condizioni** quando `taxonomy_where` è vuoto:
chi lo usa da solo ottiene tutti i modelli dell'app, non zero (oc:8140).
- Un consumer non può registrare un observer **prima** di quello del package: serve
`Event::listen('eloquent.deleting: ...')` (oc:8180).
- Assegnare una **stringa** a un attributo Spatie `HasTranslations` la salva sotto la lingua di
`app.locale`: con `app.locale = en` un nome italiano diventa `en`. Si passa sempre l'array per
lingua (`['it' => $nome]`) (oc:8588).
- Assegnare `[]` a un attributo tradotto salva nella colonna il testo `"[]"`, non `"{}"`: chi
legge la colonna in SQL deve trattare `NULL`, `''`, `'[]'` e `'{}'` come vuoti (oc:8588).
- Non mettere un oggetto in `name` (o in un altro campo già dichiarato testo) in
`EcTrack::toSearchableArray()`: gli indici esistenti lo rifiutano e, con `scout.queue` a `false`, il
salvataggio della traccia in Nova va in errore fino a `scout:import` — per un dato nuovo si
aggiunge un campo, come `name_translations` (oc:8681).
- «Reindicizza Scout» (`ReindexAppScoutAction`) riscrive i documenti nell'indice esistente con
`searchable()` e non applica un mapping cambiato: per cambiare il tipo di un campo serve
`scout:import`, che crea un indice nuovo e sposta l'alias (oc:8681).
Si applica quando tocchi modelli, trait, observer o enum del package.
HasPackageFactory risolve la factory con get_called_class(): un modello figlio in un altro
namespace (es. App\Models\UgcPoi) cerca la factory nel proprio namespace e ::factory()
non funziona. Va sovrascritto newFactory() nel figlio.identifier non è in $fillable di TaxonomyWhere: passarlo nell'array del costruttore lo
scarta in silenzio e l'observer lo rigenera. Va assegnato dopo il costruttore, prima di
save() (oc:8469).$model->name su un modello con Spatie HasTranslations restituisce sempre una stringa per
la locale corrente, mai l'array: per tutte le traduzioni serve getTranslations('name')
(oc:8241).getTranslation('name', $locale) può restituire stringa vuota, non null: serve una cascade
it → en → locale con empty(), non un ?? (oc:7648).env() diretto in un modello, sempre config(): con il config caching in produzione
env() torna null (oc:8251).env('X', 22) restituisce una stringa appena la chiave è valorizzata in .env — il default
int vale solo a chiave assente. Dove serve un int, il cast è esplicito (oc:8251).env() è risolto una volta all'avvio: nessun fallback pre-calcolato in un file di config se i
test sovrascrivono a runtime con config([...]) (oc:8464).EcPoi non ha il trait Searchable, a differenza di EcTrack: per reindicizzarlo si dispatcha
BuildAppPoisGeojsonJob, non ->searchable() (oc:8043).scopeByWhereProperty() fa early return senza condizioni quando taxonomy_where è vuoto:
chi lo usa da solo ottiene tutti i modelli dell'app, non zero (oc:8140).Event::listen('eloquent.deleting: ...') (oc:8180).HasTranslations la salva sotto la lingua di
app.locale: con app.locale = en un nome italiano diventa en. Si passa sempre l'array per
lingua (['it' => $nome]) (oc:8588).[] a un attributo tradotto salva nella colonna il testo "[]", non "{}": chi
legge la colonna in SQL deve trattare NULL, '', '[]' e '{}' come vuoti (oc:8588).name (o in un altro campo già dichiarato testo) in
EcTrack::toSearchableArray(): gli indici esistenti lo rifiutano e, con scout.queue a false, il
salvataggio della traccia in Nova va in errore fino a scout:import — per un dato nuovo si
aggiunge un campo, come name_translations (oc:8681).ReindexAppScoutAction) riscrive i documenti nell'indice esistente con
searchable() e non applica un mapping cambiato: per cambiare il tipo di un campo serve
scout:import, che crea un indice nuovo e sposta l'alias (oc:8681).How can I help you explore Laravel packages today?