ACL e controladores
Em primeiro lugar: na maioria das vezes, trata-se de coisas / camadas diferentes. Conforme você critica o código do controlador exemplar, ele coloca os dois juntos - obviamente muito apertados.
tereško já esboçou uma maneira de desacoplar isso mais com o padrão do decorador.
Eu daria um passo para trás primeiro para procurar o problema original que você está enfrentando e discutiria um pouco depois.
Por um lado, você quer ter controladores que apenas façam o trabalho para o qual são comandados (comando ou ação, vamos chamá-lo de comando).
Por outro lado, você deseja poder colocar ACL em seu aplicativo. O campo de trabalho dessas ACLs deve ser - se bem entendi sua pergunta - controlar o acesso a determinados comandos de seus aplicativos.
Esse tipo de controle de acesso, portanto, precisa de algo mais que reúna esses dois. Com base no contexto em que um comando é executado, o ACL entra em ação e decisões precisam ser tomadas se um comando específico pode ou não ser executado por um sujeito específico (por exemplo, o usuário).
Vamos resumir até este ponto o que temos:
- Comando
- ACL
- Do utilizador
O componente ACL é central aqui: ele precisa saber pelo menos algo sobre o comando (para identificar o comando para ser preciso) e precisa ser capaz de identificar o usuário. Os usuários normalmente são facilmente identificados por um ID exclusivo. Mas muitas vezes em aplicativos da web há usuários que não são identificados de forma alguma, geralmente chamados de convidados, anônimos, todos, etc. Para este exemplo, assumimos que a ACL pode consumir um objeto de usuário e encapsular esses detalhes. O objeto de usuário está vinculado ao objeto de solicitação do aplicativo e a ACL pode consumi-lo.
Que tal identificar um comando? Sua interpretação do padrão MVC sugere que um comando é composto de um nome de classe e um nome de método. Se olharmos mais de perto, existem até argumentos (parâmetros) para um comando. Portanto, é válido perguntar o que exatamente identifica um comando? O nome da classe, o nome do método, o número ou nomes dos argumentos, até mesmo os dados dentro de qualquer um dos argumentos ou uma mistura de tudo isso?
Dependendo do nível de detalhe que você precisa para identificar um comando em seu ACL, isso pode variar muito. Para o exemplo, vamos mantê-lo simples e especificar que um comando seja identificado pelo nome da classe e pelo nome do método.
Portanto, o contexto de como essas três partes (ACL, Comando e Usuário) pertencem umas às outras está agora mais claro.
Poderíamos dizer, com um componente ACL imaginário, já podemos fazer o seguinte:
$acl->commandAllowedForUser($command, $user);
Veja o que está acontecendo aqui: ao tornar o comando e o usuário identificáveis, a ACL pode fazer seu trabalho. O trabalho da ACL não está relacionado ao trabalho do objeto de usuário e do comando concreto.
Só falta uma parte, isso não pode viver no ar. E isso não acontece. Portanto, você precisa localizar o local onde o controle de acesso precisa ser acionado. Vamos dar uma olhada no que acontece em um aplicativo da web padrão:
User -> Browser -> Request (HTTP)
-> Request (Command) -> Action (Command) -> Response (Command)
-> Response(HTTP) -> Browser -> User
Para localizar esse lugar, sabemos que deve ser antes que o comando concreto seja executado, portanto, podemos reduzir essa lista e só precisamos olhar para os seguintes locais (potenciais):
User -> Browser -> Request (HTTP)
-> Request (Command)
Em algum ponto de seu aplicativo, você sabe que um usuário específico solicitou a execução de um comando concreto. Você já faz algum tipo de ACL aqui: Se um usuário solicitar um comando que não existe, você não permite que esse comando seja executado. Então, onde quer que isso aconteça em seu aplicativo, pode ser um bom lugar para adicionar as verificações de ACL "reais":
O comando foi localizado e podemos criar a identificação dele para que a ACL possa lidar com ele. Caso o comando não seja permitido para um usuário, o comando não será executado (ação). Talvez um, em CommandNotAllowedResponse
vez de CommandNotFoundResponse
para o caso, uma solicitação não pudesse ser resolvida em um comando concreto.
O local onde o mapeamento de um HTTPRequest concreto é mapeado em um comando costuma ser chamado de Roteamento . Como o Routing já tem o trabalho de localizar um comando, por que não estendê-lo para verificar se o comando é realmente permitido por ACL? Por exemplo, alargando o Router
a um roteador ciente ACL: RouterACL
. Se o seu roteador ainda não conhece o User
, então Router
não é o lugar certo, pois para o ACL funcionar não só o comando, mas também o usuário deve estar identificado. Portanto, este local pode variar, mas tenho certeza de que você pode localizar facilmente o local que precisa estender, porque é o local que atende aos requisitos de usuário e comando:
User -> Browser -> Request (HTTP)
-> Request (Command)
O usuário está disponível desde o início, Comando primeiro com Request(Command)
.
Portanto, em vez de colocar suas verificações de ACL dentro da implementação concreta de cada comando, você o coloca antes dele. Você não precisa de nenhum padrão pesado, mágica ou qualquer outra coisa, o ACL faz o seu trabalho, o usuário faz o seu trabalho e especialmente o comando faz o seu trabalho: apenas o comando, nada mais. O comando não tem interesse em saber se as funções se aplicam ou não a ele, se está guardado em algum lugar ou não.
Portanto, mantenha separadas as coisas que não pertencem uma à outra. Use uma pequena reformulação do Princípio de Responsabilidade Única (SRP) : Deve haver apenas um motivo para alterar um comando - porque o comando foi alterado. Não porque agora você introduz ACL'ing em seu aplicativo. Não porque você troca o objeto Usuário. Não porque você migra de uma interface HTTP / HTML para um SOAP ou interface de linha de comando.
A ACL no seu caso controla o acesso a um comando, não o comando em si.
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(senão, exibir "Você não tem acesso ao perfil deste usuário" ou algo parecido? Não entendi.