PHP para Iniciantes: Classes o Objetos - Propriedades

Quando vamos falar de propriedade de um objeto, nós normalmente pensamos em valores de uma instância... E é isso mesmo! Quando estiver estudando uma linguagem, o que você deve sempre se perguntar, em algum momento, é:

  1. Como declarar uma propriedade?
  2. É possível determinar o tipo de uma propriedade nessa linguagem?
  3. Essa linguagem possui controle de visibilidade?
  4. Como acesso o valor de uma propriedade de um objeto?
  5. Como altero o valor dessa propriedade?

São todas perguntas-chave que te farão entender mais rapidamente como a linguagem estudada funciona... Apesar de que nem todas tem o conceito literalmente chamado de "propriedade", você poderá encontrar a mesma intenção em nomes distintos.

Focando no PHP, você já sabe que a escrita de variáveis é precedida de $, certo? E que quando atribuímos um objeto a uma variável, na verdade, estamos apenas atribuindo uma referência ao objeto, o que significa que você nunca vai ter uma variável com um objeto irreferenciável.

Onde você quer chegar, Kiko?

Cachorrinho confuso

Com uma particularidade tão agressiva, é lógico que a forma que interagimos com o que quer que esteja "dentro" do objeto é diferente das outras coisas que você viu até então. Por exemplo, em array, você poderia acessar um índice dele apenas encapsulando o índice desejado entre colchetes ([ e ]) logo após acessá-lo. A frase ficou confusa? Então deixa que eu mostro na prática:

<?php

$matriz = [
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1],
];

// acessando a primeira linha da matriz
$primeiraLinha = $matriz[0];

// acessando a última coluna da primeira linha da matriz
$ultimaColuna = $primeiraLinha[2];
// OU
$ultimaColuna = $matriz[0][2]; // acessei o array $matriz, peguei o índice 0 - acessando outro array, depois peguei o índice 2

Quando eu quis acessar o array armazenado na variável $matriz, escrevi algo simples e fácil de entender, concorda? Mas a partir do momento que você precisa acessar um dado que está dentro de outro, o caos começa a crescer. Você deve evitar situações como essa sempre que possível. O código fica ilegível.

Agora voltando ao que estava falando sobre a particularidade de objetos... Para acessar um dado dentro de uma instância, você usa o operador de objetos (->, sinal de menos seguido do sinal de maior que), que é colocado imediatamente após um objeto. Ele irá te dar acesso ao que quer que exista na instância de acordo com a visibilidade atual. É literalmente $objeto-><alguma coisa aqui>.

Antes de te mostrar isso na prática, vamos responder aquelas perguntinhas pra seguir a ordem da evolução da minha linha de raciocínio?

Interrogações

1. Como declarar uma propriedade?

Quando falamos da declaração de propriedade, não estamos falando na criação de uma - sim, é possível criar uma propriedade não-declarada... E assim como isso é crime pra Receita Federal (quando falamos de imóveis), deveria ser crime aqui também.

Criminoso jogando tinta em câmera de vigilância

Estamos falando, na verdade, no ato de definir suas características antes mesmo de utilizá-la, logo, isso só se aplica na escrita de classes. Concorda? A classe define as características de como seus objetos irão se comportar e é sobre isso que estamos falando.

Existe duas formas de declarar uma propriedade em uma classe:

  1. a boa: {visibilidade} {opcional: tipo de dado primitivo} ${nome da propriedade};
  2. a ruim: var ${nome da propriedade}.

Você conhecia essa segunda forma? Cavernosíssima. Há muito tempo escrevíamos da segunda forma pois o entendimento comum era de que as propriedades são variáveis dentro de uma classe. Felizmente isso evoluiu, embora a linguagem aceite essa escrita até hoje.

Por favor, finja que só existe a primeira forma:

<?php

class Exemplo
{
    private string $propriedade;
}

1.1. Destrinchando a declaração de propriedade

