#35 Kit
Model, metody changeEmail a changePassword a Single Responsibility Principle
Asi záleží na úhlu pohledu, jak na konkrétní třídu nahlížíš, v mém případě jsou instance třídy Model objekty, které obsahují data, starají se o ně a dělají svoji práci dobře. Poskytují jednoduché veřejné rozhraní, které uživatel může volat, aniž by ho zajímalo, co se ve skutečnosti děje uvnitř objektu s daty, které například přes mutator nastavit nejdou.
To je jejich zodpovědnost, starat se o svá data. Že poskytují moje modely veřejné rozhraní skrze accessory či mutatory osobně nepovažuji za porušení SRP, protože stále třída pracuje pouze se sebou sama a svými vlastnostmi. Například pro lastUserActivity setter ani není, protože uživatel používající model nemá mít možnost tuto hodnotu nastavit jen tak, protože se mu chce, tato hodnota se má nastavit při volání určitých metod, které tento stav mohou ovlivnit (například konkrétně změna hesla, což je proces, kdy se aktivně mění vnitřní struktura objektu a je tedy dobré změnu zaevidovat).
Pokud máš dojem, že tebou zmíněné dvě metody porušují SRP, zkus navrhnout řešení, jak bys to udělal ty. Napadá mě mít zcela immutabilní objekt, který by mutatory vůbec neměl a kdybys chtěl změnit vlastnost, tak musíš vytvořit novou instanci toho objektu, což je šileně špatné řešení. Kdybys na druhou stranu měl službu, například UserChangingPasswordService(User $user) : User, i zde nakonec musíš do třídy User ta data nějak dostat -> opět přes mutator, jinak by ses přece k vnitřním částem třídy vůbec nedostal.
Samozřejmě, jak jsi zmínil výše, mít anemický model, kde ke všem protected/private vlastnostem napíšeš accessory/mutatory, je svým způsobem nesmysl a je dobrý jen pro zlepšení přehlednosti, v tom ti nebudu odporovat. Mé modely obsahují i business logiku (například pokud bys měl zboží, spadající do N kategorií, pak bych měl model:
final class Goods
{
/** @var string */
private $_name;
/** @var string[] */
private $_categories = array();
/** @var int */
private $_countOfCategories;
/**
* @param string $name
* @param array $categories
* @throws InvalidArgumentException
*/
public function __construct($name, array $categories = array())
{
if ($name === null || $name === "") {
throw new InvalidArgumentException("Name must mot be null or empty string.");
}
$this->_name = $name;
$this->_categories = $categories;
$this->_countOfCategories = count($categories);
}
/**
* @return string
*/
public function getName()
{
return $this->_name;
}
/**
* @return string[]
*/
public function getCategories()
{
return $this->_categories;
}
/**
* @return int
*/
public function getCountOfCategories()
{
return $this->_countOfCategories;
}
/**
* @param string $newCategory
*/
public function addCategory($newCategory)
{
if ($newCategory === null || !is_string($newCategory) || $newCategory === "") {
throw new InvalidArgumentException("A category must be a non-empty string.");
}
$this->_categories[] = $newCategory;
++$this->_countOfCategories;
}
/**
* @param string $categoryName
* @throws Exception
*/
public function removeCategory($categoryName)
{
$categoryExists = false;
if ($key = array_search($categoryName, $this->_categories) !== false) {
unset($this->_categories[$key]);
--$this->_countOfCategories;
$categoryExists = true;
}
if ($categoryExists === false) {
throw new Exception("Goods does not belong to specified category which therefore could not be removed.");
}
}
}
Všimni si, že ačkoliv dávám uživateli možnost přidat nebo odebrat kategorii, dávám uživateli také možnost získat počet kategorií, do nichž zboží patří, ale uživateli nedávám možnost tuto hodnotu zvnější změnit. Protože by tím mohl porušit vnitřní stav objektu. Uživatele veřejného API objektu zboží nezajímá, kdy se hodnota proměnné _countOfCategories mění, ale zajímá ho, aby v ní byla vždycky správná hodnota.
DataMapper: 4 metody v interface? Na co tolik? To pokaždé povinně implementuješ všechny čtyři? Tomu říkáš SRP?
Pokud by už můj interface porušoval jedno z pravidel SOLID designu, pak porušuje interface segregation principle (kdy jsem možná mohl rozdělit interface na dvě, DataMapperSave a DataMapperGet), nikoliv SRP. Vždyť ten interface má opět jedinou zodpovědnost. Mapovat objekty na persistenční vrstvu, to je jediná věc, kterou ten interface má dělat, a dělá ji opět dobře (předáš mu Model, on ti ho uloží, kam budeš potřebovat, potřebuješ model, tak ti ho najde a vrátí, pokud k němu budou data).
K 10x prepare jsem se již vyjádřil včera s tím, že já prepare a execute zavolám pouze jednou, akorát musím do doQuery poslar SQL ve správném formátu. Standardně používám pouze jednou prepare a jednou execute, protože se mi osvědčilo, že je tak operace rychlejší, než volat execute 10x, nicméně jsi mi nasadil brouka do hlavy, trošku jsem to studoval a prý by mi na tomto mohla aplikace kleknout, kdybych chtěl například vložit X tisíc záznamu najednou (nezkoušel jsem, zatím jsem se s tím problémem nepotýkal).
Co se lazy iniciace týče, ta se neřeší v tom Driveru. Ty ten samotný driver ani nikdy používat nebudeš, na to máš DataMapper. A DataMapper si běžně získávám přes továrničku, kterou mám uloženou v DI kontejneru a získávám ji teprve tehdy, kdy ji potřebuji, ne dřív, ne později. Vím, že budu pracovat s uživateli, tak mi továrnička na základě specifikací v configu vyplivne instanci DataMapperu takového, aby ukládal a získával data z místa, odkud má (ať je to relační databáze, noSQL databáze, nebo soubor).
Každopádně Kite díky za připomínky, s některými tvými poznatky souhlasím, s jinými ne a co se mi nelíbí je tvůj přístup k šablonování, ale to spíš vychází z toho, že já sám používám na šablony Twig a jsem na něj zvyklý.