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.