Les deux principales raisons qui militent contre l'utilisation des méthodes statiques sont les suivantes :
- code utilisant des méthodes statiques est difficile à test
- code utilisant des méthodes statiques est difficile à étendre
Avoir un appel de méthode statique à l'intérieur d'une autre méthode est en fait pire que d'importer une variable globale. En PHP, les classes sont des symboles globaux, donc chaque fois que vous appelez une méthode statique, vous vous appuyez sur un symbole global (le nom de la classe). C'est un cas où global est mauvais. J'ai eu des problèmes avec ce genre d'approche avec certains composants de Zend Framework. Il y a des classes qui utilisent des appels de méthodes statiques (factories) afin de construire des objets. Il m'était impossible de fournir une autre fabrique à cette instance afin d'obtenir un objet personnalisé en retour. La solution à ce problème est de n'utiliser que des instances et des méthodes instables et d'imposer des singletons et autres au début du programme.
Miško Hevery qui travaille comme coach agile chez Google, a une théorie intéressante, ou plutôt un conseil, selon lequel nous devrions séparer le temps de création de l'objet du temps d'utilisation de l'objet. Ainsi, le cycle de vie d'un programme est divisé en deux. La première partie (le main()
disons), qui s'occupe de tout le câblage des objets dans votre application et de la partie qui effectue le travail réel.
Donc au lieu d'avoir :
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Nous devrions plutôt le faire :
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
Et ensuite, dans la page index/principale, nous ferions (c'est l'étape de câblage des objets, ou le moment de créer le graphe des instances qui seront utilisées par le programme) :
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
L'idée principale est de découpler les dépendances de vos classes. De cette façon, le code est beaucoup plus extensible et, ce qui est le plus important pour moi, testable. Pourquoi est-il plus important d'être testable ? Parce que je n'écris pas toujours du code de bibliothèque, donc l'extensibilité n'est pas si importante, mais la testabilité est importante quand je fais du refactoring. Quoi qu'il en soit, le code testable donne généralement du code extensible, donc ce n'est pas vraiment une situation de l'un ou l'autre.
Miško Hevery fait également une distinction très nette entre les singletons et les Singletons (avec ou sans S majuscule). La différence est très simple. Les singletons avec un "s" minuscule sont imposés par le câblage dans l'index/main. Vous instanciez un objet d'une classe qui fait no implémentez le modèle Singleton et veillez à ne transmettre cette instance qu'à toute autre instance qui en a besoin. D'autre part, Singleton, avec un "S" majuscule, est une implémentation de l'(anti-)pattern classique. En fait, c'est un global déguisé qui n'a pas beaucoup d'utilité dans le monde PHP. Je n'en ai pas vu jusqu'à présent. Si vous voulez qu'une seule connexion à la base de données soit utilisée par toutes vos classes, il est préférable de faire comme ceci :
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
En faisant ce qui précède, il est clair que nous avons un singleton et que nous avons également un bon moyen d'injecter un mock ou un stub dans nos tests. Il est surprenant de voir comment les tests unitaires conduisent à une meilleure conception. Mais cela prend tout son sens quand on pense que les tests nous obligent à réfléchir à la façon dont nous utiliserions ce code.
/**
* An example of a test using PHPUnit. The point is to see how easy it is to
* pass the UserCollection constructor an alternative implementation of
* DbCollection.
*/
class UserCollection extends PHPUnit_Framework_TestCase
{
public function testGetAllComments()
{
$mockedMethods = array('query');
$dbMock = $this->getMock('DbConnection', $mockedMethods);
$dbMock->expects($this->any())
->method('query')
->will($this->returnValue(array('John', 'George')));
$userCollection = new UserCollection($dbMock);
$allUsers = $userCollection->getAll();
$this->assertEquals(array('John', 'George'), $allUsers);
}
}
La seule situation où j'utiliserais (et je les ai utilisés pour imiter l'objet prototype JavaScript en PHP 5.3) des membres statiques est lorsque je sais que le champ respectif aura la même valeur entre les instances. A ce moment là, vous pouvez utiliser une propriété statique et peut-être une paire de méthodes statiques getter/setter. De toute façon, n'oubliez pas d'ajouter la possibilité de surcharger le membre statique avec un membre d'instance. Par exemple, Zend Framework utilisait une propriété statique pour spécifier le nom de la classe d'adaptateur DB utilisée dans les instances de Zend_Db_Table
. Cela fait longtemps que je ne les ai pas utilisés, donc il se peut que ce ne soit plus pertinent, mais c'est ainsi que je m'en souviens.
Les méthodes statiques qui ne traitent pas des propriétés statiques devraient être des fonctions. PHP a des fonctions et nous devons les utiliser.