ACL e controller
Prima di tutto: molto spesso si tratta di cose / strati diversi. Mentre critichi il codice esemplare del controller, mette insieme entrambi, ovviamente troppo stretto.
tereško ha già delineato un modo per disaccoppiarlo maggiormente con il motivo del decoratore.
Vorrei prima fare un passo indietro per cercare il problema originale che stai affrontando e discuterne un po 'poi.
Da un lato vuoi avere controller che eseguano semplicemente il lavoro a cui sono comandati (comando o azione, chiamiamolo comando).
D'altra parte vuoi essere in grado di mettere ACL nella tua applicazione. Il campo di lavoro di questi ACL dovrebbe essere - se ho capito bene la tua domanda - controllare l'accesso a determinati comandi delle tue applicazioni.
Questo tipo di controllo dell'accesso necessita quindi di qualcos'altro che riunisca questi due elementi. In base al contesto in cui viene eseguito un comando, ACL entra in gioco e devono essere prese decisioni se un comando specifico può essere eseguito o meno da un soggetto specifico (ad esempio l'utente).
Riassumiamo a questo punto quello che abbiamo:
La componente ACL è centrale qui: deve sapere almeno qualcosa sul comando (per identificare il comando per essere precisi) e deve essere in grado di identificare l'utente. Gli utenti sono normalmente facilmente identificati da un ID univoco. Ma spesso nelle applicazioni web ci sono utenti che non sono affatto identificati, spesso chiamati guest, anonimi, tutti ecc. Per questo esempio si presume che l'ACL possa consumare un oggetto utente e incapsulare questi dettagli. L'oggetto utente è associato all'oggetto richiesta dell'applicazione e l'ACL può utilizzarlo.
Che dire dell'identificazione di un comando? La tua interpretazione del pattern MVC suggerisce che un comando è composto da un nome di classe e un nome di metodo. Se guardiamo più da vicino ci sono anche argomenti (parametri) per un comando. Quindi è valido chiedere cosa identifica esattamente un comando? Il nome della classe, il nome del metodo, il numero oi nomi degli argomenti, persino i dati all'interno di uno qualsiasi degli argomenti o una miscela di tutto questo?
A seconda del livello di dettaglio necessario per identificare un comando nel tuo ACL, questo può variare molto. Per l'esempio manteniamolo semplice e specifichiamo che un comando è identificato dal nome della classe e dal nome del metodo.
Quindi il contesto di come queste tre parti (ACL, comando e utente) appartengono l'una all'altra è ora più chiaro.
Potremmo dire che con un componente ACL immaginario possiamo già fare quanto segue:
$acl->commandAllowedForUser($command, $user);
Guarda cosa sta succedendo qui: rendendo identificabili sia il comando che l'utente, l'ACL può fare il suo lavoro. Il lavoro dell'ACL non è correlato al lavoro sia dell'oggetto utente che del comando concreto.
Manca solo una parte, questa non può vivere nell'aria. E non è così. Quindi è necessario individuare il punto in cui deve essere attivato il controllo degli accessi. Diamo un'occhiata a cosa succede in un'applicazione web standard:
User -> Browser -> Request (HTTP)
-> Request (Command) -> Action (Command) -> Response (Command)
-> Response(HTTP) -> Browser -> User
Per individuare quel luogo, sappiamo che deve essere prima che il comando concreto venga eseguito, quindi possiamo ridurre quell'elenco e dobbiamo solo esaminare i seguenti (potenziali) luoghi:
User -> Browser -> Request (HTTP)
-> Request (Command)
Ad un certo punto nella tua applicazione sai che un utente specifico ha richiesto di eseguire un comando concreto. Fai già una sorta di ACL qui: se un utente richiede un comando che non esiste, non permetti a quel comando di essere eseguito. Quindi, ovunque ciò accada nella tua applicazione potrebbe essere un buon posto per aggiungere i controlli ACL "reali":
Il comando è stato individuato e possiamo crearne l'identificazione in modo che l'ACL possa gestirlo. Nel caso in cui il comando non sia consentito a un utente, il comando non verrà eseguito (azione). Forse al CommandNotAllowedResponse
posto del CommandNotFoundResponse
caso una richiesta non può essere risolta su un comando concreto.
Il luogo in cui la mappatura di una richiesta HTTP concreta viene mappata su un comando è spesso chiamato Routing . Dato che il Routing ha già il compito di individuare un comando, perché non estenderlo per verificare se il comando è effettivamente consentito per ACL? Ad esempio estendendo la Router
a un router consapevoli ACL: RouterACL
. Se il tuo router non conosce ancora User
, allora Router
non è il posto giusto, perché affinché ACL'ing funzioni non solo il comando ma anche l'utente deve essere identificato. Quindi questo posto può variare, ma sono sicuro che puoi facilmente individuare il luogo che devi estendere, perché è il luogo che soddisfa i requisiti di utente e comando:
User -> Browser -> Request (HTTP)
-> Request (Command)
L'utente è disponibile dall'inizio, comando prima con Request(Command)
.
Quindi, invece di mettere i tuoi controlli ACL all'interno dell'implementazione concreta di ogni comando, lo metti prima di esso. Non hai bisogno di schemi pesanti, magia o altro, l'ACL fa il suo lavoro, l'utente fa il suo lavoro e soprattutto il comando fa il suo lavoro: solo il comando, nient'altro. Il comando non ha interesse a sapere se i ruoli si applicano o meno ad esso, se è custodito da qualche parte o meno.
Quindi tieni separate le cose che non appartengono l'una all'altra. Utilizzare una riformulazione leggermente del principio di responsabilità unica (SRP) : dovrebbe esserci un solo motivo per modificare un comando, perché il comando è cambiato. Non perché ora introduci ACL nella tua applicazione. Non perché cambi l'oggetto Utente. Non perché si migra da un'interfaccia HTTP / HTML a un'interfaccia SOAP o a riga di comando.
L'ACL nel tuo caso controlla l'accesso a un comando, non il comando stesso.
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(altrimenti, visualizza "Non hai accesso al profilo di questo utente" o qualcosa del genere? Non capisco.