1.1.1. Visibilidade

O primeiro texto, private, remete a privacidade, que podem ser as seguintes:

  • private: visibilidade privada, ou seja, somente métodos da classe onde estamos declarando podem acessar essa propriedade diretamente;
  • protected: visibilidade protegida, ou seja, somente métodos da classe onde estamos declarando e das classes que herdarem as propriedades dela poderão acessar diretamente;
  • public: visibilidade pública, todo mundo pode acessar diretamente.

Exemplos:

<?php

class Exemplo
{
    private string $myPrivate = "ola";
    protected string $myProtected = "mundo";
    public string $myPublic= "louco";

    public function getMyPrivate(): string
    {
        return $this->myPrivate;
    }
}

class ExemploHerdeiro extends Exemplo
{
    public function getMyProtected(): string
    {
        return $this->myProtected;
    }

    public function getMyPrivateWithError(): string
    {
        return $this->myPrivate;
    }
}

$exemplo = new Exemplo();
$exemploHerdeiro = new ExemploHerdeiro();

// se você descomentar a linha abaixo, receberá um erro:
// var_dump($exemplo->myPrivate);
// isso acontece porque, nesse escopo, fora da classe, essa propriedade não é visível
// então o interpretador dá um erro como se ela não existisse

// para acessar o valor de uma propriedade com visibilidade restringida, eu criei o
// método getMyPrivate, que retorna o valor dela:
var_dump($exemplo->getMyPrivate()); // string(ola)

// da mesma forma, se você descomentar a linha abaixo, também receberá um erro:
// var_dump($exemplo->myProtected);
// por ser protegida, é preciso expor seu valor em outro método
// como seu só escrevi esse método na classe herdeira (ExemploHerdeiro),
// então é preciso acessar por lá:
var_dump($exemploHerdeiro->getMyProtected()); // string(mundo)

// já a propriedade pública é acessível normalmente
var_dump($exemplo->myPublic); // string(louco)

// sendo pública, a classe herdeira também irá herdá-la de forma pública:
var_dump($exemploHerdeiro->myPublic);

// porém, a classe herdeira não consegue acessar nada que estiver privado na
// classe pai, como mostro no erro a seguir (descomente para ver):
// var_dump($exemploHerdeiro->getMyPrivateWithError());

1.1.2. Tipo de dado primitivo

Eu já te mostrei toda a seção sobre os tipos de dados primitivos existentes no PHP, certo? Você basicamente deve trabalhar com os tipos que sua propriedade irá aceitar.

Antes do PHP 8, você estava limitado a declarar somente um tipo por vez. Agora é possível declarar mais de um tipo aceito concatenando com o operador OU (|). Deixando isso mais flexível de modo a não precisar aceitar todos os tipos para aceitar mais de um. Mas mesmo sendo possível... Tente limitar sempre a um tipo, ok?!

1.1.3. Nomenclatura da propriedade

As regras são as mesmas de quando vamos escrever uma variável. Como eu já mencionei isso no artigo anterior (e acho que já ficou bem claro), vou evitar essa repetição, rs.

1.2. Outras novidades do PHP 8

Além dessas formas que te apresentei, a partir do PHP 8, passamos a ter novas possibilidades no que diz respeito a declaração de propriedades. Por exemplo: uma das dores de cabeça de escrever uma propriedade não-pública, é que muitas vezes somos forçados a escrever comandos get para dar acesso ao dado armazenado. Quando um dado é exposto somente para leitura, nós não poderíamos torná-lo public, pois isso iria expor, também, para escrita.

Agora nós podemos declarar um tipo de somente leitura informando a palavra-chave readonly.

1.2.1. Propriedade readonly

Essa definição deve ficar entre a visibilidade e o tipo de dado da propriedade, modificando aquela assinatura que escrevi mais cedo para {visibilidade} readonly {tipo de dado primitivo} ${nome da propriedade}. Simples, não? Com isso, você pode manter a propriedade pública, sem medo.

