Rammen har ingen mulighed for at vide, om du startede en transaktion. Du kan endda bruge $db->query('START TRANSACTION')
som frameworket ikke kender til, fordi det ikke parser SQL-sætninger, du udfører.
Pointen er, at det er et programansvar at spore, om du har startet en transaktion eller ej. Det er ikke noget, rammerne kan.
Jeg ved, at nogle rammer forsøger at gøre det, og laver cockamamie-ting som at tælle, hvor mange gange du har påbegyndt en transaktion, og kun løse det, når du har foretaget commit eller rollback et matchende antal gange. Men dette er fuldstændig falsk, fordi ingen af dine funktioner kan vide, om commit eller rollback faktisk vil gøre det, eller om de er i et andet lag af nesting.
(Kan du fortælle, at jeg har haft denne diskussion et par gange? :-)
Opdatering 1: Propel er et PHP-databaseadgangsbibliotek, der understøtter konceptet om den "indre transaktion", der ikke forpligter sig, når du fortæller det. Begyndelse af en transaktion øger kun en tæller, og commit/rollback formindsker tælleren. Nedenfor er et uddrag fra en postlistetråd, hvor jeg beskriver et par scenarier, hvor det mislykkes.
Opdatering 2: Doktrin DBAL har også denne funktion. De kalder det Transaction Nesting.
Kan du lide det eller ej, transaktioner er "globale", og de adlyder ikke objektorienteret indkapsling.
Problemscenarie #1
Jeg kalder commit()
, er mine ændringer begået? Hvis jeg løber inde i en "indre transaktion" er de ikke. Koden, der styrer den ydre transaktion, kunne vælge at rulle tilbage, og mine ændringer ville blive kasseret uden min viden eller kontrol.
For eksempel:
- Model A:start transaktion
- Model A:udfør nogle ændringer
- Model B:start transaktion (støjsvag no-op)
- Model B:udfør nogle ændringer
- Model B:commit (lydløs no-op)
- Model A:rollback (kasserer både model A-ændringer og model B-ændringer)
- Model B:WTF!? Hvad skete der med mine ændringer?
Problemscenarie #2
En indre transaktion ruller tilbage, den kan kassere legitime ændringer foretaget af en ydre transaktion. Når kontrollen returneres til den ydre kode, mener den, at dens transaktion stadig er aktiv og tilgængelig til at blive begået. Med din patch kunne de kalde commit()
, og da transDepth nu er 0, ville den stille $transDepth
til -1 og returner sand, efter ikke at have begået noget.
Problemscenarie #3
Hvis jeg kalder commit()
eller rollback()
når der ikke er nogen aktiv transaktion, sætter den $transDepth
til -1. Den næste beginTransaction()
øger niveauet til 0, hvilket betyder, at transaktionen hverken kan rulles tilbage eller forpligtes. Efterfølgende opkald til commit()
vil blot dekrementere transaktionen til -1 eller mere, og du vil aldrig være i stand til at forpligte dig, før du gør en anden overflødig beginTransaction()
for at øge niveauet igen.
Dybest set er det en dødsdømt idé at forsøge at administrere transaktioner i applikationslogik uden at lade databasen udføre bogføringen. Hvis du har et krav om, at to modeller skal bruge eksplicit transaktionskontrol i én applikationsanmodning, skal du åbne to DB-forbindelser, en for hver model. Så kan hver model have sin egen aktive transaktion, som kan forpligtes eller rulles tilbage uafhængigt af hinanden.