Školení OOP v PHP

Publikoval admin v

9. a 10. ledna 2013 jsme absolvoval školení objektového programování v PHP od Jirky Knesla. Občas opakoval věci, které jsme věděli, ale nikdy není naškodu si to připomenout. Z mého pohledu to bylo celé zahrnuto na určité praktické použití znalostí o OOP při vytváření web. aplikací.

Kromě základních pojmů nám Jirka připomněl heslo Tell, Don’t Ask, což znamená, že primárně nemáme měnit data objektu, který si vyžádám, ale volat metodu toho objektu, který chci měnit, aby sám změnil svůj vnitřní stav.

Objekty by samy neměly být jen pouhé kontejnery pro data, ale měly by mít i nějaké chování.

Programátor by se při práci měl přiblížit k 5S (z japonštiny seiri, seiton, seiso, seiketsu, shitsuke)

  • kde to má být – jasná organizace
  • pořádek – kontrola, jestli určitá věc je na správném místě
  • čištění – zanech kód na konci čistší než byl
  • standardizace – štábní kultura
  • dodržet výše uvedené 4 body a nevracet se ke starému způsobu práce

K organizaci jsme se bavili o modelu MVC, který se používá v PHP frameworcích a organizace do jednotlivých vrstev je následující:

  • kontroler
    • předávání a dat do šablony
    • získávání z request,
    • přesměrování
    • práce s response
    • ACL
  • model
    • veškerá práce s daty
    • i validace dat (ne ve formuláři)
    • vyhledávání dat
    • ukládání dat
  • view
    • zobrazení dat ve správném formátu

RozpozModel obsahuje více vrstev a řeší ideálně veškerou logiku, kontroler maximálně použije nějaký formulář, ale nic nepočítá a nic extra sám nekontroluje.

Programátor při práci s nějakým frameworkem musí vědět, co má udělat pro přidání nové stránky.

Při přebírání cizího kódu nebo úpravě starých kódů by nám mohla pomoct kniha Údržba kódů převzatých programů.

Navrhování/rozpoznávání objektů

Kromě klasického navrhování lze praktikovat hledání objektů pomocí testů.

  • zdola nahoru – píšu testy pro maličké věci a pak vytvářím nadřazené větší, které závisí na těch malých, pokud mi něco chybí, tak vytvořím dalším elementární část a postupně to roste nahoru a první vždy vytvářím testy, takže musím vědět, co chci na vstupu, co má být na výstupu a co se má dít uvnitř.
  • shora dolů – můžu vytvářet něco abstraktního, ale pak se může stát, že budu programovat něco, co vlastně ve finále nebudu potřebovat. Toto je častější vývoj, je ale správný?

Další pomůckou mohou být tzv. CRC kartičky (class – responsibility – collaboration). Kartička o velikosti vizitky, na kterou si napíšeme název třídy (nahoře), její zodpovědnost (vlevo) a její závislosti závislost (vpravo). Důležitá je velikost vizitky, protože nás limituje velikostí.

Objekt by měly mít ideálně právě jednu zodpovědnost

U modulárního systému nespojovat zbytečně modulovou závislost, závislost vytvářet pouze na rozhraní, které ale není součástí modulu, je např. v balíku názevModuluInterface.

Vytvářet explicitní závislosti – předávané zvenku parametrem nebo přes dependency injection kontejner.

S.O.L.I.D – je sada pravidel, která je až příliš extrémní

  • single responsibility – objekt má právě jednu zodpovědnost
  • open close principal – především pro dědění, uvést protected všechno co chci, aby bylo otevřené pro volání při dědění, uzavřít všechno, co se skutečně nemá volat (tzn. private) … obzvláště výhodné je to pak při refaktorizaci, nevýhodné to může být, pokud píšete framework nebo knihovnu, kterou budou používat ostatní a zapomenete něco zveřejnit.
  • Liskov substitution principle – potomek může pouze rozšiřovat rozhraní tak, aby se vždy dal místo potomka použít stále rodiče. Tzn. když přetěžuji metodu, tak všechny rozšířené parametry budou defaultně nastavené. Obecně nezvětšovat závislost oproti zděděné třídě.
  • Interface segregation principle – udělat dostatečné rozhraní malé – podle zodpovědností, abych při změně implementace zodpovědnosti nemusel implmenetovat zbytečné metody.
  • dependency injection principl – konkrétní třídy závisí na abstraktních, ne naopak

Při implementaci metod nemíchejte různé úrovně abstrakce v jedné metodě, tzn. pokud budu pracovat s určitými objekty, tak na stejné úrovni nebudu zapisovat do soubor a dělat SQL dotazy.

Law of Demeter – pravidla, co udělat v metodě (umožní splnit pravidlo tell don’t ask)

$this->volamMojiMetodu()
$this->atr->metoda()
$obj->metoda()
$obj = new Obj
// už ale nesmím 
$this->volamMojiMetodu()->dalsiMetodaJinehoObjektu()

