Propiedades en PHP

En este artículo abordaremos la cuestión de cómo simular el uso de propiedades en PHP. Utilizamos el término “propiedades” con el sentido que se le da en C#, esto es, el mecanismo por el cuál el acceso a los atributos de un cierto objeto se puede llevar a cabo a través de métodos de acceso, para lograr la encapsulación, pero de forma totalmente transparente desde fuera del objeto.

Supongamos por ejemplo que estamos diseñando una clase que represente a las personas de una familia, y que no queremos que el atributo edad pueda tener valores mayores de, digamos, 150 años, ya que está claro que esto se trata de un error. Una primera solución consistiría simplemente en hacer que el atributo fuera privado y que sólo se pudiera modificar a través de un método set. Añadamos también un método get para completar el ejemplo.

class Persona {
    private $edad;

    public function getEdad() {
      return $this->edad;
    }

    public function setEdad($edad) {
      if($edad < 150)
        $this->edad = $edad;
      else
        throw new Exception("La edad no es correcta");
    }
}

Una persona que hiciera uso de nuestra clase tendría que hacer algo así:

$p = new Persona();
$p->setEdad(20);
echo $p->getEdad();
$p->setEdad(200);

¿No sería mucho más conveniente usar la misma sintaxis que si accediéramos a los atributos directamente?

$p = new Persona();
$p->edad = 20;
echo $p->edad;
$p->edad = 200;

Hasta que se lance PHP 6, que puede que las implemente con una sintaxis similar a la de C#, tendremos que recurrir a simular nuestras propias propiedades. Esto es bastante sencillo gracias si hacemos uso de los métodos mágicos __get y __set.

Los métodos mágicos son funciones con nombre reservado, que comienzan siempre con __, y que se invocan automáticamente en determinadas circunstancias. Ejemplos conocidos son la función __construct, el constructor, que se ejecuta al crear el objeto y la función __toString, que se utiliza para crear una cadena que represente al objeto, cuando se intenta utilizar el objeto como si se tratara de una cadena.

Los métodos __get y __set, como es de esperar, se ejecutan al intentar acceder a un atributo que es inaccesible desde el contexto actual. Podríamos conseguir una funcionalidad similar a las propiedades de C# entonces utilizando un código similar al siguiente:

class Persona {
    private $edad;

    public function __get($nombre) {
      if($nombre == 'edad')
        return $this->edad;
    }

    public function __set($nombre, $valor) {
      if($nombre == 'edad')
        if($valor < 150)
          $this->edad = $valor;
        else
          throw new Exception("La edad no es correcta");
    }
}

La solución anterior requeriría añadir un if o un case por cada atributo que quisiéramos encapsular. El siguiente código es mucho más flexible. En él comprobamos si existe un método cuyo nombre comience con get o set y termine con el nombre del atributo, en cuyo caso se utilizará dicho método.

class Persona {
    private $edad;

    public function __get($nombre) {
      $metodo = 'get' . ucfirst($nombre);

      if (method_exists($this, $metodo))
        return $this->$metodo();
      else
        return $this->$nombre;
    }

    public function __set($nombre, $valor) {
      $metodo = 'set' . ucfirst($nombre);

      if (method_exists($this, $metodo))
        $this->$metodo($valor);
      else
        $this->$nombre = $valor;
    }

    public function setEdad($edad) {
      if($edad < 150)
        $this->edad = $edad;
      else
        throw new Exception("La edad no es correcta");
    }
}

Por supuesto, también podemos afinar un poco más llevando esta lógica a una clase padre que podemos extender cuando necesitemos hacer uso de “propiedades”:

abstract class ClasePropiedades {
    public function __get($nombre) {
      $metodo = 'get' . ucfirst($nombre);

      if (method_exists($this, $metodo))
        return $this->$metodo();
    }

    public function __set($nombre, $valor) {
      $metodo = 'set' . ucfirst($nombre);

      if (method_exists($this, $metodo))
        $this->$metodo($valor);
    }
}


class Persona extends ClasePropiedades {
    private $edad;

    public function getEdad() {
      return $this->edad;
    }

    public function setEdad($edad) {
      if($edad < 150)
        $this->edad = $edad;
      else
        throw new Exception("La edad no es correcta");
    }
}


