Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Henrique Barcelos

Model e a interface entre Model e Controller

Recommended Posts

Boa tarde, pessoal...

 

Na minha longa jornada tentando criar minha base de trabalho (nem me atrevo a chamar de framework), recentemente terminei uma parte da camada do Model, a que diz respeito ao banco de dados. Me baseei fortemente no Zend Framework para criar essa parte e está funcionando tudo muito bem, obrigado. Dei uma enxugada no ZF pegando apenas os conceitos principais.

 

O problema agora é que não existem Models propriamente ditos dentro do ZF. Dei uma olhada em alguns tutoriais espalhados pela rede e cheguei em alguns onde o programador simplesmente extendia o "model" da classe Zend_Db_Table_Abstract, o que na minha opinião é um erro conceitual.

 

Me explico. O Model é a camada que lida com a lógica do negócio. Zend_Db_Table_Abstract é uma abstração para uma tabela do banco de dados, ou seja, não vejo um relacionamento de herança aí. Para mim, o Model TEM UM (ou pode ter) objeto que lida com o armazenamento secundário da informação, ou seja, temos aí composição.

 

Imagine que um dia eu resolva utilizar outro tipo de armazenamento, como arquivos de texto (eu sei que é improvável), atom, feeds rss, arquivos xml, etc, e aí? Pholdel... vou ter que alterar todos os Models do meu sistema... E eu sou MUITO PREGUIÇOSO para fazer retrabalho...

 

Enfim, a minha ideia é manter a composição e tendo uma classe Model_Abstract com algumas funcionalidades comuns. O problema é que eu estou meio perdido sobre o que agrupar nessa classe. Chego até a me perguntar se é cabível criá-la...

 

Além disso, outra questão que sempre me aflinge é que eu gosto de mostrar "mensagens personalizadas" nos CMSs que eu crio, do tipo "Fotos enviadas com sucesso", "Enquete cadastrada com sucesso", etc, etc, etc. Acho que é questão de usabilidade, do usuário saber o que está se passando.

 

Como eu faço hoje: utilizo session e o padrão Observer. O Model é um Subject e existe um Observer (singleton) que é associado ao Model pelo Controller. As mensagens personalizadas são armazenadas em um array no Model e ao fim de cada ação ele notifica seu Observer, que invoca um método chamado getMessage() que retorna a mensagem atual do Model.

 

Esse observer salva a mensagem na sessão. Logo depois, o Model retorna alguma coisa (aqui também não sei o que retornar, já explico) para o Controller que redireciona para uma página específica, normalmente, se estou excluindo uma foto, por exemplo, devo retornar para a listagem de fotos. Se faz necessário armazenar também as páginas de redirecionamento no Controller. Ao ser redirecionado, a 'View' das mensagens exibe as mensagens da última requisição e as remove da sessão.

 

Aí fica essa nhaca: mensagens específicas no Model, páginas de redirecionamento no Controller. Talvez essas mensagens também devessem ficar no Controller, pois também fazem parte da resposta retornada. Aí também fica outra questão: como fazer a interface Model-Controller? Retornar só um array de dados ou algum booleano é muito superficial, não me permite tratar condições de erros. Se eu ficar lançando exceções no Model, vou estender demais o Controller afim de tratá-las.

 

Enfim, espero ter sido claro...

Compartilhar este post


Link para o post
Compartilhar em outros sites

O problema agora é que não existem Models propriamente ditos dentro do ZF. Dei uma olhada em alguns tutoriais espalhados pela rede e cheguei em alguns onde o programador simplesmente extendia o "model" da classe Zend_Db_Table_Abstract, o que na minha opinião é um erro conceitual.

O ZF não tem uma camada de Model bem definida (aliás isso é muito discutido) porque cada aplicação é uma aplicação diferente (:lol:), com regras de negócio diferentes, e logo um Domain Model diferente.

O Zend_Db_Table é apenas para não ficar o vazio de implementação.

 

