Controladores


1Apresentação

Os controladores são objetos PHP que devem atender a 4 critérios:

  • Herdar do objeto \Temma\Web\Controller.
  • Ter um nome em StudlyCase (primeira letra em maiúscula, o restante em minúscula, cada palavra começando com maiúscula, sem underscore): ArticleViewer, ApplicationCms, MobileAppLibrary.
  • Estar contido em um arquivo cujo nome seja exatamente o do objeto, com o sufixo ".php".
  • Ser acessível pelo autoloader:
    • Se o objeto não estiver em um namespace, o arquivo deve ser colocado no diretório controllers/ do projeto.
    • Se o objeto estiver em um namespace, o arquivo deve ser colocado:
      • em uma estrutura de diretórios dentro do diretório controllers/ ou do lib/ do projeto,
      • ou em uma estrutura de diretórios colocada dentro de um caminho declarado pela diretiva de configuração includePaths,

As ações são os métodos públicos do controlador, e seus nomes devem começar com letra minúscula.


2Exemplo

/** Controlador de usuários. */
class User extends \Temma\Web\Controller {
    /**
     * Ação raiz.
     * Chamada para a URL "/user".
     */
    public function __invoke() {
        // ...
    }
    /**
     * Ação de exibição.
     * Chamada para URLs como "/user/show/123".
     * @param  int  $id  Identificador do usuário a ser exibido.
     */
    public function show(int $id) {
        // ...
    }
    /**
     * Ação padrão.
     * Chamada quando a ação solicitada não existe.
     * @param  string $name    Nome da ação solicitada.
     * @param  array  $params  Lista dos parâmetros possíveis.
     */
    public function __call($name, $params) {
        // ...
    }
}
/** Controlador de artigos. */
class Article extends \Temma\Web\Controller {
    /** Inicialização, chamada antes da ação. */
    public function __wakeup() {
        // ...
    }
    /** Finalização, chamada depois da ação. */
    public function __sleep() {
        // ...
    }
    /**
     * Ação proxy, chamada sempre.
     * @param  string $name    Nome da ação chamada.
     * @param  array  $params  Lista dos parâmetros fornecidos.
     */
    public function __proxy($name, $params) {
        // ...
    }
}

3Raiz, proxy e padrão

É possível definir um controlador raiz, um controlador proxy e um controlador padrão (veja configuração):

  • O controlador raiz (diretiva rootController) é chamado quando nenhum controlador foi explicitamente solicitado. Ele é usado para definir o controlador que gerencia a página inicial do site.
  • O controlador proxy (diretiva proxyController) é sempre chamado, mesmo que o controlador chamado exista. Ele é usado para contornar completamente o framework, para criar seu próprio gerenciamento de todas as URLs.
  • O controlador padrão (diretiva defaultController) é chamado quando o controlador solicitado não existe. Ele permite definir um controlador que gerencia URLs que não são conhecidas de antemão.

Um controlador também pode conter uma ação raiz, uma ação proxy e uma ação padrão:

  • A ação raiz é definida com o método mágico __invoke().
    Ela é usada quando nenhuma ação foi explicitamente solicitada (por exemplo, /myController).
  • A ação proxy é definida com o método mágico __proxy().
    Se ela existir, é sempre chamada, mesmo que a ação solicitada exista.
  • A ação padrão é definida usando o método mágico __call().
    Ela oferece a possibilidade de um controlador gerenciar URLs que não são conhecidas de antemão, além de suas ações especificadas.

Por exemplo, se o objeto User contiver os métodos __invoke(), show() e __call(), ele poderá ser chamado com as seguintes URLs:

  • http://mysite.com/user => usa a ação raiz (o método __invoke())
  • http://mysite.com/user/show => usa a ação show (o método show())
  • http://mysite.com/user/list => usa a ação padrão (o método __call)
  • http://mysite.com/user/add => usa a ação padrão (o método __call)

Se esse objeto contivesse um método __proxy(), ele seria sempre chamado.

O método __invoke() não recebe nenhum parâmetro.
Os métodos __proxy() e __call() esperam dois parâmetros:

  1. Uma string contendo o nome da ação que foi solicitada.
  2. Um array contendo a lista de parâmetros que deveriam ter sido passados para a ação.

Em relação ao carregamento de templates: se nenhum template for explicitamente definido (usando o método _template() ou o atributo Template), o Temma usa um arquivo cujo nome corresponde ao nome da ação, em um subdiretório com o nome do controlador.

  • Para a ação raiz __invoke(), o template carregado será templates/Controller/__invoke.tpl.
  • Para a ação padrão __call(), o template corresponderá ao nome da ação solicitada na URL (e não __call.tpl): por exemplo, a URL /user/list tratada por __call() usará o template templates/User/list.tpl.
  • Para a ação proxy __proxy(), o template usado também corresponderá ao nome da ação solicitada na URL (e não __proxy.tpl).

