He visto mucha documentación que habla sobre el tema de los métodos "getter / setter", o traducido al castellano los métodos "accesores / modificadores", y la mayoría se va a los extremos, perdiendo de explicar de forma simple la razón de su existencia.
Trataremos de sacarle la "mística" al asunto.
Antes de usar la estrategia de los "set y get" para los atributosEn PHP4 todos los atributos de un objeto son siempre atributos públicos, es decir, cuando creamos una instancia del objeto a partir de la definición de la clase, tenemos acceso completo a cada uno de los atributos, tanto para leerlos como para modificarlos.
Definición de la clase "Usuario"
1
<?php
2 class Usuario{
3 var $nombre;
4 var $nombreReal;
5 var $edad;
6 var $clave;
7 }
8 ?>
Creo el objeto "unUsuario":
$unUsuario = new Usuario();
En este momento el usuario está vacío, pues sus atributos no tienen ningún valor asignado. Por lo tanto, le daremos sentido a este objeto:
1
<?php
2 $unUsuario->nombre = "eplace";
3 $unUsuario->nombreReal = "Enrique Place";
4 $unUsuario->edad = "33";
5 $unUsuario->clave = "pepe";
6 ?>
Inventemos un escenario completamente de fantasía (surrealista diría)Supongamos ahora que nuestro sistema es mantenido por varios desarrolladores y que una parte del sistema es mantenida por un "estudiante de programación" que decidió unilateralmente que cuando le llegue el objeto "unUsuario" le pondrá siempre el nombre en mayúsculas y le colocará una clave por defecto si esta está vacía.
1
<?php
2 $unUsuario->nombre = strtoupper($unUsuario->nombre);
3
4 if (is_null($unUsuario->clave)){
5 $unUsuario->clave="clavePorDefecto";
6 }
7 ?>
Nosotros, como
"desarrolladores experimentados", nos sentimos molestos por la tontería que acaba de hacer el "estudiante"... ahora la pregunta es, como evitamos que esto suceda?
Uno de los problemas aquí es que PHP4 no soporta la definición del "alcance" (o también llamada "visibilidad") de los atributos y métodos, algo habitual en cualquier lenguaje serio de tipo
"Orientado a Objetos". Esto hace que -aunque no nos guste- el desarrollador de turno pueda hacer lo que quiera con los atributos de cualquier objeto a su alcance.
Entonces migremos a PHP5PHP5, consciente de este problema, implementa la definición de "visibilidad" de los atributos y métodos, como lo haría Java o .Net. Si hiciéramos una migración "mecánica" de nuestro código, este cambiaría la sintaxis a esta forma (ya que la sintaxis "var" pasa a desuso y esta definía a los atributos siempre "públicos"):
1
<?php
2
3 class Usuario{
4 public $nombre;
5 public $nombreReal;
6 public $edad;
7 public $clave;
8 }
9
10 ?>
Ahora disponemos de las siguientes opciones gracias a la nueva sintaxis:
public, private y protected.
Ahora bien, si queremos que el
"estudiante" no pueda modificar nuestros datos, podemos pasar a colocar todos los atributos como "private":
1
<?php
2 class Usuario{
3 private $nombre;
4 private $nombreReal;
5 private $edad;
6 private $clave;
7 }
8 ?>
Pronto, ahora cuando el
"estudiante" quiera ver o modificar un atributo, el sistema le enviará un error. El problema ahora es que nosotros queremos que:
- La edad se pueda saber y cambiar en todo momento.
- Se pueda saber el nombre del usuario, pero no modificarlo
- No nos interesa que se sepa el nombre real del mismo
- Pero queremos que pueda colocar una nueva clave si el usuario se la olvida, pero no saber la que existe actualmente
Esto no lo podemos hacer ni teniendo todos los atributos públicos como sucedía con PHP4 ni restringiendo toda la visibilidad como nos permite ahora PHP5.
¿Cómo se hace entonces?Por un tema de principios de la
POO los atributos de los objetos deben ser siempre "privados" (concepto "encapsulación": no son accesibles desde fuera del objeto, solo el objeto tiene la potestad de usarlos directamente) y se deberán crear métodos públicos que sustituya una u otra operación, o ambas, cada vez que la situación lo amerite: un
método "setter" para "cargar un valor" (asignar un valor a una variable) y/o un
método "getter" para "retornar el valor" (solo devolver la información del atributo para quién la solicite).
Requerimiento 1) la edad se puede acceder y modificar en todo momento, por consiguiente se deben agregar los dos métodos, un "get" y un "set" para ese atributo:1
<?php
2 class Usuario{
3 private $nombre;
4 private $nombreReal;
5 private $edad;
6 private $clave;
7
8 public function getEdad() {
9 return $this->edad;
10 }
11 public function setEdad($edad){
12 $this->edad = $edad;
13 }
14 }
15 ?>
Pronto, ahora el atributo se puede consultar o modificar no directamente, solo a través de los métodos "get / set". En este caso no se nota la utilidad, pero pasemos al siguiente requerimiento.
Requerimiento 2) poder saber el nombre del usuario pero no modificarlo, para eso hay que agregar solo un método get para ese atributo:1
<?php
2
3 class Usuario{
4 private $nombre;
5 private $nombreReal;
6 private $edad;
7 private $clave;
8
9 public function getEdad() {
10 return $this->edad;
11 }
12 public function setEdad($edad){
13 $this->edad = $edad;
14 }
15 public function getNombre(){
16 return $this->nombre;
17 }
18 }
19 ?>
Ahora se puede consultar el nombre pero no modificar, pues el atributo no es visible desde fuera del objeto, solo a través de los métodos públicos que vamos definiendo.
Requerimiento 3) no nos interesa que se sepa el nombre real del usuarioLo dejamos como está y queda inaccesible desde fuera del objeto.
Requerimiento 4) queremos que pueda colocar una nueva clave si el usuario se la olvida, pero no saber la que existe actualmentePara eso, hacemos lo contrario que con el atributo nombre, agregamos un método "set" pero no el "get":
1
<?php
2 class Usuario{
3 private $nombre;
4 private $nombreReal;
5 private $edad;
6 private $clave;
7
8 public function getEdad() {
9 return $this->edad;
10 }
11 public function setEdad($edad){
12 $this->edad = $edad;
13 }
14 public function getNombre(){
15 return $this->nombre;
16 }
17 public function setClave($clave){
18 $this->clave = $clave;
19 }
20 }
21 ?>
Pronto, usando simples métodos podemos reforzar el diseño de nuestro objeto, restringiendo según nuestra necesidad el acceso a sus atributos.
Formalicemos: "Getter/Setter es solo un tema de conceptos"Cuando empezamos a aprender a usar Objetos el primer error que cometemos es dejar todos los atributos públicos, lo que permite que cualquier usuario de nuestra clase pueda hacer y deshacer sin control nuestro objeto (modificando y consultando sus valores).
Generalmente nuestros objetos deberían contener determinada información que no necesariamente el usuario de nuestro objeto debería saber, porque no conviene, o porque directamente no corresponde y permitirle acceder a la misma es aumentar innecesariamente la complejidad del uso del objeto.
Regla: "Evitar que el objeto muestre detalles de su implementación"
Otro ejemplo: si tu sabes que tu objeto "Persona" tiene una fecha de nacimiento y calculas su edad, no deberías poder permitir que alguien "de afuera del objeto" cambie la edad, pues está relacionada con otra información (fecha de nacimiento) y a partir de ella es que se genera (calcularEdad()). Lo correcto sería modificar la fecha de nacimiento para que el propio objeto la vuelva a calcular.
Los detalles del funcionamiento del objeto son internos, y el usuario del objeto (otro desarrollador u otro sistema) no debe ni necesita conocerlos. Por consiguiente ya tenemos otro caso claro, el atributo "edad" no debería poder ser modificado externamente, pero sí consultado cada vez que se necesite.
Si este fuera un atributo público sería imposible restringir una operación y habilitar la otra. De ahí que nace el concepto de "getter / setter", o de "métodos accesores / modificadores", o como muchos autores se refieren a estos métodos especiales como "propiedades" (pero creo que puede generar confusiones con el concepto "atributo", pues muchos otros autores usan las mismas palabras indistintamente).
Si tú colocas atributos privados, estos serán solo
"vistos / accedidos / usados / modificados" dentro de la propia clase. Si tu quieres que puedan ser accedidos desde fuera de la clase, debes crear métodos públicos que internamente "accedan" a los atributos, pero que los dejarán "resguardados" dentro del objeto (no hay que olvidar que en un caso pueden hacer una operación u otra, no necesariamente ambas).
Recalco, digo "nomenclatura", puesto que los nombres de los métodos pueden llamarse como quieras, pero si cumplen con ambas definiciones (get/set), complirá con la esencia de la funcionalidad. El tema es, por convención, se tiende a reconocer así a los métodos que solo sirven para
"hacer operaciones básicas como si trabajáramos con atributos públicos", y que además, no deben tener más complejidad que esa. De lo contrario, ya sería mejor que crearas un método comunes y corriente, asignándole un nombre claro a su acción.
Errores más comunesDefinir todos los "get" y "set" para todos los atributos existentes. Es casi lo mismo que si fueran todos públicos, careciendo de utilidad. Lo habitual es que esto no suceda, cuanto más escondamos de nuestro objeto mejor será, pues disminuimos la complejidad de su uso y evitamos que cambie a un estado que no queremos, y cuando menos se sepa de como trabaja internamente, más fácil será poder reutilizarlo en contextos distintos (deberá ser raro que debas dejar disponible todo y no existan algunos que sean solo de uso interno).
Otro error común es agregarle más lógica que asignar un valor o retornar el valor del atributo. Si necesitas agregarle lógica, ya dejan de ser "get / set", lo cual es mejor que cambiemos de nombre y lo dejemos con los demás métodos comunes de nuestra clase.
Por ejemplo: si para cambiar el nombre del usuario, antes de asignar el valor voy a la base de datos y verifico que no exista otro igual, y luego en caso de nombre duplicado genero otro alternativo, etc, yo preferiría o desglosar el método setNombre en varias llamadas a métodos privados, o directamente crear un método nuevo que se llame cambiarNombre, reflejando un proceso fuera del simple get/set.
Nota: esto es muy a nivel personal, hay opiniones encontradas sobre tener una lógica elemental dentro de un set/get o hacerlo tan extenso como necesitemos. Claramente yo estoy en el primer grupo.
Como detalle, para que quede más evidente y visualmente claroComo todo es convención a la hora de definir la forma de trabajo en un entorno de desarrollo, es habitual que los atributos siempre vayan juntos al principio de la clase e inmediatamente -antes de empezar a definir los métodos- deberían estar los métodos "accesores / modificadores". Lo que se suele hacer, pues son métodos muy simples y generalmente carentes de mucha lógica (como se explica en el punto anterior), tal vez sería mejor hacerlos en una sola línea, sin
indentación:
1
<?php
2
3 class Usuario{
4 private $nombre;
5 private $nombreReal;
6 private $edad;
7 private $clave;
8
9 /** getter / setter */
10 public function getEdad() {return $this->edad;}
11 public function setEdad($edad){$this->edad = $edad;}
12
13 public function getNombre(){return $this->nombre;}
14
15 public function setClave($clave){$this->clave = $clave;}
16
17 }
18 ?>
Nota: De todas formas no tomar esto más de un ejemplo, ya que
el estándar oficial de condificación para PHP (el que define la empresa
Zend) no sugiere en ningún momento esta práctica.
En resumenEl tema no es tan complejo, de forma genérica esta es la explicación de qué es "getter/setter" y para qué sirve y cómo se usan.
También quiero destacar que a pesar de existir esta técnica, no quiere decir que deba ser usada o que su uso esté completamente permitido.
Hay que entender que la POO no debería hacer uso de de los getter y setters ya que tendríamos acceso de forma directa al "estado del objeto" (
la información que tienen los atributos de un objeto) y permitir el acceso o modificación genera el mismo efecto de manipulación que si fuera una operación de "corazón abierto". Nuestro objeto debería estar protegido del exterior y no permitir tener acceso directo a sus atributos, y trabajar siempre sobre sus métodos ("los métodos definen el comportamiento del objeto"). En caso de no poder hacerlo, se podría priorizar disponer de "get" (obtener valor de un atributo de forma directa) y no "set" (modificar un valor directo de un atributo), y
en el peor de los casos, tener un uso muy controlado de los "set".Para más información sobre diseño OO, discusiones teóricas, buenas prácticas, etc, consultar escritos de gurúes como
Martín Fowler.
Artículo basado en una respuesta dada en Foros del Web