Levei alguns anos para compreender o que são classes e objetos, e levei mais alguns anos para decidir utilizá-los.
Um objeto é, em um sentido, semelhante a um array. Mas enquanto em um array costumamos criar e eliminar chaves dinamicamente, normalmente um objeto já possui uma estrutura definida de “propriedades“.
Quem define a estrutura de um objeto é a classe a partir da qual o objeto é criado.
Exemplo de uma classe com algumas propriedades:
class document { // document public $title; public $content; } // document
Para criar um objeto desta classe, utilizamos a instrução “new“:
$my_document = new document();
Podemos atribuir conteúdo às propriedades do objeto assim como obter seus valores:
$my_document->title = "Bem-vindo"; print $my_document->title;
Se fosse apenas por isto, não faria diferença utilizar um array ou um objeto, visto que eu poderia fazer exatamente a mesma coisa com um array:
$my_document['title'] = 'Bem-vindo'; print $my_document['title'];
Mas um objeto não contém apenas propriedades, eles também possuem funções vinculadas a si. Isto quer dizer que os objetos não apenas carregam informações, como eles também podem “fazer” coisas.
As funções que um objeto pode executar são chamadas de “métodos”, e são definidos pela classe que deu origem ao objeto:
class document { // document public $title; public $content; public function render() { // convert to html $buffer = "<html>\r\n<head>\r\n<title>'; $buffer .= $this->title; $buffer .= "</title>\r\n</head>\r\n<body>\r\n<h1>"; $buffer .= $this->title; $buffer .= "</h1>\r\n"; $buffer .= nl2br ($this->content); $buffer .= "</body>\r\n</html>"; return $buffer; } // convert to html } // document
Observe que o método é definido dentro da classe. Quando o método é invocado, a variável $this passa a representar o objeto que invocou o método.
A partir do exemplo acima, poderíamos criar a nossa página assim:
$my_document = new document(); $my_document->title = 'Olá mundo'; $my_document->content = 'Eu estou prestes a me tornar um programador de verdade!'; print $my_document->render();
Mas isto ainda não é tão excitante. Poderíamos ter a função render() que realizasse o mesmo processo, bastando fazer:
print render ($my_document);
Mas vamos agora mergulhar na magia do PHP, e os objetos nunca mais serão os mesmos!
O primeiro método mágico que vamos ver é o __construct(). Se você definir este método em sua classe, ele será chamado sempre que um novo objeto for criado a partir dela.
class document { // document public $title; public $content; public function __construct ($name) { // magic construct $string = file_get_contents ($name . '.txt'); $array = string2array ($string); $this->title = $array['title']; $this->content = $array['content']; } // magic construct public function render() { // render ... } // render } // document
Agora começa a ficar interessante pois, ao invocarmos algo como:
$my_document = new document('index');
Isto fará com que o arquivo “index.txt” seja aberto, e seu conteúdo seja colocado nas propriedades do objeto.
print $my_document->render();
e você terá um documento HTML com o conteúdo do arquivo.
O próximo método mágico que vamos ver é o __destruct(). Se este método for declarado, ele será chamado quando o objeto for destruído. E um objeto é destruído quando utilizamos o uunset(), quando o removemos de uma variável ou quando o script termina.
class document { // document public $title = ''; public $content = ''; private $file_name; private $data = array(); public function __construct ($name) { // magic construct $this->file_name = $name . '.txt'; if (is_file ($this->file_name)) { // file exists $string = file_get_contents ($this->file_name); $this->data = string2array ($string); if (isset ($this->data['title'])) $this->title = $this->data['title']; if (isset ($this->data['content'])) $this->content = $this->data['content']; } // file exists } // magic construct public function render() { // render ... } // render public function __destruct () { // magic destruct $current_data = array(); if ($this->title) $current_data['title'] = $this->title; if ($this->content) $current_data['content'] = $this->content; if ($this->data and !$current_data) unlink ($this->file_name); elseif ($current_data != $this->data) file_put_contents ($this->file_name, array2string ($current_data)); } // magic destruct } // document
Temos aqui algumas novidades. A primeira delas é que “inicializamos” as propriedades do nosso objeto com valores padrão, ou seja, determinamos um valor inicial para as propriedades do objeto. Veja no “Manual do PHP” em “Referência da linguagem” -> “Classes e objetos” -> “Propriedades“.
Outra novidade é a palavra-chave protected, que não permite que estas variáveis sejam acessadas, a não ser dentro dos métodos do próprio objeto, ou seja, através da variável $this. Veja no “Manual do PHP” em “Referência da linguagem” -> “Classes e objetos” -> “Visibilidade“.
Optamos por verificar com is_file() se o arquivo que procuramos existe ou não. Apenas se existir nós faremos o procedimento de abrí-lo e carregar o seu conteúdo para as variáveis $document->title e $document->content. Também guardamos o nome do arquivo em $this->file_name e uma cópia do arquivo original em $this->data para uso posterior.
Já sabemos o que acontecerá quando o arquivo for encontrado: seu nome e seu conteúdo serão guardados e o título será separado do restante do arquivo, certo? Mas, e se o arquivo não existir?
Neste caso, o nome do arquivo será guardado, mas as outras variáveis ficarão em branco. Como as inicializamos com o valor ”, este será o valor retornado.
$my_file = new_file ('arquivo_invalido'); print $my_file->title; // não imprimirá nada.
Vamos agora para o método __destruct(). As primeiras instruções parecem esquisitas, pois vamos juntar novamente o título guardado em $this->title com o conteúdo guardado em $this->content. Isto deveria “recompor” o conteúdo original do arquivo.
Mas para quê recompor o conteúdo do arquivo, se já temos uma cópia em $this->data?
É justamente para saber se nosso documento sofreu alguma alteração, comparando o conteúdo original com aquele que acabamos de reconstruir. Se o arquivo original for diferente do arquivo reconstruído, então ele deverá ser salvo.
Com sorte, file_put_contents() criará um novo arquivo caso ele não exista. Então, se tentarmos criar um objeto $document a partir de um arquivo inexistente, seu conteúdo ficará vazio. Mas, tão logo fizermos alguma alteração, então ele será gravado.
$document = new document('index') ; print $document->title; // não imprime nada pois o arquivo não existe. $document->title = 'Olá mundo'; $document->content = 'Eu estou prestes a me tornar um programador de verdade!'; unset ($document); // irá invocar o método mágico __destruct().
Agora o arquivo “index.txt” foi criado com duas linhas, respectivamente:
'title'='Olá mundo' 'content'=' Eu estou prestes a me tornar um programador de verdade!'
Se criarmos novamente um objeto document(‘index’), agora o conteúdo salvo estará disponível:
$document = new document('index'); print $document->render();
Mas tem mais um truquezinho escondido na manga da nossa classe document(): se o arquivo foi aberto e nós apagarmos o seu conteúdo, então o arquivo será removido!
$document = new document('index'); $document->title = ''; $document->content = ''; unset ($document);
Agora “index.txt” foi removido!
Resumindo:
- se criamos um objeto document() a partir de um nome inexistente, o objeto ficará vazio.
- Se o objeto permanecer vazio, nada será feito.
- Se um objeto vazio for preenchido com dados, então um arquivo de dados será criado.
- Se o objeto for criado a partir de um arquivo existente, então seu conteúdo será carregado para o objeto.
- Se um objeto estiver preenchido com dados de um arquivo existente, mas os dados forem alterados, então as alterações serão salvas.
- Se um objeto estiver preenchido com dados de um arquivo existente, e removermos os dados do objeto, então o arquivo será removido.
Podemos utilizar este raciocínio para adicionar, recuperar, alterar e remover dados de um banco de dados, assim como podemos incrementar nossa classe para que ela possua vários campos (propriedades) e funções (métodos).
Uma tarefa interessante seria um método para limpar todos os campos do objeto de uma única vez, forçando o arquivo a ser removido. Partindo do nosso exemplo:
class document { // document ... public function clear() { // clear $this->title = ''; $this->content = ''; } // clear } // document
Nossa programação fica assim:
// para criar um arquivo: $document = new document ('teste'); $document->title = $_POST['title'] // recebe o conteúdo do campo "title" de um formulário. $document->content = $_POST['content'] // recebe o conteúdo do campo "content" de um formulário. // observe que, se o formulário for submetido vazio, o documento não será criado // para imprimir o documento: print $document->render(); // para limpar o documento e removê-lo $document->clear();
Para incrementar nossa classe, não precisamos definir uma infinidade de propriedades, pois o valor de uma propriedade pode ser um array. Assim, o conteúdo de um arquivo pode ser carregado como um array em uma propriedade, e tudo ficará mais simples:
class document { // document public $scope; protected $file_name; protected $data = array(); public function __construct ($name) { // magic construct $this->file_name = $name . '.txt'; if (is_file ($this->file_name)) $this->data = string2array (file_get_contents ($this->file_name)); $this->scope = $this->data; } // magic construct public function clear () { // clear $this->scope = array(); } // clear public function render() { // render ... } // render public function __destruct () { // magic destruct if ($scope != $data) { // file changed if ($scope) file_put_contents ($this->file_name, array2string ($this->scope)); else unlink ($this->file_name); } // file changed } // magic destruct } // document
A diferença principal é que, ao invés de acessarmos o conteúdo do documento como $document->title ou $document->content, agora precisamos fazer $document->scope[‘title’] e $document->scope[‘content’]. Mas isto não é um problema, pois a nossa função render_tags() utiliza um array como conteúdo do documento, então, podemos adaptar a nossa classe para chamar render_tags() usando seu próprio conteúdo como argumento.
Não vamos entrar em mais detalhes, pois por hora queremos apenas nos familiarizar com uma linha de raciocínio, afinal, apesar de este exemplo ser legal, nosso sistema exige construções um pouco diferentes.
O uso de classes e objetos traz para a programação novos elementos. Você deve ter percebido que programar pode se tornar bastante “mágico” – como o exemplo que mostramos aqui, abrimos e salvamos dados sem sequer nos preocupar se o arquivo existe ou não, se precisará ser criado, salvo ou removido. Outro elemento importante é que “modelamos” a linguagem ao batizar propriedades e métodos, o que irá determinar como realizaremos as ações posteriores.
Mas, ao mesmo tempo, toda essa magia e modelagem poderá se tornar um inferno se nossos programas não forem adequadamente documentados – e pior ainda se não deixarem transparecer alguma lógica. A magia se tornará “magia negra“!
É por isto que se torna essencial uma documentação detalhada e um cuidado exaustivo com a lógica. Se alguém encontrar maneiras mais claras de documentar ou batizar classes, propriedades e métodos, por favor, manifeste-se!