Uno de los grandes problemas que podemos encontrarnos al trabajar con PHP es que, por carecer nativamente de una separación de ámbitos, podemos ejecutar casi cualquier bloque de código en casi cualquier lado. Eso puede ser una gran ventaja en muchos casos, pero también puede llevarnos a provocar, muchas veces inconscientemente, un desorden enorme en la organización de nuestros proyectos, tanto a nivel código como a nivel de archivos.
Teniendo en cuenta este problema, este post es un intento de repaso por algunas prácticas que, creo, deberíamos considerar al programar con PHP. Más concretamente, cuando trabajamos con funciones privadas o internas.
Funciones (no tan) privadas
El título del artículo es engañoso: técnicamente, las funciones privadas en PHP no existen. No hay manera de declarar el nivel de acceso a una función como se hace con los métodos de clase, y siempre que haya sido previamente declarada, puede usarse en cualquier lugar. Pero que se pueda no significa que se deba.
Se puede tener, por ejemplo, una función que solamente esté hecha para usarse una vez, como un medio para emprolijar el código de otra función; una función que solo debe ser usada dentro de otras funciones bajo una serie de condiciones; una función que por ninguna razón debería ser usada fuera de los límites de nuestra aplicación; una función experimental con un funcionamiento imprevisible en implementaciones diferentes de la nuestra, y un montón de otros motivos por los que no desearíamos que se reutilizara fuera de nuestro estricto control, o al menos sin que se sepa exactamente cómo, dónde y para qué se está usando.
Cualquier función de este tipo es (o debería ser) entendida como privada o interna. Privada, porque se supone que solamente debería ser implementada por personas determinadas (por ejemplo, por quien esté a cargo del proyecto); interna, porque no debería ser usada fuera del proyecto. Mi preferencia es llamarlas internas, para no dar lugar a la confusión con el concepto de privacidad aplicado a los métodos de clase.
El problema, como decía antes, es que nada le impide a otro programador reutilizar una función previamente declarada, porque las funciones en PHP, en sentido técnico, son siempre públicas, y no hay manera de aplicar restricciones sobre ellas. Pero sí podemos ser más solidarios con los demás (y con nosotros mismos) y dejar ciertos tipos de indicadores sobre el uso de este tipo de funciones. No sería exactamente una restricción, como en un método de clase, sino más bien una manera de decir que, si se hace tal cosa, se puede ocasionar un comportamiento inesperado, o romper algo importante. O todo.
Convención sobre configuración
Acá es cuando las convenciones aparecen a nuestro rescate, y a través de ellas podemos dejar señales de lo poco recomendable de usar ciertas funciones. Si bien técnicamente seguiría sin haber restricciones, sí habría una suerte de intercambio entre programadores por medio de ciertas indicaciones sobre cómo trabajar con el código preexistente.
La convención por excelencia es poner un guión bajo delante del nombre de la funcion. De esta manera, una funcion llamada validate_data
pasaría a llamarse _validate_data
. Lo bueno de esta convencion es que es tan ampliamente usada que la mayoría de los programadores experimentados ya la conocen, y al ver una función con un guión bajo adelante, saben de inmediato que es privada.
Ejemplo:
function _validate_data() {
// Do something ...
}
El problema es que los programadores que no conocen la convención no solamente pueden reutilizar estas funciones, sino además usar el guión bajo delante de funciones que no sean privadas. Entonces, si no estamos seguros del nivel de los programadores para quienes va a ser accesible nuestro código, esta convención no es suficiente.
También se puede recurrir a convenciones de documentación, como las de PHPDoc. En este caso pueden usarse tags que denoten que una función no se debe reutilizar, como por ejemplo @private
o @internal
(personalmente, yo prefiero usar @internal
por eso de que no existen funciones privadas en sentido técnico). Esto puede ir acompañado del tag @see
para indicar donde estamos usando la función.
Ejemplo:
/**
* Validate some data.
*
* @internal
* @see some_function() in functions.php
*/
function validate_data() {
// Do something ...
}
Sin embargo, lamentablemente, y como ya sabemos de sobra, no es una práctica muy común leer documentación, incluso entre los programadores. Entonces, por más marcas internas que dejemos, siempre puede haber alguien que las pase completamente por alto.
Ante esto, un recaudo más puede consistir en tener un archivo separado para nuestras funciones privadas, con un nombre como private.php
o internal.php
. De esta manera, basta con ver el nombre del archivo en el que está la función para saber que hay que ser especialmente cuidadoso con ella.
Mi preferencia es usar todas estas convenciones simultáneamente. Así, si alguien no está al tanto de la práctica del prefijo, con leer la documentación interna puede ver que la función es privada. Si todo esto falla, todavía puede darse cuenta del nombre del archivo en el que está la función.
De esta manera, una función privada como las que yo uso, dentro de un archivo llamado internal.php
, quedaría así:
/**
* Validate some data.
*
* @internal
* @see some_function() in functions.php
*/
function _validate_data() {
// Do something ...
}
Conclusiones
Claro que ninguno de estos métodos es infalible. Ninguno provee la seguridad que dan las declaraciones de acceso a métodos de clase, y nunca podemos confiar lo suficiente en que los demás van a prestarle la atención que pretendemos a nuestro código. Alguien podría argumentar que, si la función en cuestión tiene que tener algún acceso restringido, entonces no debería ser una función sino un método de clase. Es un punto válido, pero no me interesa mucho discutirlo porque la realidad es que a veces simplemente no se puede aplicar un método, ya sea por limitaciones de nuestra aplicación, por política de nuestro equipo, porque no necesitamos OOP, o porque un programador anterior dejó el código de tal manera que sería más complicado aplicar métodos privados que funciones.
En el caso de WordPress, en ocasiones no hay mucho lugar para usar OOP, como en el desarrollo de themes (aunque algunos themes avanzados sí aplican OOP) o de plugins simples, por lo cual tiene mucho más sentido usar funciones que inventar clases sin realmente necesitarlas.
Hay montones de razones para usar funciones o usar métodos, pero al tratarse de PHP no existe una regla general para aplicar una determinada práctica (salvo que trabajemos con un framework que nos impone sus propias reglas) y siempre vamos a estar atados a nuestro contexto y a tener que lidiar con él de la manera más práctica posible.
Lo único que pude leer en donde se trata este tema con algo de profundidad es en el blog de Tom McFarlin, por lo cual me gustaría saber cómo intentan resolver este problema otros programadores (si es que lo intentan), porque lo presentado acá es solamente la forma en que yo trato de hacerlo, y que encontré que me da buenos resultados, pero no considero el tema como cerrado en lo mas mínimo.