Me explico. O Model é a camada que lida com a lógica do negócio. Zend_Db_Table_Abstract é uma abstração para uma tabela do banco de dados, ou seja, não vejo um relacionamento de herança aí. Para mim, o Model TEM UM (ou pode ter) objeto que lida com o armazenamento secundário da informação, ou seja, temos aí composição.

 

Imagine que um dia eu resolva utilizar outro tipo de armazenamento, como arquivos de texto (eu sei que é improvável), atom, feeds rss, arquivos xml, etc, e aí? Pholdel... vou ter que alterar todos os Models do meu sistema... E eu sou MUITO PREGUIÇOSO para fazer retrabalho...

 

Enfim, a minha ideia é manter a composição e tendo uma classe Model_Abstract com algumas funcionalidades comuns. O problema é que eu estou meio perdido sobre o que agrupar nessa classe. Chego até a me perguntar se é cabível criá-la...

Ou seja, você deseja um Adapter?

 

Esse observer salva a mensagem na sessão. Logo depois, o Model retorna alguma coisa (aqui também não sei o que retornar, já explico) para o Controller que redireciona para uma página específica, normalmente, se estou excluindo uma foto, por exemplo, devo retornar para a listagem de fotos. Se faz necessário armazenar também as páginas de redirecionamento no Controller. Ao ser redirecionado, a 'View' das mensagens exibe as mensagens da última requisição e as remove da sessão.

Você pode retornar da Model um ResultSet, e a View não deve ter a responsabilidade de remover nada da sessão, isso é responsabilidade do Controller.

 

Aí fica essa nhaca: mensagens específicas no Model, páginas de redirecionamento no Controller. Talvez essas mensagens também devessem ficar no Controller, pois também fazem parte da resposta retornada. Aí também fica outra questão: como fazer a interface Model-Controller? Retornar só um array de dados ou algum booleano é muito superficial, não me permite tratar condições de erros. Se eu ficar lançando exceções no Model, vou estender demais o Controller afim de tratá-las.

As mensagens são referentes ao modelo de negócio abordado pela aplicação? Elas definem de forma direta o fluxo de alguma coisa? São apenas representativas?

Eu também tive uma dúvida referente à parte da interface Model-Controller, e o que criei foi uma interface Result, assim cada classe de serviço retorna uma classe que implementa essa interface.

Algo como:

interface Result {
   public function isValid();
   public function hasMessages();
   public function getMessages( $msgIdx = null );
   public function addMessage( $msgIdx );
   public function setMessages( array $msgs );
   public function setStatus();
   public function getStatus();
   public function setData( data);
   public function getData();
}

Compartilhar este post


Link para o post
Compartilhar em outros sites
O ZF não tem uma camada de Model bem definida (aliás isso é muito discutido) porque cada aplicação é uma aplicação diferente (laugh.gif), com regras de negócio diferentes, e logo um Domain Model diferente.

O Zend_Db_Table é apenas para não ficar o vazio de implementação.

O que eu quis dizer que é Zend_Db_Table NÃO É o model, tem funcionalidades para serem utilizadas no model, mas NÃO É o model propriamente dito.

 

Ou seja, você deseja um Adapter?

Talvez sim...

 

As mensagens são referentes ao modelo de negócio abordado pela aplicação? Elas definem de forma direta o fluxo de alguma coisa? São apenas representativas?

São mensagens como exemplifiquei: "Registro inserido", "Registro removido", etc...

Eu até pensei em padronizar as mensagens e ter menos dor de cabeça.

 

Talvez o problema também esteja na maneira que eu organizo minhas aplicações.

O Model é uma camada única, utilizando o Passive Model, ou seja, é totalmente dependente do Controller.

 

Os Controllers variam por módulo. O que são os módulos? Ex.: site público, área administrativa, etc.

Cada módulo tem uma estrutura diferente, o que requer intervenções diferentes do Controller sobre a View.

Um exemplo clássico é na hora de setar o título da página, por exemplo. No site público, eu quero um certo título, no admin, já quero outro.

 

