No definas atributos con tipo en PHP

Author
Por Darío Rivera
Publicado el en PHP

En PHP tenemos la posibilidad de agregar el tipo a los atributos de una clase. Esta es una gran característica de PHP puede ser utilizada desde PHP 7.4. Sin embargo, hay que saber cómo utilizarla para evitar algunos errores que puede traer consigo este feature.

Definir un atributo con tipo es tan sencillo como colocar el tipo antes de nombre de la variable así

protected string $name;

En este punto, es muy importante que te preguntes si este dato va a ser en algún momento nulo o siempre será un string, ya que si intentas acceder a él sin ser inicializado obtendrás un error como el siguiente:

$name must not be accessed before initialization

Este es un error muy común y es muy sencillo de obtener. En adelante, utilizaremos variaciones del siguiente ejemplo para mostrar nuestro punto. Observa cómo podría ser causado este error.

class Request
{
    protected string $body;

    public function getBody(): string
    {
        return $this->body;
    }
}

$request = new Request();
$request->getBody();     // acceso sin inicializar el atributo

No hay la menor duda, de que esto hace nuestro código mucho más propenso a errores!. Cuando utilizamos tipos en PHP o en cualquier otra lenguaje de programación siempre tenemos que asegurarnos de inicializar las variables!. Este es el clásico problema de olvidar manejar las referencias nulas expuesto por Tony Hoare en The billion Dollar Mistake.

Atributos requeridos

Una forma de evitar este error, es pensar si el atributo es requerido. En dado caso, la solución sería recibirlo en el constructor así:

class Request
{
    protected string $body;

    public function __construct(string $body)
    {
        $this->body = $body;
    }

    public function getBody(): string
    {
        return $this->body;
    }
}

Ya que el dato es requerido en el constructor como string, la comprobación de tipo se haría desde la creación del mismo objeto, por lo que ya no es responsabilidad de esta clase asegurarse que el atributo body fue inicializado. Con esto, ya no obtendríamos un error al acceder a dicho atributo puesto que siempre se inicializa en el constructor.

Atributos no requeridos

Cuando un atributo no es requerido lo más recomendable es siempre inicializar el atributo a un valor por defecto, sea nulo, cero, vacío, etc. Veamos como quedaría nuestro ejemplo con esta premisa.

class Request
{
    protected ?string $body = null;

    public function getBody(): ?string
    {
        return $this->body;
    }

    public function hasBody(): bool
    {
        return (bool) $this->body;
    }

    public function setBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }
}

Si no inicializamos el atributo a un valor, tendríamos que verificar cada vez si el atributo ha sido inicializado y esto agregaría una lógica extra añadiendo más responsabilidad de la debida. Veamos como sería.

class Request
{
    protected ?string $body;

    public function getBody(): ?string
    {
        return $this->body ?? null;
    }

    public function hasBody(): bool
    {
        return (bool) ($this->body ?? false);
    }

    public function setBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }
}

Cada vez que querramos utilizar dicho atributo, tendríamos que hacer uso del operador de fusión de null.

$this->body ?? null

Esto hace el código mucho más frágil, ya que depende del programador agregar cada vez dicha comprobación. Si usas PHP con una versión anterior a la 7 el código sería aún más escandaloso.

isset($this->body) ? $this->body : null

Atributos sin Tipo

En última instancia, siempre podrás usar atributos sin tipo. Incluso de esta forma, es mucho menos frágil el código ya que por defecto el atributo será nulo y solo podrá ser modificado con una función setter que verifique el tipo de dato.

class Request
{
    protected $body;

    public function getBody(): ?string
    {
        return $this->body;
    }

    public function hasBody(): bool
    {
        return (bool) $this->body;
    }

    public function setBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }
}

En conclusión, es recomendable utilizar atributos con tipo siempre que estos sean inicializados o esté asegurada su inicialización en la función constructora. De otro modo, el tipo puede ser verificado con una función setter siempre que no sea requerido y no usar variables con tipo.


Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.