O uso dos métodos mágicos do PHP dessa forma não é um problema e não gera ambiguidade. Isso oferece uma separação clara entre as ações "normais" e as ações raiz/proxy/padrão.


4Inicialização

Um controlador pode conter o método mágico __wakeup() (que não recebe nenhum parâmetro).

Ele será chamado automaticamente antes que a ação seja chamada, o que pode ser usado para inicializar o controlador. Assim, é possível inicializar atributos privados do controlador, instanciando objetos que poderão ser usados em todas as ações.

Esse método pode não retornar nada, e nesse caso a execução continua normalmente. Ele também pode retornar os mesmos valores que os métodos de ação (veja Fluxo de execução), o que pode modificar a execução da ação, dos pós-plugins e da view.


5Finalização

Um controlador pode conter o método mágico __sleep() (que não recebe nenhum parâmetro).

Ele será chamado automaticamente depois que a ação for chamada. Pode ser útil para liberar memória inicializada pelo controlador.

Esse método pode não retornar nada, e nesse caso a execução continua normalmente. Ele também pode retornar os mesmos valores que os métodos de ação (veja Fluxo de execução), modificando assim a execução dos pós-plugins e da view.


6Gerenciamento de variáveis de template

As variáveis de template são valores definidos por plugins e/ou ações. Elas podem ser usadas diretamente nos templates, mas também são a principal forma de transferir dados entre os pré-plugins, o controlador e os pós-plugins.

Para criar ou modificar uma variável de template:

$this['myVariable'] = 'my value';

Para ler uma variável de template:

$value = $this['myVariable'];

Para saber se uma variável de template existe, ou se está vazia:

// a variável está definida?
if (isset($this['myVariable']))
    doSomething();

// a variável está vazia?
if (empty($this['myOtherVariable']))
    doSomethingElse();

// variável não definida: uso de um valor padrão
$var = $this['myVariable'] ?? 'defaultValue';

// variável não definida ou vazia: valor padrão
$var = $this['myVariable'] ?: 'defaultValue';

Para destruir uma variável de template:

unset($this['myVariable']);

Importante: variáveis de template cujo nome começa com underscore ("_") são "privadas" para os plugins e para o controlador; elas não ficam disponíveis nos templates.


7Variáveis de template definidas pelo Temma

O framework define várias variáveis de template que ficam disponíveis nos controladores (e nos plugins):

  • $URL: URL solicitada, a partir da barra raiz.
  • $CONTROLLER: Nome do controlador solicitado.
  • $ACTION: Nome da ação solicitada.

Além dessas variáveis, todos os dados configurados na seção autoimport da configuração são importados automaticamente como variáveis de template.


8Métodos utilizáveis nos controladores

Os controladores oferecem métodos específicos que podem ser usados no código das ações.

$this->_template(string)
Se usarmos a view padrão (\Temma\Views\Smarty), esse método redefine o caminho do template a ser usado. Se esse método não for usado para especificar um template, o Temma tentará usar um arquivo cujo nome seja o da ação (com o sufixo ".tpl"), colocado em um diretório cujo nome seja o do controlador.
Sempre retorna o valor self::EXEC_FORWARD (veja a documentação sobre o fluxo de execução).

Também é possível definir o template a ser usado com o atributo Template.

$this->_redirect(?string $url=null, bool $referer=false)
Se você quiser fazer um redirecionamento (e, portanto, não usar uma view), esse método define a URL de redirecionamento. Se $referer for definido como true, o cabeçalho HTTP REFERER é usado como URL de redirecionamento, com $url como alternativa caso o cabeçalho esteja ausente ou vazio.
Sempre retorna o valor self::EXEC_HALT (veja a documentação sobre o fluxo de execução).

$this->_redirect301(?string $url=null, bool $referer=false)
Igual ao método anterior, mas realiza um redirecionamento 301 (permanente) e não 302 (temporário). Se $referer for definido como true, o cabeçalho HTTP REFERER é usado como URL de redirecionamento, com $url como alternativa caso o cabeçalho esteja ausente ou vazio.
Sempre retorna o valor self::EXEC_HALT (veja a documentação sobre o fluxo de execução).

$this->_httpError(int)
Se for encontrado um erro que deva ser tratado de forma genérica, esse método permite especificar o código de erro HTTP (4XX, 5XX).
Sempre retorna o valor self::EXEC_HALT (veja a documentação sobre o fluxo de execução).

$this->_httpCode(int)
Permite especificar o código de resposta HTTP, assim como o método _httpError(), exceto que nenhum erro é gerado e o processamento não é interrompido.
Sempre retorna o valor self::EXEC_HALT (veja a documentação sobre o fluxo de execução).

$this->_view(string)
Usado para definir a view que será utilizada. Por padrão, é \Temma\Views\Smarty.
Sempre retorna o valor self::EXEC_FORWARD (veja a documentação sobre o fluxo de execução).