Além disso, tem outra coisa que não sei se está correta: eu faço a verificação de acesso em sistemas protegidos a partir do controller. Por isso também que preciso de controllers diferentes para cada módulo. No site público, não há qualquer verificação na hora de executar ações, já no sistema de administração, nada pode ser executado sem a autenticação prévia.

Se os controllers fossem únicos, ficaria meio complicado (eu acho) de definir se o acesso está sendo feito a partir do site público ou da área administrativa.

 

Tive um insight há poucos minutos sobre utilizar o padrão Intercepting Filter, me permitindo adicionar um filtro de autenticação a partir do front controller da área protegida, nesse caso, eu teria um front controller para cada módulo, o que não sei se está bom também... Mas não consegui pensar como faria para impedir o usuário de tentar executar uma ação de deleção a partir do site público, por exemplo...

 

Definir manualmente também quais os métodos que necessitam de autenticação seria um porre =/.

 

Enfim, já estou divagando demais...

 

O que eu gostaria de entender é: qual a relação dessa sua interface Result com a resposta (Response) da requisição?

O problema é que o Model manda apenas dados crus para o Controller (até onde eu sei), então não consegui entender o que seriam essas "mensagens"...

 

Agradeço a atenção B)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Os Controllers variam por módulo. O que são os módulos? Ex.: site público, área administrativa, etc.

Cada módulo tem uma estrutura diferente, o que requer intervenções diferentes do Controller sobre a View.

Um exemplo clássico é na hora de setar o título da página, por exemplo. No site público, eu quero um certo título, no admin, já quero outro.

Essas tarefas repetitivas na View você pode vim a criar um sistema de Helpers, se já não tiver.

 

Além disso, tem outra coisa que não sei se está correta: eu faço a verificação de acesso em sistemas protegidos a partir do controller. Por isso também que preciso de controllers diferentes para cada módulo. No site público, não há qualquer verificação na hora de executar ações, já no sistema de administração, nada pode ser executado sem a autenticação prévia.

Se os controllers fossem únicos, ficaria meio complicado (eu acho) de definir se o acesso está sendo feito a partir do site público ou da área administrativa.

Os controllers não devem ser únicos, devem existir controllers para cada recurso/seção do teu site. A parte de verificação de permissões do usuário, você poderia usar uma ACL (A Wikipedia em inglês está em blackout por causa da SOPA)

 

Tive um insight há poucos minutos sobre utilizar o padrão Intercepting Filter, me permitindo adicionar um filtro de autenticação a partir do front controller da área protegida, nesse caso, eu teria um front controller para cada módulo, o que não sei se está bom também... Mas não consegui pensar como faria para impedir o usuário de tentar executar uma ação de deleção a partir do site público, por exemplo...

Ao meu ver o Front Controller é único para toda a aplicação, o que você poderia implementar no teu Front Controller é um sistema de eventos na tua aplicação, por exemplo, o controller inciou determinada ação? Dispara um event (algo parecido com o Pattern Observer). Aí você pode criar Plugins que podem modificar o fluxo da aplicação assim que determinado evento for disparado. (Um plugin de ACL...!?)

 

O que eu gostaria de entender é: qual a relação dessa sua interface Result com a resposta (Response) da requisição?

O problema é que o Model manda apenas dados crus para o Controller (até onde eu sei), então não consegui entender o que seriam essas "mensagens"...

 

A relação entre minha interface Result e a resposta vai depender do que o Controller faz com ela, lembre-se, eu retorno das minhas classes de serviço uma classe de Result, esse objeto retornado, fica acessivel em algum Controller (o Controller que chamou a classe de serviço), se por exemplo, esse Result está sujo (não está válido), e possui mensagens de erro, eu posso muito bem retornar essas mensagens de erro para a View responsável pela Action atual, ou posso apenas enviá-las para um log de erros, tudo depende do que eu fizer, e o que eu desejo fazer, com ela (o objeto Result) no Controller.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Essas tarefas repetitivas na View você pode vim a criar um sistema de Helpers, se já não tiver.

Não tenhho, estava cogitando a hipótese de nem ter... hehe... preciso pesquisar mais a respeito...

 

