Las dos razones principales contra el uso de métodos estáticos son:
- el código usando métodos estáticos es difícil de probar
- el código que usa métodos estáticos es difícil de extender
Tener una llamada a un método estático dentro de otro método es en realidad peor que importar una variable global. En PHP, las clases son símbolos globales, por lo que cada vez que llama a un método estático se basa en un símbolo global (el nombre de la clase). Este es un caso cuando global es malvado. Tuve problemas con este tipo de enfoque con algún componente de Zend Framework. Hay clases que usan llamadas a métodos estáticos (fábricas) para construir objetos. Fue imposible para mí suministrar otra fábrica a esa instancia para obtener un objeto personalizado devuelto. La solución a este problema es utilizar solo instancias y métodos de instancias y aplicar singletons y similares al comienzo del programa.
Miško Hevery , que trabaja como entrenador ágil en Google, tiene una teoría interesante, o más bien aconseja, que separemos el tiempo de creación del objeto del momento en que lo usamos. Entonces el ciclo de vida de un programa se divide en dos. La primera parte ( main()
digamos el método), que se encarga de todo el cableado de objetos en su aplicación y la parte que hace el trabajo real.
Entonces, en lugar de tener:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Más bien deberíamos hacer:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
Y luego, en la página índice / principal, lo haríamos (este es el paso de cableado del objeto, o el momento de crear el gráfico de instancias que utilizará el programa):
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
La idea principal es desacoplar las dependencias de tus clases. De esta forma, el código es mucho más extensible y, lo más importante para mí, comprobable. ¿Por qué es más importante ser comprobable? Debido a que no siempre escribo el código de la biblioteca, la extensibilidad no es tan importante, pero la comprobabilidad es importante cuando refactorizo. De todos modos, el código comprobable generalmente produce código extensible, por lo que no es realmente una situación de uno u otro.
Miško Hevery también hace una clara distinción entre singletons y Singletons (con o sin S mayúscula). La diferencia es muy simple. Los singletons con minúscula "s" se aplican mediante el cableado en el índice / principal. Instancia un objeto de una clase que no implementa el patrón Singleton y se asegura de pasar esa instancia a cualquier otra instancia que lo necesite. Por otro lado, Singleton, con una "S" mayúscula es una implementación del patrón clásico (anti). Básicamente, un disfraz global que no tiene mucho uso en el mundo PHP. No he visto uno hasta este punto. Si desea que todas sus clases utilicen una única conexión de base de datos, es mejor hacerlo así:
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
Al hacer lo anterior, está claro que tenemos un singleton y también tenemos una buena manera de inyectar un simulacro o un trozo en nuestras pruebas. Es sorprendente cómo las pruebas unitarias conducen a un mejor diseño. Pero tiene mucho sentido cuando crees que las pruebas te obligan a pensar en la forma en que usarías ese código.
/**
* 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 única situación en la que usaría (y los he usado para imitar el objeto prototipo de JavaScript en PHP 5.3) miembros estáticos es cuando sé que el campo respectivo tendrá el mismo valor de instancia cruzada. En ese punto, puede usar una propiedad estática y quizás un par de métodos getter / setter estáticos. De todos modos, no olvide agregar la posibilidad de anular el miembro estático con un miembro de instancia. Por ejemplo, Zend Framework estaba usando una propiedad estática para especificar el nombre de la clase de adaptador DB utilizada en instancias de Zend_Db_Table
. Ha pasado un tiempo desde que los he usado, por lo que puede que ya no sea relevante, pero así es como lo recuerdo.
Los métodos estáticos que no tratan con propiedades estáticas deberían ser funciones. PHP tiene funciones y deberíamos usarlas.