Il motivo per cui dico che le transazioni non appartengono al livello del modello è fondamentalmente questo:
I modelli possono chiamare metodi in altri modelli.
Se un modello tenta di avviare una transazione, ma non sa se il chiamante ha già avviato una transazione, il modello deve condizionatamente avviare una transazione, come mostrato nell'esempio di codice in Risposta di @Bubba . I metodi del modello devono accettare un flag in modo che il chiamante possa dirgli se è consentito avviare la propria transazione o meno. Oppure il modello deve avere la capacità di interrogare lo stato "in transazione" del chiamante.
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
Cosa succede se il chiamante non è un oggetto? In PHP, potrebbe essere un metodo statico o semplicemente codice non orientato agli oggetti. Questo diventa molto disordinato e porta a un sacco di codice ripetuto nei modelli.
È anche un esempio di Attacco di controllo , che è considerato negativo perché il chiamante deve sapere qualcosa sul funzionamento interno dell'oggetto chiamato. Ad esempio, alcuni dei metodi del tuo Modello potrebbe avere un parametro $transazionale, ma altri metodi potrebbero non avere quel parametro. Come dovrebbe il chiamante sapere quando il parametro è importante?
// I need to override method's attempt to commit
$video->setPrivacy($privacy, false);
// But I have no idea if this method might attempt to commit
$video->setFormat($format);
L'altra soluzione che ho visto suggerita (o addirittura implementata in alcuni framework come Propel) è fare beginTransaction()
e commit()
no-ops quando il DBAL sa che è già in una transazione. Ma questo può portare ad anomalie se il tuo modello tenta di eseguire il commit e scopre che non lo fa davvero. Oppure tenta di eseguire il rollback e la richiesta viene ignorata. Ho già scritto di queste anomalie.
Il compromesso che ho suggerito è che I modelli non conoscono le transazioni . Il modello non sa se la sua richiesta a setPrivacy()
è qualcosa che dovrebbe impegnare immediatamente o fa parte di un quadro più ampio, una serie più complessa di modifiche che coinvolgono più Modelli e dovrebbero solo essere impegnato se tutte queste modifiche hanno esito positivo. Questo è il punto delle transazioni.
Quindi, se i modelli non sanno se possono o devono iniziare e impegnare la propria transazione, allora chi lo sa? GRASP include un modello di controller che è una classe non dell'interfaccia utente per un caso d'uso e le viene assegnata la responsabilità di creare e controllare tutti i pezzi per realizzare quel caso d'uso. I titolari sono a conoscenza delle transazioni perché questo è il luogo in cui tutte le informazioni sono accessibili se il caso d'uso completo è complesso e richiede l'esecuzione di più modifiche nei modelli, all'interno di una transazione (o forse all'interno di più transazioni).
L'esempio di cui ho scritto prima, ovvero avviare una transazione in beforeAction()
metodo di un controller MVC e esegui il commit nel afterAction()
metodo, è una semplificazione . Il Titolare dovrebbe essere libero di avviare e commettere tutte le transazioni necessarie logicamente per completare l'azione in corso. O talvolta il Titolare potrebbe astenersi dal controllo esplicito delle transazioni e consentire ai Modelli di autocommettere ogni modifica.
Ma il punto è che le informazioni su quali transazioni sono necessarie sono qualcosa che i modelli non conoscono:devono essere informati (sotto forma di un parametro $transazionale) oppure interrogarle dal loro chiamante, che dovrebbe comunque delegare la questione fino all'azione del Titolare.
Puoi anche creare un Livello di servizio di classi che sanno come eseguire casi d'uso così complessi e se racchiudere tutte le modifiche in un'unica transazione. In questo modo eviti molto codice ripetuto. Ma non è comune che le app PHP includano un livello di servizio distinto; l'azione del Titolare coincide solitamente con un Livello di Servizio.