Os controllers não devem ser únicos, devem existir controllers para cada recurso/seção do teu site. A parte de verificação de permissões do usuário, você poderia usar uma ACL (A Wikipedia em inglês está em blackout por causa da SOPA)

 

É como eu faço atualmente... Quanto à ACL, outra coisa que preciso pesquisar mais antes de dar minha opinião.

Quanto à wikipedia, só desabilitar o Javascript do navegador que dá =]...

 

Ao meu ver o Front Controller é único para toda a aplicação, o que você poderia implementar no teu Front Controller é um sistema de eventos na tua aplicação, por exemplo, o controller inciou determinada ação? Dispara um event (algo parecido com o Pattern Observer). Aí você pode criar Plugins que podem modificar o fluxo da aplicação assim que determinado evento for disparado. (Um plugin de ACL...!?)

Ah, eu logo vi que a minha ideia não ia funcionar muito bem, desisti.

 

A relação entre minha interface Result e a resposta vai depender do que o Controller faz com ela, lembre-se, eu retorno das minhas classes de serviço uma classe de Result, esse objeto retornado, fica acessivel em algum Controller (o Controller que chamou a classe de serviço), se por exemplo, esse Result está sujo (não está válido), e possui mensagens de erro, eu posso muito bem retornar essas mensagens de erro para a View responsável pela Action atual, ou posso apenas enviá-las para um log de erros, tudo depende do que eu fizer, e o que eu desejo fazer, com ela (o objeto Result) no Controller.

O que eu não entendi é do que ela se compõe. Ela é retornada pelo model, não é? O model deve retornar apenas dados "crus", possíveis erros e mensagens de status, segundo o meu entendimentos.

Compartilhar este post


Link para o post
Compartilhar em outros sites

O que eu não entendi é do que ela se compõe. Ela é retornada pelo model, não é? O model deve retornar apenas dados "crus", possíveis erros e mensagens de status, segundo o meu entendimentos.

 

Pseudo Code:

 

//Camada de Domain Model
namespace App\Domain\Services\User;
use App\Domain\Services\Service;

class Auth extends Service {

   public function login( $email, $pass )
   {
       //Efetua validações e etc.

       if( $isValid ){                                            //essa variavel $user é o usuário já autenticado, que é definido como os dados sendo transportados pelo objeto Result, $messages é vazio pois não tem o que informar.
           $result = $this->createResult( $status = Result::Valid, $data = $user, $messages = array());
       } else {                          //aqui é inválido, logo a variavel $messages vai conter os erros encontrados ao tentar autenticar o usuário.
           $result = $this->createResult( $status = Result::Invalid, $messages = $validationMsgs );
       }

       return $result;
   }

   //...
}

namespace User\Controller;

use Zend\Mvc\Controller\ActionController;

class AuthController extends ActionController {

   public function loginAction()
   {
       $userAuth = $this->getService('User\Auth');
       $result = $userAuth->login( $this->getPost('email'), $this->getPost('password') );

       if( $result->isValid() ){

           //redireciona, carrega permissões, etc
           //usando isso eu obtenho o usuário autenticado:
           $user = $result->getData();

       } else { //login é inválido

           $this->view->assign( 'messages', $result->getMessages() );
           //efetua qualquer outra lógica aqui, como ativa captcha no formulário de login, aumenta algum contador com a quantidade de tentativas inválidas, etc... - Ainda pode ser usada o padrão Strategy para desaclopar ainda mais o código.

       }
   }

   //...
}

Compartilhar este post


Link para o post
Compartilhar em outros sites

Acho que comecei a entender... Os dados "brutos" são o segundo parâmetro de createResult...

Na minha última versão, eu retornava sempre um Registry, onde eu poderia colocar o que quisesse, só não tinha muita certeza do que colocar.

 

Agora uma outra questão é a seguinte:

Imagine uma aplicação com 2 módulos: admin e site público.

No site público eu gostaria de alterar o título da página de acordo com o conteúdo, já no admin, não.

Como os models são únicos eu tenho 2 opções:

* O controller decide qual titulo atribuir à variável de template

