Interfaces em PHP

Artigo que explica o conceito de Interfaces no paradigma orientado a objetos do PHP, recurso crucial para a característica de polimorfismo do paradigma.

"Interface", no Paradigma Orientado a Objetos, representa uma estrutura que especifica um conjunto de métodos que uma família de classes terá. Nesta especificação são informados os nomes dos métodos, visibilidade e os respectivos parâmetros, mas nenhum deles são implementados na interface. Diz-se que uma classe "implementa" uma interface quando ela possui os métodos previstos na interface e declara, explicitamente, implementá-la.

O propósito principal das interfaces está diretamente relacionado ao polimorfismo. Através deste recurso, uma estrutura (função, método ou trecho de código em escopo global) consegue trabalhar com um objeto sem saber exatamente de que classe ele é, mas conhecendo uma estrutura base que este objeto possui. Esta estrutura base pode ser uma classe base ou uma interface. Isso porque a estrutura está interessada apenas em utilizar alguns recursos do objeto, e que são previstos na classe base ou interface. Desta forma, nenhum método mais específico é utilizado.

Vejamos um exemplo prático. Primeiramente, como criar uma interface chamada "Cache", que representa um conjunto de métodos esperados por uma classe que consegue guardar e recuperar dados em cache através de um identificador da informação:

interface Cache {

    /**
     * Indica se uma informacao esta guardada em cache
     * @param mixed $id Identificador da informacao
     * @return bool
     */
    public function em_cache($id);

    /**
     * Armazena uma informacao em cache, rotulado com um identificador.
     * Passando NULL como valor, o valor e' apagado.
     * @param mixed $id Identificador da informacao
     * @param mixed $valor Valor a ser armazenado
     * @return bool
     */
    public function set_valor($id, $valor);


    /**
     * Obtem uma informacao da cache atraves de um identificador
     * @param mixed $id Identificador da informacao
     * @return mixed
     */
    public function get_valor($id);
}

É importante que a especificação de uma interface seja a mais simples possível, para que todas as classes que pretendam realizar o propósito, consigam fazer isso sem "empacar" em como implementar determinado método.

Agora vejamos dois exemplos de classes que implementam a interface Cache: a CacheArquivo e a CacheSessao. Cada uma irá armazenar as informações em um lugar diferente, mas o funcionamento de ambas é idêntico a nível de interface.

Classe CacheArquivo vai guardar os dados em arquivos no diretório "/tmp/cache_arquivo".

class CacheArquivo implements Cache {

    /**
     * {@inherit}
     */
    public function em_cache($id) {
        return file_exists('/tmp/cache_arquivo/'.strval($id));
    }

    /**
     * {@inherit}
     */
    public function set_valor($id, $valor) {
        if (!is_dir('/tmp/cache_arquivo')) {
             if (!mkdir('/tmp/cache_arquivo')) {
                 return false;
             }
        }

        // Se e' um valor: armazenar
        if (!is_null($valor)) {
            $arq = '/tmp/cache_arquivo/'.strval($id);
            $conteudo = serialize($valor);
            return file_put_contents($arq, $conteudo);

        // Se nao e' um valor: apagar
        } else {
            $arq = '/tmp/cache_arquivo/'.strval($id);
            return unlink($arq);
        }
    }


    /**
     * {@inherit}
     */
    public function get_valor($id) {
        if (!$this->em_cache($id)) {
            return null;
        }
        $conteudo = file_get_contents('/tmp/cache_arquivo/'.strval($id));
        $valor = unserialize($conteudo);
        return $valor;
    }
}

Classe CacheSessao vai guardar os dados em $_SESSION.

class CacheSessao implements Cache {

    /**
     * Inicializa a sessao, caso nao tenha sido inicializada
     * @return void
     */
    private function iniciar_sessao() {
        if (!session_id()) {
            session_start();
        }
    }

    /**
     * {@inherit}
     */
    public function em_cache($id) {
        $this->iniciar_sessao();
        return array_key_exists(strval($id), $_SESSION['cache_sessao']);
    }

    /**
     * {@inherit}
     */
    public function set_valor($id, $valor) {
        $this->iniciar_sesso();

        // Se e' um valor: armazenar
        if (!is_null($valor)) {
            $conteudo = serialize($valor);
            $_SESSION['cache_sessao'][strval($id)] = $conteudo;
            return array_key_exists(strval($id), $_SESSION['cache_sessao']);

        // Se nao e' um valor: apagar
        } else {
            unset($_SESSION['cache_sessao'][strval($id)]);
            return !array_key_exists(strval($id), $_SESSION['cache_sessao']);
        }
    }


    /**
     * {@inherit}
     */
    public function get_valor($id) {
        $this->iniciar_sessao();
        if (!$this->em_cache($id)) {
            return null;
        }
        $conteudo = $_SESSION['cache_sessao'][strval($id)];
        $valor = unserialize($conteudo);
        return $valor;
    }
}

Observe que a classe que implementa uma interface pode ter outros métodos além daqueles previstos na interface. Aliás, a classe pode, inclusive, implementar mais de uma interface (basta separá-las por vírgula).

Finalmente, podemos criar uma função que recebe por parâmetro uma classe que implemente Cache. A função não precisa saber se é CacheArquivo ou CacheSessao, o importante é que o objeto vai guardar as informações desejadas e recuperá-las quando solicitado.

function guardar_xy(Cache $cache, $x, $y) {
    $cache->set_valor('x', $x);
    $cache->set_valor('y', $y);
}

Ou seja, a função guardar_xy sabe que o objeto recebido tem o comportamento de uma cache e, portanto, tem o método set_valor. Com isso, a função chama o método sem medo de erro de sintaxe, pois a "indução de tipo" utilizada na assinatura da função já garante que o código só será executado se, em tempo de execução, for passado um objeto de classe que implemente a interface.

Além disso, interfaces também podem estender outras interfaces. Isso permite que um comportamento possa ser especificado de uma maneira simples e, de acordo com detalhes mais específicos, ser estendido na forma de outra especificação.

0 comentários