Comentarios
  1. No es una solución perfecta, pero bueno.

    ¿Se os ocurren alternativas mejores?

    Responder

    • La alternativa es buena, – de hecho es la que utilizo -; quizá le haya faltado explicar y remarcar que dentro de los __set y __get, se debería hacer un control de acceso a las variables, no vaya a ser que por cualquier casualidad alguien modifique lo que no debe.

      Además, quizá también convendría comentar la convención de nombres de PHP, que pone un _ (underscore) antes de los atributos privados de una clase.
      Haciendo eso, distingo los atributos que son “privados/protegidos” y cosas internas del objeto, que no se deben tocar; de los atributos que se pueden modificar libremente.

      Utilizo esta solución en un ActiveRecord implementado 100% por mi, en el que los modelos definen las propiedades, y luego extienden de la clase del ActiveRecord; después, con los métodos mágicos modificas las propiedades/atributos que no comiencen por _ (los modificables), y se hace un uso flexible de los mismos.

      Para terminar, hay otra manera de implementar esto, pero sólo funciona con los atributos no estáticos (los estáticos a partir de php 5.3), y es utilizar el método mágico __call($method,$arguments) …
      para emular el Get y el Set, y redirigir las peticiones; pudiendo encapsular en ellas alguna regla (si son redirigidas a otro método) de acceso o bien la posiblidad de devolver un array o varios valores (por ejemplo, con atributos del estilo dirección).
      Es un poco más complejo, y añade un retraso porque _call debe hacer comprobaciones; pero a la larga sirve muy bien para implementaciones de clases con restricciones de acceso y de existencia.

      Saludos

      Responder

  2. David

    Hola!

    La verdad es que parece interesante la solución. Pero en el último caso, y pregunto por duda/desconocimiento ya que no dejo de ser junior en esto, no sería más lógico implementar una interfaz que extender (¿heredar?) de una clase sin respetar la cierta lógica que en este caso debería de existir ?

    Espero respuestas,

    Saludos.

    Responder

    • Una interfaz no es más que un contrato, no puede tener ningún tipo de implementación. Por eso utilizo una clase abstracta, indicando que la implementación no es completa y que no se puede instanciar la clase directamente.

      Responder

      • David

        Ok, ahora creo que lo entendí mejor, aunque intentaré esta tarde repasar el concepto de interfaz…

        Me ha parecido buena idea lo que proponen más abajo: poner ejemplos de POO (más allá de un framework que se aproxime) en PHP para así tener un mejor acercamiento en este lenguaje.

        Saludos.

        Responder

      • ¿Declara la clase como abstracta con el fin de que no se pueda instanciar y por que no esta completa su implementación? ¿No cree que es mejor declararla como final y simplemente con ello no se permitirá su instancia?

        Saludos

        Responder

        • Hola Ismael.

          Si declararas la clase como final, no podrías heredar de ella. Y precisamente quieres todo lo contrario, obligar a que tengan que extenderla.

          Responder

  3. Buen artículo,

    creo que el uso de __set y __get se debe hacer cuando queremos hacer algo más aparte del set y el get.

    El uso de los típicos get y set es para cuando queremos algo intermedio entre private y public, por ejemplo un nombre de usuario que puede ser leido pero no establecido, en ese caso haríamos el get pero no el set.

    Y de momento no hay muchas alternativas, habrá que esperar jeje.

    Responder

  4. Aether

    Tambien podría ser una solucion implementar de una manera mas rapida de esta otra forma(claro, obviando algunas reglas de negocio q pueda manejar el objeto):

    class Person {

    private $firstName;
    private $lastName;
    private $age;
    private $country;

    function __call($method, $arguments) {
    $prefix = strtolower(substr($method, 0, 3));
    $property = strtolower(substr($method, 3));

    if (empty($prefix) || empty($property)) {
    return;
    }

    if ($prefix == “get” && isset($this->$property)) {
    return $this->$property;
    }

    if ($prefix == “set”) {
    $this->$property = $arguments[0];
    }
    }
    }

    Bueno perdon si quedo un poco desordenado el codigo, pero puede ser una idea tambien 😛

    Responder

  5. Curro

    Buen tutorial, lástima que aun no encuentro una buena aplicación para utilizar programación orienta a objetos en PHP. ¿ustedes en qué tipo de aplicaciones han utilizado OOP? Agradecería algún buen ejemplo

    Gracias!

    Responder

    • daniel

      Pues aplicaciones especificas no sabría decir, pero si se que por ejemplo Zend Framework utiliza código 100% orientado a objetos, y con esta herramienta el tiempo de desarrollo de cualquier aplicación/proyecto se reduce notoriamente.

      La verdad no se manejar el framework, estoy empezando a aprender, pero veo que en el OOP de PHP se han adaptado muchas cosas de Java, si se fijan en los primeros códigos del articulo, de no ser por algunos caracteres de sintaxis diría que es Java. Eso me parece genial, porque el lenguaje de SUN siempre ha tenido bien claros sus directrices y estándares.

      A pesar de programar en PHP no entiendo muy bien el objetivo de este artículo, pues hasta ahora no he tenido necesidad de simular el uso de propiedades ni en PHP, ni en Java… supongo que será de utilidad para algunos.

      Saludos!

      Responder

      • linkamp

        El poblema de crear los metodos get y set para acceder a las atributos de un objeto es el tiempo que se tarda en ello. Con la mayoria de IDE en java se puede hacer de forma automatizada. No conozco ninguna herramienta o IDE que permite hacerlo en PHP, existe?

        Responder

        • Netbeans te permite hacerlo, por ejemplo. Comienzas a crear la clase y una vez en el ámbito de la clase pulsas Alt + Insert, que es el atajo para auto generar código. Te dará la opción de generar el constructor, métodos get, métodos set, o ambos.

          Responder

          • Linkamp

            Pues no lo sabia, gracias. Por desgracia en el trabajo utilizamos Eclipse.

    • linkamp

      En todas las que tengan más lógica que un simple CRUD, yo recomendaria utilizar objetos. Por varios motivos, pero básicamente la reusabilidad, separación de tareas, división de los problemas en partes pequeñas, …

      las aplicaciones CRUD són las tipicas que permiten grabar, borrar, leer, y modificar. E incluso en estas puede tener sentido trabajar con objetos en un patrón DAO.

      Responder

    • Andrés

      Yo tengo en constante desarrollo un framework web PHP 100% basado en objetos, y utilizando multitud de patrones de diseño.

      Responder

      • ¿Tienes el código publicado en alguna parte, por curiosidad?

        Responder

        • Sí; en http://github.com/andreums/framework1.5 , aunque no está actualizado el código del repositorio y es un proyecto “medio secreto”, ya que es mi proyecto final de carrera, y hasta que no termine no puedo liberarlo al público.

          Por otra parte, puedes probar http://github.com/andreums/WebWork , que es una app web hecha con mi framework para las prácticas de una asignatura (http://webwork.hostoi.com/) que si bien es una web sencilla para la universidad, utiliza mi framework al 100%.

          Básicamente casi todo el código de mi framework está escrito por el que escribe, y al ser un framework que es más bien personal o sencillo (por el momento), busca resolverme las aplicaciones webs personales, para amigos, y para algunos de los clientes de la empresa en la que trabajo.
          Eso sí, incluye multitud de facilidades como reescritura de URLs automática, enrutador de peticiones, ActiveRecord, …
          muchas cosas.

          Cuando termine la carrera liberaré el código del framework bien documentado y con una web/blog que hable de él; de momento es algo secreto y que funciona.

          Muchas gracias por interesarte por mi framework 🙂
          Un saludo.

          Andrés

          Responder

          • Muchas gracias a ti por el trabajo. Habrá que echarle un vistazo 🙂

        • Sí, está en github.com/andreums/framework1.5

          Saludos

          pd: ¿No ha enviado mi comentario anterior? ¿Lo ha puesto en SPAM?

          Responder

          • Pues sí, lo había tomado como spam Akismet, por alguna extraña razón.

  6. jmarfil

    ‘¿No sería mucho más conveniente el poder acceder a los atributos directamente?’

    Te aseguro que no.

    Si utilizas una clase, se entiende que quieres hacer POO, y poder acceder a los datos miembros directamente es un ‘guarrada’, con perdón.

    No entiendo muy bien el sentido del artículo.
    Si lo que quieres es exponer la forma de hacer una interface get/set para digamos 20 o mas datos, me parece correcto, pero NO SIEMPRE es interesante/bueno/elegante tener un get/set por cada atributo

    Responder

    • daniel

      Lo que dices de que “no siempre es interesante/bueno/elegante tener un get/set por cada atributo” no es correcto, aunque yo a veces por pereza no lo hago como debería, te argumento con una de las buenas practicas en POO, Cito:

      *** Solo los métodos constructores y métodos de acceso o métodos de modificación pueden acceder directamente las variables de instancia de la clase.***

      Por convención, inclusive dentro de la misma clase, si se desea cambiar el valor de una variable deberán utilizarse para esto los métodos modificadores y de acceso.

      Espero dejar de cometer ese error, ya que como dices, si tengo por decir 30 atributos puede resultar un poco tedioso, aunque así debe de hacerse.

      Saludos!

      Responder

      • Losky

        Yo estoy de acuerdo con que no siempre es conveniente tener un getter y setter para todas las propiedades. Cual es la diferencia en tener un setter para cada propiedad y tener acceso publico a las propiedades ?

        Los setter tienen que existir para mantener la consistencia del objeto.
        Así, en el caso de una instancia de dirección, con propiedades País, Localidad, Calle, etc., estaría mal que uno cambie el País, sin cambiar la localidad.
        Para salvar el caso, no sería correcto tener un setter por cada propiedad, sino más bien por ejemplo:
        setDirección(Calle, Numero);
        setDirección(Localidad, Calle, Numero)
        setDirección(País, Localidad, Calle, Numero)

        Esto si mantiene una consistencia interna del objeto al no permitir que cambies el país de residencia sin indicar también la localidad calle y número. Cual sería el caso en que algo cambie de país pero no de localidad ? Es correcto tener un setter para la propiedad País ?

        Saludos

        Responder

        • Lo que ocurre es que nunca sabes cuándo van a cambiar los requisitos.

          Puede que un cierto atributo no necesite ningún tipo de validación ni cálculo en este momento, pero que pase a necesitarlo en un futuro próximo.

          Por eso son tan interesantes las propiedades. Puedes insertar un método que haga de intermediario en el futuro sin que el usuario tenga que refactorizar su código, y sin que se de cuenta ni si quiera.

          Responder

    • Programador Java

      Siempre es interesante, bueno y elegante utilizar métodos get y set. Es uno de los principios básicos de la orientación a objetos: el encapsulamiento.

      Si no ves utilidad a las propiedades de C#, debe ser por no haberlas utilizado nunca, porque yo las hecho en falta cada día 😉

      Responder

    • David

      No te has enterado de nada. No se accede a los atributos directamente. Se hace a través de métodos como tiene que ser. La gracia está en que el programador que hace uso de tu clase no lo sabe porque es totalmente transparente.

      De esta forma podrías permitir el acceso directamente en un principio y luego cambiarlo a usar un get y un set si se hace necesario sin que el usuario de la clase tenga que adaptar una sola línea de código. Por ejemplo.

      Responder

  7. linkamp

    Yo lo hago con un sistema parecido al que comentas en el post. Aunque algo personalizado. Pongo el código aquí por si le sirve de ayuda a alguien.

    abstract class Bean {

    protected $_data = array ();

    public function __construct (){
    $fields = $this->getFields();

    for($i=0;$i= func_num_args()){
    $value = func_get_arg($i);
    $this->_setField($fields[$i], $value);
    }
    }

    }

    public function __call ($fn, $args){
    $type = substr ($fn, 0, 3);
    $name = strtolower (substr ($fn, 3));

    if ($type == ‘get’) {
    return $this->_getField ($name);
    }

    if ($type == ‘set’) {
    return $this->_setField ($name, $args[0]);
    }

    if ($type == ‘has’) {
    return $this->_setField ($name, $args[0]);
    }

    $type = substr ($fn, 0, 2);

    if ($type == ‘is’) {
    return $this->_setField ($name, $args[0]);
    }

    throw new Exception (“Function {$fn} is not implemeted.”);
    }

    public function __get($name){
    return $this->_getField ($name);
    }

    public function __set($name, $value) {
    $this->_setField ($name, $value);
    }

    protected function _getField ($name){
    $this->_checkField ($name);

    return isset ($this->_data[$name]) ? $this->_data[$name] : null;
    }

    protected function _setField ($name, $value){
    $this->_checkField ($name);

    $this->_data[$name] = $value;
    return $this;
    }

    private function _checkField ($field){
    if (!in_array ($field, $this->getFields())) {
    throw new Exception (“Field {$field} is invalid.”);
    }
    }

    protected function /*Array*/ getFields(){
    if(isset($this->_fields)){
    return $this->_fields;
    }else{
    return array();
    }
    }

    }

    class ExampleBean extends Bean {
    protected $_fields = array (‘id’, ‘name’);
    }

    $bean = new ExampleBean(1, “Example1″);

    Responder

    • linkamp

      la última linea esta mal és
      $bean = new ExampleBean(1, “Example1″);

      Responder

  8. mario

    Una de las máximas de la POO es que no puedes acceder directamente a los atributos de una clase, para eso están los métodos de dicha clase, intentar lo contrario me parece una chapuza o una absurdez tremenda.
    Simplemente el hecho de plantear tal posibilidad hace ver que no se tiene mucha idea de POO o bien realmente no necesitas utilizar la POO para llevar a cabo tu proyecto, analiza si realmente necesitas POO y si es así, ya sabes a lo que atenerte.

    Responder

    • En serio, leed el artículo antes de comentar, que molesta mucho tener que explicar lo mismo varias veces.

      NO se accede directamente a los atributos. A poco que te hubieras fijado en el código te habrías dado cuenta.

      Se utilizan métodos get y set, pero se combina con el uso de los métodos mágicos __get y __set para que desde fuera parezca que se accede directamente.

      Responder

  9. […] Propiedades en PHP […]

    Responder

Deja un comentario