* Crio "variáveis" com "namespaces" no model. Ex.:

public function setVar($namespace, $name, $value){
$this->_vars[$namespace][$name] = $value;
}

 

O problema da primeira abordagem é que não sei se isso realmente deve ir no controller.

O problema da segunda abordagem é que se eu tiver 20 módulos na aplicação, preciso setar 20 valores diferentes.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Assim, ambos os módulos, admin e public, compartilham O MESMO HTML? 99% de chance de sua resposta ser NÃO.

 

Sendo assim, cada Action, de Controller, de cada Módulo tem seu próprio Template View, então, por que não escrever a tag TITLE do jeito que você quer em cada um deles?

 

Se você fizer questão de mostrar um breadcrumbs simplificado no título, daí sim você cria uma variável de template:

 

$this -> view -> title = array( 'Seção', 'Subsection', 'Action' );

E com um View Helper, você montaria o título:

 

<html>
<head>
<title><?php echo $this -> getHelper( 'breadcrumbs', $this -> title ); ?></title>
</head>
<body>
</body>
</html>

E nesse helper, algo como:

 

<?php

interface ViewHelper {

   public function draw();
}

abstract class AbstractViewHelper implements ViewHelper {

   protected $args;

   public function __construct() {

       $this -> args = func_get_args();
   }
}

class BreadCrumbs extends AbstractViewHelper {

   public function draw() {

       return implode( ' :: ', $this -> args );
   }
}

class View {

   // ...

   public function getHelper() {

       $args = func_get_args();

       if( count( $args ) == 0 ) {
           throw new ViewException( 'Informe ao menos o helper que deseja invocar' );
       }

       $helperName = array_shift( $args );

       $reflection = new ReflectionClass( $helperName );

       $object = $reflection -> newInstanceArgs( $args );

       return $object -> draw();
   }

   // ...
}

Escrevi agora de cabeça, mas acho que resolveria, pelo menos esse caso em particular.

Compartilhar este post


Link para o post
Compartilhar em outros sites
Assim, ambos os módulos, admin e public, compartilham O MESMO HTML? 99% de chance de sua resposta ser NÃO.

Ah, você poderia por 100% de certeza... ehuheuheehu... não tem como...

 

Entendi, então nesse caso é algo que ficaria sob responsabilidade do controller...

Imagine que estou querendo colocar o título de uma notícia como título da página:

public function viewAction(){
// Recupera os dados de algum jeito...
$news = $result->getData();
$this->_view->assign('page_title', $news['title']);
// Continua executando...
}

 

Era bem o que eu estava pensando mesmo, inicialmente, só tive dúvidas se estava misturando as responsabilidades.

Na verdade, eu já faço assim, mas como não tenho um mecanismo de templates, eu uso um Registry singleton para toda a aplicação. Com um pouco de cuidado na hora de nomear as variáveis, funciona da mesma maneira...

Antes, quando estava começando, já cheguei ao absurdo de ir buscar a notícia de novo no banco na View pq já trazia o HTML todo renderizado e não tinha como buscar o título (na verdade tinha, mas eu não conhecia expressões regulares, hehe)...

 

Assim que eu terminar Request/Response, vou começar a brincar com o controller...

Agradeço a atenção de vocês...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Mas você não deve fugir deles.

 

Como toda reusabilidade pregada, direta ou indiretamente, pelos principios da OOP, os helpers servem para dar uma mãozinha com tarefas que se repetem ao longo de uma aplicação.

 

Sem eles, você poderia ter um sistema todo Orientado a Objetos, que atende à todas as diversas siglas que com ele vêm, mas fracassaria em torno de 50% no DRY, pois seus Templates teriam repetição desnecessária.

 

Imagina se de repente você muda alguma coisa no título, por exemplo. Iria mesmo mexer em sei lá quantos arquivos, ou apenas na classe do helper?

Compartilhar este post


Link para o post
Compartilhar em outros sites

É que na verdade eu queria evitar PHP gerador de HTML, por questões de performance, mas sei que é inevitável...

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.