Les deux principales raisons contre l'utilisation de méthodes statiques sont:
- le code utilisant des méthodes statiques est difficile à tester
- le code utilisant des méthodes statiques est difficile à étendre
Avoir un appel de méthode statique dans 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 fiez à un symbole global (le nom de la classe). C'est un cas où le global est mauvais. J'ai eu des problèmes avec ce type d'approche avec certains composants de Zend Framework. Il existe des classes qui utilisent des appels de méthodes statiques (usines) pour créer des objets. Il était impossible pour moi de fournir une autre usine à cette instance afin de récupérer un objet personnalisé. La solution à ce problème consiste à n'utiliser que des instances et des méthodes instace et à appliquer des singletons et autres au début du programme.
Miško Hevery , qui travaille en tant que coach agile chez Google, a une théorie intéressante, ou plutôt conseille, que nous devrions séparer le temps de création de l'objet du moment où nous utilisons l'objet. Le cycle de vie d'un programme est donc divisé en deux. La première partie ( main()
disons la méthode), qui prend en charge tout le câblage des objets dans votre application et la partie qui fait le travail réel.
Donc au lieu d'avoir:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Nous devrions plutôt faire:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
Et puis, dans l'index / page principale, nous ferions (c'est l'étape de câblage de l'objet, ou le temps de créer le graphique des instances à utiliser 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, la partie la plus importante pour moi, testable. Pourquoi est-il plus important d'être testable? Parce que je n'écris pas toujours de code de bibliothèque, l'extensibilité n'est donc pas si importante, mais la testabilité est importante lorsque je refactore. Quoi qu'il en soit, le code testable produit 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 claire entre les singletons et les singletons (avec ou sans un 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 n'implémente pas 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 du modèle classique (anti-). Fondamentalement, 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 DB soit utilisée par toutes vos classes, il est préférable de le 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 nous avons également un bon moyen d'injecter un faux ou un stub dans nos tests. C'est étonnamment comment les tests unitaires conduisent à une meilleure conception. Mais cela a beaucoup de sens quand vous pensez que les tests vous obligent à réfléchir à la façon dont vous utiliseriez 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 dans PHP 5.3) des membres statiques est lorsque je sais que le champ respectif aura la même valeur cross-instance. À ce stade, vous pouvez utiliser une propriété statique et peut-être une paire de méthodes getter / setter statiques. Quoi qu'il en soit, n'oubliez pas d'ajouter la possibilité de remplacer le membre statique par un membre d'instance. Par exemple, Zend Framework utilisait une propriété statique afin de spécifier le nom de la classe d'adaptateur de base de données utilisée dans les instances de Zend_Db_Table
. Cela fait un moment que je ne les ai pas utilisés, donc ce n'est peut-être plus pertinent, mais c'est comme ça 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 devrions les utiliser.