Quando declaramos uma propriedade readonly, o PHP irá limitar o escopo de alteração da mesma somente no processo de construção. Ou seja, se você tentar alterar o dado depois da instância estar construída, mesmo que executando dentro da própria classe, você terá um belo de um erro. Quer ver?

<?php // PHP 8.1

class Exemplo
{
    public readonly string $text;

    public function __construct(string $text)
    {
        // aqui podemos alterar
        $this->text = $text;
    }

    public function setText(string $newText): void
    {
        // aqui não podemos
        $this->text = $newText;
    }
}

$exemplo = new Exemplo("Ola mundo");
var_dump($exemplo->text); // string(Ola mundo)

// descomente para ver o erro
// $exemplo->setText("Adeus mundo");

// aqui também não podemos
// descomente para ver o erro
// $exemplo->text = "Adeus mundo";

Você pode rodar os códigos do PHP 8.1 no site 3v4l.org.

Ah, e quando eu digo que essa propriedade só pode ser escrita no construtor, significa que ela também não pode ter nenhum valor padrão.

<?php
class Exemplo
{
    public readonly int $vida = 42; // erro
}

Mesmo que não seja permitido alterar um dado, o readonly não impede mutação. Como você já sabe, objetos se tornam referências onde quer que você os atribua, seja em uma variável ou em uma propriedade.

Você não pode alterar de uma referência para outra, mas você pode alterar os valores dentro dessa referência:

<?php

class Exemplo
{
    public int $vida = 42;
}

class ExemploReadonly
{
    public readonly Exemplo $exemplo;

    public function __construct(Exemplo $exemplo)
    {
        $this->exemplo = $exemplo;
    }
}

$readonly = new ExemploReadonly(new Exemplo());
$readonly->exemplo->vida = 43; // permitido
var_dump($readonly->exemplo->vida);

// não permitido:
$readonly->exemplo = new Exemplo();

Então muito cuidado com o que você deixa readonly... Se sua intenção era proteger o dado dentro do objeto de ser alterado, era para aquele dado ser readonly também.

1.2.2. Declaração de propriedades via construtor

Se você já escreveu algumas classes em PHP, você já deve ter se deparado bastante com situações como essa:

<?php
class Exemplo
{
    private string $propriedade1 = "abc";
    private int $propriedade2 = 123;
    private array $propriedade3 = [];
    private float $propriedade4 = 3.21;

    public function __construct(
        string $propriedade1,
        int $propriedade2,
        array $propriedade3,
        float $propriedade4
    ) {
        $this->propriedade1 = $propriedade1;
        $this->propriedade2 = $propriedade2;
        $this->propriedade3 = $propriedade3;
        $this->propriedade4 = $propriedade4;
    }
}

Você não acha super redundante? Olha quantas vezes eu escrevi o mesmo nome só nesse trecho... E então, no PHP 8.0, trouxeram uma novidade que resolveu essa questão: declaração de propriedade em construtor.

Você só precisa incluir a visibilidade nos argumentos, então o interpretador sabe que precisa eleger aqueles argumentos como propriedades, ficando assim:

<?php

class Exemplo
{
    public function __construct(
        private string $propriedade1 = "abc",
        private int $propriedade2 = 123,
        private array $propriedade3 = [],
        private float $propriedade4 = 3.21
    ) {
    }
}

Bem mais simples, não?! Conseguiram unificar de uma vez só:

  1. necessidade de declarar propriedade;
  2. necessidade de declarar argumento;
  3. necessidade de atribuir o argumento à propriedade.

Magnífico.

Morgan Freeman batendo palmas

E com isso, você já sabe tudo o que precisaria saber sobre declaração de propriedades, o que nos leva à próxima pergunta.

2. É possível determinar o tipo de uma propriedade nessa linguagem?