Também é possível definir a view a ser usada com o atributo View.

$this->_header(string)
Usado para definir um cabeçalho HTTP a ser enviado pela view.

$this->_getHttpError()
Retorna o código de erro HTTP que foi definido com _httpError(), ou null se nenhum foi definido.

$this->_getHttpCode()
Retorna o código de resposta HTTP que foi definido com _httpCode(), ou null se nenhum foi definido.

$this->_templatePrefix(string)
Permite definir um prefixo para o caminho de carregamento dos arquivos de template.
Retorna a instância do objeto atual.

Também é possível definir o template a ser usado com o atributo Template, passando a ele o parâmetro prefix.

$this->_subProcess(string, string)
Usado para executar uma ação de outro controlador. Veja abaixo.
Retorna o status de execução do subcontrolador.


9Atributos dos controladores

$this->_loader
Objeto de injeção de dependências, que permite recuperar outros objetos.
Mais informações na documentação de injeção de dependências.

$this->_session
$this->_loader->session
A menos que a configuração tenha previsto desativar o gerenciamento de sessões, esse atributo contém uma instância de \Temma\Base\Session.
Mais informações na documentação de sessões.

$this->_config
$this->_loader->config
Esse atributo contém uma instância de \Temma\Web\Config. O controlador pode assim acessar (somente leitura) tanto os parâmetros da aplicação Temma quanto os parâmetros estendidos.
Mais informações na documentação de configuração e na documentação do objeto Config.

$this->_request
$this->_loader->request
Esse atributo contém uma instância do tipo \Temma\Web\Request. Isso possibilita recuperar, ou até modificar, os diferentes componentes da requisição.
Mais informações na documentação do objeto Request.

$this->_response
$this->_loader->response
Esse atributo contém uma instância de \Temma\Web\Response. Isso permite controlar alguns parâmetros da resposta.
Mais informações na documentação do objeto Response.

$this->_dao
Se solicitado pelo controlador, o Temma instanciará automaticamente um objeto DAO para facilitar a comunicação com o banco de dados.
Mais informações na documentação do modelo.

$this->_temmaAutoDao
Esse atributo é diferente dos outros. Ele não é definido pelo framework. Cabe a você usá-lo ao criar o controlador, para pedir ao Temma que crie automaticamente o objeto DAO que facilita o acesso ao banco de dados.
Mais informações na documentação do modelo.


10Acesso às fontes de dados

Um controlador pode acessar as fontes de dados de duas maneiras diferentes: por meio do componente de injeção de dependências, ou diretamente, como se fossem atributos do controlador.

Imagine que o arquivo etc/temma.php defina fontes de dados chamadas db (banco de dados MySQL), ndb (banco de dados Redis) e cache (servidor Memcached).

Acesso como atributos:

$db = $this->db;
$ndb = $this->ndb;
$cache = $this->cache;

Acesso por meio do componente de injeção de dependências:

// acesso orientado a objetos
$db = $this->_loader->dataSources->db;
$ndb = $this->_loader->dataSources->ndb;
$cache = $this->_loader->dataSources->cache;

// acesso no estilo array
$db = $this->_loader->dataSources['db'];
$ndb = $this->_loader->dataSources['ndb'];
$cache = $this->_loader->dataSources['cache'];

11Subprocessamento

Em uma ação, podemos facilmente chamar a execução de outra ação do mesmo controlador:

class Article extends \Temma\Web\Controller {
    // não queremos exibir a lista de artigos,
    // queremos exibir apenas o primeiro artigo
    public function list() {
        // chama a ação show()
        $this->show(1);

        // pegamos o artigo na variável de template
        // criada pela ação show()
        $article = $this['article'];

        // criamos uma nova variável de template
        // contendo uma lista com um único item
        $this['articles'] = [$article];
    }

    // ação usada para exibir um artigo
    public function show(int $id) {
        $this['article'] = $this->_loader->ArticleDao->get($id);
    }
}

Um controlador também pode transmitir a execução da requisição atual para outro controlador. Para isso, basta usar o método _subProcess(). É até possível modificar a requisição antecipadamente, para adaptá-la ao subcontrolador:

class HomepageController extends \Temma\Web\Controller {
    public function index() {
        // modificamos o primeiro parâmetro da ação
        $this->_loader->request->setParam(0, 'display');

        // chamamos a ação "publish" do controlador "User"
        $this->_subProcess('User', 'publish');

        // podemos recuperar uma variável de template
        // definida pelo subcontrolador...
        $name = $this['name'];

        // ...e então sobrescrever esse valor condicionalmente
        if (strlen($name) < 3)
            $this['name'] = 'default name';
    }
}

Nesse exemplo, o controlador que gerencia a página inicial do site realiza uma sub-requisição equivalente a uma conexão à URL /user/publish/display, e depois realiza um processamento condicional sobre os dados gerados.