PHP je věda

Objektově orientované programování nejen v PHP

Koordinace business logiky

Předpokládejme následující situaci. V Requestu dostaneme například povel, u uživatele Marek změnit adresu, adresu však nemění Marek sám, ale nějaký administrátor. U tohoto administrátora je potřeba zkontrolovat, zda na to má právo. Kód by pak byl následující:

<?php
 
interface AdminUser {
 
    /**
     * @return string
     */
    public function getRole();
}
 
interface AdminUserRepository {
 
    /**
     * @param $id
     *
     * @return AdminUser
     */
    public function findById($id);
 
}
 
interface User {
 
    public function setAddress();
}
 
interface UserRepository {
 
    /**
     * @param $userId
     *
     * @return User
     */
    public function findById($userId);
 
    /**
     * @param User $user
     *
     * @return User
     */
    public function save(User $user);
 
}
 
class UserController {
 
    /**
     * @var UserRepository
     */
    protected $userRepository;
 
    /**
     * @var AdminUserRepository
     */
    protected $adminUserRepository;
 
    public function __consruct() {
        $this->userRepository = new UserMySqlRepository();
        $this->adminUserRepository = new AdminUserRepositoryMySQL();
    }
 
    public function changeAddressAction() {
        $address     = $_REQUEST['address'];
        $userId      = $_REQUEST['id'];
        $adminUserId = $_SESSION['id_user'];
 
        $user =  $this->userRepository->findById($userId);
        $adminUser = $this->adminUserRepository->findById($adminUserId);
 
        if ($adminUser->getRole() == 'admin') {
            $user->setAddress($address);
            $this->userRepository->save($user);
        }
    }
}

A zde se již dostáváme do problémů, jelikož máme v controlleru logiku. Představme si, že by logika pro validaci, zda smí adminUser změnit adresu uživatele user byla mnohem složitější a že by se měnilo mnohem více dat, než jen adresa. Pokud bychom chtěli vykonat tuto logiku na nějakém jiném místě, museli bychom copypastovat. Řešením pro tento problém je napsat service pro zpracování tohoto úkolu.

Kód jednou napsaný v controlleru nelze znovu použít

Pro znovupoužitelnost kódu se využívá tzv. služeb. Služba je obecně třída, která něco vykonává. Řešením pro tento problém je tedy napsat service pro zpracování tohoto úkolu.

<?php
 
class UserChangingService {
 
    public function changeAddress(AdminUser $adminUser, User $userToChange, UserRepository $repository, $address) {
        if ($adminUser->getRole() == 'admin') {
            $userToChange->setAddress($address);
            $repository->save($userToChange);
        }
    }
}
 
class ControllerWithService {
    /**
     * @var UserRepository
     */
    protected $userRepository;
 
    /**
     * @var AdminUserRepository
     */
    protected $adminUserRepository;
 
    /**
     * @var UserChangingService
     */
    protected $userChangingService;
 
    public function __consruct() {
        $this->userRepository = new UserMySqlRepository();
        $this->adminUserRepository = new AdminUserRepositoryMySQL();
        $this->userChangingService = new UserChangingService();
    }
 
    public function changeAddressAction() {
        $address     = $_REQUEST['address'];
        $userId      = $_REQUEST['id'];
        $adminUserId = $_SESSION['id_user'];
 
        $user =  $this->userRepository->findById($userId);
        $adminUser = $this->adminUserRepository->findById($adminUserId);
 
        $this->userChangingService->changeAddress($adminUser, $user, $this->userRepository, $address);
    }
}

Rozdíl mezi třídou Controller a ControllerWithService je ten, že Controller vykonává logiku (dělá validaci, pracuje s databázovým rozhranním) a ControllerWithService pouze koordinuje. ControllerWithService ví že uživatele může změnit pomocí UserChangingService a ví, jaké objekty k tomu potřebuje. Pokud takovouto service jednou napíšeme, pokaždé, když chceme změnit uživatele, máme jistotu, že např. nezapomeneme validovat, zda uživatel, který se snaží adresu změnit na to opravdu má právo. Pokud se logika ukládání a validace uživatele změní, měníme jen na jednom místě. Náš Controller potom opravdu jen koordinuje vytváření instancí, zapouzdřenou logiku volá pomocí metod a žádnou vlastní logiku nevykonává.

Představme si, že bychom měli program na správu uživatelských dat zaměstnanců určitých firem. Mimo jiné by měl program umět i přiřadit zaměstnancům novou adresu, pokud by se firma přestěhovala. Příklad je samozřejmě absurdní, ale pokud bychom takovýto úkol dostali, máme na to již připravenou službu, která zaměstnance „přestěhuje“ a to včetně validace (MyCronJob).

<?php
class MyCronJob {
 
    /**
     * @var UserMySqlRepository
     */
    protected $repository;
 
    /**
     * @var UserChangingService
     */
    protected $userChangingService;
 
    public function __construct() {
        $this->repository = new UserMySqlRepository();
        $this->userChangingService = new UserChangingService();
    }
 
 
 
    public function exec() {
        $allUsers = $this->repository->findAllUsersWithAddress('Old address 3, City');
        $adminUser = new User("AdminUser");
        $newAddress = "New Address 4, City";
        foreach ($allUsers as $user) {
            $this->userChangingService->changeAddress($adminUser, $user, $this->repository, $newAddress);
        }
    }
}
 

Musíme se zde opravdu jen starat o to, jak vytvořit ty správné instance objektů. Pokud víme, že metoda UserChangingService::changeAddress změnu uloží, musíme vytvořit pouze správné objekty a metoda se postará o validaci i o uložení pozměněného uživatele do databáze. Další výhodou psaní znovupoužitelného kódu pomocí servisů dosažení atomicity operací. Pokud dosáhneme toho, že jednořádkovým voláním jsme schopni vykonat nějakou určitou operaci, máme jistotu, že se vykonají všechny potřebné operace.

Výhodou zapouzdření kódu do servisů je dosažení atomicity operací