Причината, поради която казвам, че транзакциите не принадлежат към слоя на модела е основно следната:
Моделите могат да извикват методи в други модели.
Ако модел се опита да стартира транзакция, но няма информация дали неговият извикващ вече е започнал транзакция, тогава моделът трябва да условно стартирайте транзакция, както е показано в примера с код в @Bubba's answer . Методите на модела трябва да приемат флаг, така че повикващият да може да му каже дали му е разрешено да стартира собствена транзакция или не. В противен случай моделът трябва да има способността да отправя заявка към състоянието на обаждащия се „в транзакция“.
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
Ами ако обаждащият се не е обект? В PHP това може да бъде статичен метод или просто необектно-ориентиран код. Това става много объркано и води до много повтарящ се код в моделите.
Също така е пример за Control Coupling , което се счита за лошо, защото повикващият трябва да знае нещо за вътрешната работа на извикания обект. Например, някои от методите на вашия модел може да имат $transactional параметър, но други методи може да нямат този параметър. Как трябва да знае обаждащият се кога параметърът има значение?
// 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);
Другото решение, което видях предложено (или дори внедрено в някои рамки като Propel), е да се направи beginTransaction()
и commit()
no-ops, когато DBAL знае, че вече е в транзакция. Но това може да доведе до аномалии, ако вашият модел се опита да се ангажира и установи, че всъщност не се ангажира. Или се опитва да се върне назад и тази заявка е игнорирана. Писал съм за тези аномалии и преди.
Компромисът, който предложих, е, че Моделите не знаят за транзакции . Моделът не знае дали искането му към setPrivacy()
е нещо, което трябва да извърши незабавно или е част от по-голяма картина, по-сложна серия от промени, които включват множество модели и трябва само да бъдат ангажирани, ако всички тези промени успеят. Това е смисълът на транзакциите.
Така че, ако моделите не знаят дали могат или трябва да започнат и да извършат собствена транзакция, тогава кой знае? GRASP включва шаблон на контролера който не е UI клас за случай на употреба и му е възложена отговорността да създаде и контролира всички части за изпълнение на този случай на употреба. Контролерите знаят за транзакциите тъй като това е мястото, където е достъпна цялата информация за това дали пълният случай на употреба е сложен и изисква множество промени да бъдат направени в моделите, в рамките на една транзакция (или може би в рамките на няколко транзакции).
Примерът, за който писах преди, е да стартирате транзакция в beforeAction()
метод на MVC контролер и го запишете в afterAction()
метод, е опростяване . Контролерът трябва да е свободен да стартира и извършва толкова транзакции, колкото логически изисква за завършване на текущото действие. Или понякога контролерът може да се въздържа от изричен контрол на транзакциите и да позволи на моделите да извършват автоматично всяка промяна.
Но въпросът е, че информацията за това какви транзакции(и) са необходими е нещо, което Моделите не знаят – трябва да им се каже (под формата на $transactional параметър) или да я потърсят от повикващия, който така или иначе ще трябва да делегира въпроса чак до действието на Контролера.
Можете също така да създадете Сервизен слой от класове, всеки от които знае как да изпълни такива сложни случаи на използване и дали да обхване всички промени в една транзакция. По този начин избягвате много повтарящ се код. Но не е обичайно PHP приложенията да включват отделен сервизен слой; действието на контролера обикновено съвпада с сервизен слой.