Eu já respondi isso ao explicar que, na declaração da propriedade, é possível informar qual tipo de dados primitivos é esperado. Isso é realmente opcional. Se por algum motivo você não sabe com qual tipo irá lidar, é possível não colocar nada.

Logo, a resposta para essa pergunta é sim, é possível.

3. Essa linguagem possui controle de visibilidade?

Também respondi no momento em que mostrei em qual trecho da declaração nós determinamos a visibilidade da propriedade, confere? Nesse caso, informar a visibilidade também é opcional - exceto em declaração no construtor. Quando você não informa a visibilidade, o interpretador entende que é uma propriedade pública.

Mesmo que seja opcional, por favor, sempre determine qual a visibilidade desejada.

E a resposta para essa pergunta é sim, possui.

4. Como acesso o valor de uma propriedade de um objeto?

Lembra que mencionei o Operador de Objetos? Sinal de menos com sinal de maior que, ->. Esse operador serve para requisitar qualquer coisa de um objeto, incluindo uma propriedade. Se você escrever $objeto->nomeDaPropriedade, irá ler o dado da propriedade nomeDaPropriedade, se ela existir.

Veja um exemplo mais claro:

<?php // PHP 8.1

class Tomada
{
    public function __construct(
        public readonly string $corrente = '220v'
    ) { }
}

$tomada220 = new Tomada();
$tomada110 = new Tomada(corrente: '110v'); // named arguments, do PHP 8.0

// quanto é a corrente da tomada 220?
var_dump($tomada220->corrente); // string(220v)

// quanto é a corrente da tomada 110v?
var_dump($tomada110->corrente); // string(110v)

5. Como altero o valor dessa propriedade?

Usando o mesmo Operador de Objetos em conjunto com o Operador de Atribuição, é possível atribuir o valor a uma propriedade.

Basicamente, você só precisa acessar a propriedade que deseja atribuir um (novo) valor e colocar a atribuição que desejar. Por exemplo:

<?php // PHP 8.0
class Porta
{
    public function __construct(
        public string $status
    ) { }
}

const ABERTA = 'aberta';
const FECHADA = 'fechada';

$porta = new Porta(status: FECHADA);
// acessando:
var_dump($porta->status); // string(fechada)

// atribuindo:
$porta->status = ABERTA;

// confirmando, acessando novamente:
var_dump($porta->status); // string(aberta)

Você pode usar qualquer expressão de atribuição, até aquelas atribuições mescladas com outros operadores:

<?php
class Moeda
{
    public function __construct(
        public int $centavos
    ) { }

    public function saldo(): float
    {
        return $this->centavos / 100;
    }
}

$cinquenta = new Moeda(50);
$vinteECinco = new Moeda(25);
$dez = new Moeda(10);
$cinco = new Moeda(5);

$carteira = new Moeda(0.0);
$carteira->centavos += $cinquenta->centavos; // 50 centavos
$carteira->centavos *= 2; // 1 real
$carteira->centavos -= $dez->centavos; // 90 centavos
$carteira->centavos += $cinco->centavos * 2; // 1 real
$carteira->centavos /= $cinco->centavos; // 20 centavos
$carteira->centavos += $cinco->centavos; // 25 centavos

var_dump($carteira->saldo()); // float(0.25)

Legal, né? Hehe...

E então zeramos as perguntas, o que nos leva ao fim desse artigo. Curtiu?! Comenta e compartilha! Qual foi o melhor exemplo desse artigo? Quando eu estava escrevendo o código da tomada, estava pensando em criar um método tipo colocarODedo que responderia se você vive ou morre baseado na voltagem dela, hahaha. Mas acho que iria fugir muito do tema, então deixei pra lá.

Homem jogando papel amassado no lixo

E me conta aí: como tá sendo o aprendizado? Tem algum assunto que você tem muita dificuldade e quer ler em um artigo dedicado? Manda aí!

Inté!!