Powodem, dla którego mówię, że transakcje nie należą do warstwy modelu jest w zasadzie:
Modele mogą wywoływać metody w innych modelach.
Jeśli model próbuje rozpocząć transakcję, ale nie wie, czy osoba wywołująca już rozpoczęła transakcję, musi warunkowo rozpocznij transakcję, jak pokazano w przykładzie kodu w @Odpowiedź Bubby . Metody modelu muszą akceptować flagę, aby osoba wywołująca mogła powiedzieć, czy może rozpocząć własną transakcję, czy nie. W przeciwnym razie model musi mieć możliwość odpytywania swojego rozmówcy o stan „w transakcji”.
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
A jeśli dzwoniący nie jest przedmiotem? W PHP może to być metoda statyczna lub po prostu kod niezorientowany obiektowo. To staje się bardzo nieuporządkowane i prowadzi do wielu powtarzających się kodów w modelach.
Jest to również przykład Sprzęgła sterującego , co jest uważane za złe, ponieważ wywołujący musi coś wiedzieć o wewnętrznym działaniu wywoływanego obiektu. Na przykład niektóre metod Twojego Modelu może mieć parametr $transactional, ale inne metody mogą nie mieć tego parametru. Skąd rozmówca ma wiedzieć, kiedy parametr ma znaczenie?
// 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);
Inne rozwiązanie, które widziałem sugerowane (lub nawet zaimplementowane w niektórych frameworkach, takich jak Propel), to zrobienie beginTransaction()
i commit()
no-ops, gdy DBAL wie, że jest już w transakcji. Ale może to prowadzić do anomalii, jeśli twój model próbuje dokonać zatwierdzenia i stwierdzi, że tak naprawdę nie jest zatwierdzony. Lub próbuje wycofać i ignoruje to żądanie. Pisałem o tych anomaliach już wcześniej.
Zasugerowany przeze mnie kompromis polega na tym, że modele nie wiedzą o transakcjach . Model nie wie, czy jego żądanie do setPrivacy()
jest czymś, co należy zatwierdzić natychmiast, czy też jest częścią większego obrazu, bardziej złożonej serii zmian, która obejmuje wiele modeli i powinna tylko zostać popełnione, jeśli wszystkie te zmiany się powiedzie. To jest punkt transakcji.
Więc jeśli Modelki nie wiedzą, czy mogą lub powinny rozpocząć i dokonać własnej transakcji, to kto wie? GRASP zawiera Wzorzec kontrolera która jest klasą niezwiązaną z interfejsem użytkownika dla przypadku użycia i przypisuje się jej odpowiedzialność za tworzenie i kontrolowanie wszystkich elementów w celu wykonania tego przypadku użycia. Administratorzy wiedzą o transakcjach ponieważ w tym miejscu dostępne są wszystkie informacje o tym, czy cały przypadek użycia jest złożony i wymaga wielu zmian w modelach, w ramach jednej transakcji (lub być może w kilku transakcjach).
Przykład, o którym pisałem wcześniej, czyli rozpoczęcie transakcji w beforeAction()
metody kontrolera MVC i zatwierdź ją w afterAction()
metoda, jest uproszczeniem . Kontroler powinien mieć swobodę uruchamiania i zatwierdzania tylu transakcji, ile logicznie wymaga do zakończenia bieżącej akcji. Czasami kontroler może powstrzymać się od jawnej kontroli transakcji i pozwolić Modelom na automatyczne zatwierdzanie każdej zmiany.
Ale chodzi o to, że informacje o tym, jakie transakcje są konieczne, są czymś, czego Modele nie znają -- trzeba im je przekazać (w formie parametru $transactional) albo zapytać o to od wywołującego, który i tak musiałby delegować pytanie aż do działania kontrolera.
Możesz także utworzyć Warstwę usług klas, z których każda wie, jak wykonać tak złożone przypadki użycia i czy zawrzeć wszystkie zmiany w jednej transakcji. W ten sposób unikniesz wielu powtarzających się kodów. Ale nie jest powszechne, że aplikacje PHP zawierają odrębną warstwę usług; działanie kontrolera jest zwykle zbieżne z warstwą usług.