Smell Codes – špatné vzory

  • duplicitní kód v metodách a třídách
    • ale pozor na obrovské bázové třídy, která vlastně nemají žádnou zodpovědnost (je to pouze skladiště metod)
    • pokud budete duplicitní metody přenášet do jiné třídy, tak asi ne vždycky do bázové, ale do dalších objektů, které si vytvořím na daném místě a zavolám patřičné metody
  • dlouhé métody
    • udržovat krátké metody, rozdělit dlouhý kód na metody
  • příliš mnoho zanoření IF-ELSE
    • určit si úroveň zanoření (IF-ELSE)
  • dlouhé třídy
    • vytvářejte krátké třídy (najít zodpovědnosti, také podle toho, jak pracují metody s atributy)
  • řešíme problém, který vyžaduje malou změnu na mnoha místech
    • v takových případech můžeme vytvořit abstraktní vrstva, do které se přesune ten upravovaný kód, tam se přesouvá a upravuje ten kód a pokud je vše OK, tak se zase přesouvá zpět na původní místo (tady chybí příklad)
  • třída pracuje primárně s daty jiného objektu
    • je vhodné, přenést metody právě k tomu objektu. Toto řešení ale neplatí např. napříč vrstvami MVC
  • používání polí a subpolí místo objektů
    • výhoda subpolí je pouze jejich flexibilita, pokud ji nepotřebuji, tak jsou lepší objekty
  • větší počet stejných switchů pro volání různých metod
    • přenést do jednoho místa do Factory, která bude vracet objekt, který bude mít minimálně jednu metodu (např. action) podle definovaného rozhraní a ta se právě bude volat
  • statické třídy
    • použít raději kontejner Dependency Injection
  • zbytečná abstrakce
    • nedělat zbytečnou abstrakci pokud se neplánuje
  • použití message chain
    • nepoužívat, nejedná se o Fluent zápis, souvisí to s Law of Demeter viz výše
  • zbytečná dědičnost
    • dědičnost používjete, když má nadřazený prvek nějakou společnou identitu a zodpovědnost
  • špatná delegace – když třída polovinu svých metod deleguje dál
    • je lepší přímo používat ten delegovaný objekt
  • různě pojmenované rozhraní pro metody stejného charakteru
    • používejte společné rozhraní pro se stejnou funkcionalitou … např. názvy metod pro získání DB objektů (vždycky insertXXX, nekombinovat např. insertXXX a addXXX)
  • objekty pouze se settry a gettery pravděpodobně nemusí být k ničemu
    • pokud s tím objektem něco pracuje, tak je třeba zvážit jestli např. tu metodu nepřidat do tohoto objektu … nebo objekt a jeho atributy přenés do jiného objektu.
    • v případě, že má objekt pouze metody set a get, tak se jedná o datovou strukturu
  • omezená funkčnost potomků při dědičnosti
    • pokud použijete dědičnost, tak implementujte všechny metody aby fungovali a nezmenšujte funkcionalitu

Práce s databází

Přístup přes DataMapper

Controler volá ArticleRepository, to  poskládá SQL, prostřednictvím DBAdapter získá data skrz ArticleMapper, který mapuje atributy obj. na tabulky a sloupce v DB a controler získá Article (Article je primitivní hloupý objekt, vůbec neví o DB, ale obsahuje data z ní). Je to pak čistě objektová práce.

Repozitář pak vytvářejte pro určitý účel (zodpovědnost), ne pro tabulku, takže klidně nad více tabulkami.

Přístup přes AcitveRecord

Existuje ArticleRow, ArticleTable a skrz SQLBuilder a DB adapter sestaví dotaz a získá data … neexistuje ArticleMaper. Při tomto přístupu stále vím, že pracuji s DB, protože ArticleRow dědí z nějaké bázové Row, která si drží svoji tabulku apod.

Při práci s databázi je pak vhodné vytvářet více různých metod i tříd i pro různé množiny dat. Např. pro export, rozšířený pohled nad daty, apod.

Návrhové vzory

Dále jsme diskutovali o návrhových vzorech. Níže uvádím jen ty, které mě zaujaly nebo jsem považoval za dobré je znovu zmínit.

  • MVC s pas. view (View si tak nezískává data samo, ale dostane všechno předané)
  • Dependency Injection
  • Adapter
  • Strategy je jako Adapter, ale používá se např. pro různé algoritmy (např jeden sort, ale různé způsoby BubbleSort, QuickSort, …)
  • DataMaper
  • nepoužívat Singleton
  • opatrně s Factory
  • opatrně ActiveRecord (hlavně u velkých webů)

Aspektově orientované programování AOP

Snažil jsem se vytáhnout nějakou základní myšlenku i z wikipedie, ale zůstanu u toho, jak jsem to pochopil od Jirky Knesla. Je to prostě programování, kde se do velké části využívá zpráv (události a reakce na ně kdekoliv), čímž se může snadno zjednodušit algoritmus řešení hlavního programu. AOP je vhodné pro logování a kontrolu oprávnění při určité akci.

– existuje patch přímo do PHP

– lze použít framework flow3

Testovatelnost

  • nepoužívat exit a die
  • nepoužívat echo, var_dump, var_export mimo šablony
  • nepoužívat obecnou Exception, protože to vyhazuje až assert při testování a díky tomu se zjistí, jestli prošel nebo ne,
  • stejně tak nechytat obecné Exception, protože se může
  • Používat raději SPL Exceptions
  • Nepoužívat GET a POST v objektech, zapouzdřit do jiného objektu, který se dá při testování podstrčit
  • Nepoužívat global (výjimkou může výt logger)
  • Nepoužívat statické „cokoliv“
  • nepoužívat veřejné atributy
  • omezit settery, protože to předání dat z venku objektu lze udělat metodou, která již něco dělá (např. místo $user->setName(‚Radek‘); $user->save(); by možná bylo lepší $user->createUser(‚Radek‘);), ale existují i výjimky.
  • V modulárních systémech by každý modul měl mít vedle samotného modulu i interface a tzv. vnější výjimky

Samozřejmě pravidel a rad pro správné testování a psaní testovatelného kódu je na webu spousta.

Co mohu testovat?

  • návrat hodnota
  • vnější stav
  • vnitřní stav
  • výjimky