Grunden til, at jeg siger, at transaktioner ikke hører hjemme i modellaget er grundlæggende denne:
Modeller kan kalde metoder i andre modeller.
Hvis en model forsøger at starte en transaktion, men den ikke har kendskab til, om dens kaldende allerede har startet en transaktion, så skal modellen betinget start en transaktion, som vist i kodeeksemplet i @Bubbas svar . Modellens metoder skal acceptere et flag, så den, der ringer, kan fortælle den, om det er tilladt at starte sin egen transaktion eller ej. Eller også skal modellen have mulighed for at forespørge opkalderens "i en transaktion"-tilstand.
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
Hvad hvis den, der ringer, ikke er et objekt? I PHP kan det være en statisk metode eller blot ikke-objektorienteret kode. Dette bliver meget rodet og fører til en masse gentagen kode i modeller.
Det er også et eksempel på Kontrolkobling , hvilket anses for dårligt, fordi den, der ringer, skal vide noget om det opkaldte objekts interne funktion. For eksempel nogle af metoderne i din model kan have en $transactional parameter, men andre metoder har muligvis ikke denne parameter. Hvordan skal den, der ringer, vide, hvornår parameteren har betydning?
// 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);
Den anden løsning, jeg har set foreslået (eller endda implementeret i nogle rammer som Propel), er at lave beginTransaction()
og commit()
no-ops, når DBAL ved, at den allerede er i en transaktion. Men dette kan føre til anomalier, hvis din model forsøger at forpligte sig og finder ud af, at den ikke rigtig forpligter sig. Eller forsøger at rulle tilbage og får den anmodning ignoreret. Jeg har skrevet om disse uregelmæssigheder før.
Det kompromis, jeg har foreslået, er, at modeller ikke kender til transaktioner . Modellen ved ikke, om dens anmodning til setPrivacy()
er noget, det skal begå med det samme, eller er det en del af et større billede, en mere kompleks serie af ændringer, der involverer flere modeller og kun bør være forpligtet, hvis alle disse ændringer lykkes. Det er meningen med transaktioner.
Så hvis modeller ikke ved, om de kan eller bør begynde og foretage deres egen transaktion, hvem gør så? GRASP inkluderer et Controller-mønster som er en ikke-UI-klasse for en use case, og den er tildelt ansvaret for at oprette og kontrollere alle dele for at opnå denne use case. Kontrollører kender til transaktioner fordi det er det sted, hvor alle oplysninger er tilgængelige om, hvorvidt den komplette use case er kompleks og kræver flere ændringer, der skal udføres i modeller, inden for en transaktion (eller måske inden for flere transaktioner).
Eksemplet jeg har skrevet om før, det er at starte en transaktion i beforeAction()
metode for en MVC-controller og begå den i afterAction()
metode, er en forenkling . Controlleren bør være fri til at starte og udføre så mange transaktioner, som det logisk nok kræver for at fuldføre den aktuelle handling. Eller nogle gange kunne controlleren afstå fra eksplicit transaktionskontrol og tillade modellerne at autocommitere hver ændring.
Men pointen er, at informationen om, hvilke(n) transaktion(er) der er nødvendige, er noget, som modellerne ikke ved -- de skal fortælles (i form af en $transactional parameter) ellers forespørge det fra deres opkalder, som ville alligevel skulle uddelegere spørgsmålet helt op til controllerens handling.
Du kan også oprette et Servicelag af klasser, som hver især ved, hvordan man udfører sådanne komplekse use cases, og om alle ændringerne skal inkluderes i en enkelt transaktion. På den måde undgår du en masse gentagen kode. Men det er ikke almindeligt, at PHP-apps inkluderer et særskilt Service Layer; controllerens handling er normalt sammenfaldende med et servicelag.