J'entends par service une classe issue du modèle Service / DAO / modèle de données, ce modèle a été détaillé dans un précédent post : Concevoir une application Zend selon le modèle Service / DAO / Modèle de données. Dans ce post je montrais un exemple de service très simple, je propose de repartir de la classe Service_User qui correspond à un service de gestion d'utilisateurs dans une application Zend / PHP.
Voici cette classe:
// File 'application/services/Service.php' /** * Service used to manage Model_Users. * * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com) */ class Service_User implements Service_Service { /** * DAO used to manage the persistence for Model_Users entities. * * @var Dao_User */ private $userDao; /** * Constructs a new Service_User. * * @return Service_User */ public function __construct() { $this->userDao = new Dao_User(); } /** * Saves a Model_User in the system. * * @return Model_User $user the user to save in the system. */ public function save(Model_Account $user) { $this->userDao->saveOrUpdate($user); } /** * Lists all the Model_Users registered in the system. * * @return Array An array of Model_Users which represents * all the registered Model_Users. */ public function listAll() { return $this->userDao->listAll(); } }
Cette classe de service comporte 3 fonctions, vous pouvez remarquer qu'aucune de ces fonctions n'utilise de code relatif aux transactions.
L'astuce que je propose pour rendre transactionnelles ces 3 fonctions est de créer un service de wrapping Service_TransactionalService qui se chargera de démarrer / commiter / rollbacker automatiquement les transactions.
Voici cette classe de wrapping.
/** * Service which wraps an other one to add transaction around * each method of the wrapped service. * * @author Baptiste GAILLARD (baptiste.gaillard@hotmail.com) */ class Service_TransactionalService implements Service_Service { /** * The service to make transactional. * * @var Service */ private $service; /** * The Zend Db Adapter. * * @var Zend_Db_Adapter */ private $db; /** * Constructs a new Service_TransactionalService by wrapping * an other one. * * @param $service the service to wrap. * @return Service_TransactionalService */ function __construct(Service_Service $service) { $this->service = $service; $this->db = Zend_Registry::get('db'); } /** * Wraps a service call around a transaction. * * @param String $name name of the function to call. * @param Array $arguments arguments to pass to the function. * @return Object return of the called function. */ function __call($name, $arguments) { // -- Starts a transaction $this->db->beginTransaction(); try { $ret = call_user_func_array( array($this->service, $name), $arguments); // -- Commit the transaction $this->db->commit(); return $ret; } catch (Exception $e) { // -- Rollback the transaction $this->db->rollBack(); echo $e->getMessage(); } } }
Toute la magie de cette classe repose dans l'utilisation de la méthode PHP __call(), cette méthode est appelée à chaque fois que l'on appelle une méthode inexistante sur un objet.
Dans notre cas nous pouvons appeler 3 méthodes sur un objet de type Service_TransactionnalService, par exemple, pour un appel à listAll() :
$transactionnalService = new Service_TransactionnalService( new Service_UserService()); $transactionnalService->listAll();
L'appel à listAll() sur $transactionnalService équivaut à avoir le code suivant dans la méthode listAll() de la classe de service Service_User.
/** * Lists all the Model_Users registered in the system. * * @return Array An array of Model_Users which represents * all the registered Model_Users. */ public function listAll() { // -- Starts a transaction $this->db->beginTransaction(); try { $ret = $this->userDao->listAll(); // -- Commit the transaction $this->db->commit(); return $ret; } catch (Exception $e) { // -- Rollback the transaction $this->db->rollBack(); echo $e->getMessage(); } }
Voilà, nous avons réussi à wrapper tous les appels de méthodes de nos services dans des transactions. Dans un de mes prochains post j'expliquerai comment accéder à vos services transactionnels de manière simple au sein de votre application afin d'éviter d'avoir des instanciations à la classe Service_TransactionnalService partout ou vous avez besoin d'un service transactionnel.
Aucun commentaire:
Enregistrer un commentaire