First question
Please, could you explain me how simpliest ACL could be implemented in MVC.
Here is the first approach of using Acl in Controller...
<?php
class MyController extends Controller {
public function myMethod() {
//It is just abstract code
$acl = new Acl();
$acl->setController('MyController');
$acl->setMethod('myMethod');
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
It is very bad approach, and it's minus is that we have to add Acl piece of code into each controller's method, but we don't need any additional dependencies!
Next approach is to make all controller's methods private
and add ACL code into controller's __call
method.
<?php
class MyController extends Controller {
private function myMethod() {
...
}
public function __call($name, $params) {
//It is just abstract code
$acl = new Acl();
$acl->setController(__CLASS__);
$acl->setMethod($name);
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
It is better than previous code, but main minuses are...
The next approach is to put Acl code into parent Controller, but we still need to keep all child controller's methods private.
What is the solution? And what is the best practice? Where should I call Acl functions to decide allow or disallow method to be executed.
Second question
Second question is about getting role using Acl. Let's imagine that we have guests, users and user's friends. User have restricted access to viewing his profile that only friends can view it. All guests can't view this user's profile. So, here is the logic..
The main question is about detecting owner of profile. We can detect who is owner of profile only executing model's method $model->getOwner(), but Acl do not have access to model. How can we implement this?
I hope that my thoughts are clear. Sorry for my English.
Thank you.
In my humble opinion, the best way to approach this would be to use decorator pattern, Basically, this means that you take your object, and place it inside another object, which will act like a protective shell. This would NOT require you to extend the original class. Here is an example:
class SecureContainer
{
protected $target = null;
protected $acl = null;
public function __construct( $target, $acl )
{
$this->target = $target;
$this->acl = $acl;
}
public function __call( $method, $arguments )
{
if (
method_exists( $this->target, $method )
&& $this->acl->isAllowed( get_class($this->target), $method )
){
return call_user_func_array(
array( $this->target, $method ),
$arguments
);
}
}
}
And this would be how you use this sort of structure:
// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );
$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller
// only now they will be checked against ACL
$controller->actionIndex();
As you might notice, this solution has several advantages:
Controller
But, there are one major issue with this method too - you cannot natively check if secured object implements and interface ( which also applies for looking up existing methods ) or is part of some inheritance chain.
In this case the main difference you should recognize is that you Domain Objects (in example: Profile
) itself contains details about owner. This means, that for you to check, if (and at which level) user has access to it, it will require you to change this line:
$this->acl->isAllowed( get_class($this->target), $method )
Essentially you have two options:
Provide the ACL with the object in question. But you have to be careful not to violate Law of Demeter:
$this->acl->isAllowed( get_class($this->target), $method )
Request all the relevant details and provide the ACL only with what it needs, which will also make it a bit more unit-testing friendly:
$command = array( get_class($this->target), $method );
/* -- snip -- */
$this->acl->isAllowed( $this->target->getPermissions(), $command )
Couple videos that might help you to come up with your own implementation:
You seem to have the quite common ( and completely wrong ) understanding of what Model in MVC is. Model is not a class. If you have class named FooBarModel
or something that inherits AbstractModel
then you are doing it wrong.
In proper MVC the Model is a layer, which contains a lot of classes. Large part of the classes can be separated in two groups , based on the responsibility:
Instances from this group of classes deal with computation of values, check for different conditions, implement sales rules and do all the rest what you would call "business logic". They have no clue how data is stored, where it is stored or even if storage exists in first place.
Domain Business object do not depend on database. When you are creating an invoice, it does not matter where data comes from. It can be either from SQL or from a remote REST API, or even screenshot of a MSWord document. The business logic does no change.
Instances made from this group of classes are sometimes called Data Access Objects. Usually structures that implement Data Mapper pattern ( do not confuse with ORMs of same name .. no relation ). This is where your SQL statements would be (or maybe your DomDocument, because you store it in XML).
Beside the two major parts, there is one more group of instances/classes, that should be mentioned:
This is where your and 3rd party components come in play. For example, you can think of "authentication" as service, which can be provided by your own, or some external code. Also "mail sender" would be a service, which might knit together some domain object with a PHPMailer or SwiftMailer, or your own mail-sender component.
Another source of services are abstraction on to on domain and data access layers. They are created to simplify the code used by controllers. For example: creating new user account might require to work with several domain objects and mappers. But, by using a service, it will need only one or two lines in the controller.
What you have to remember when making services, is that the whole layer is supposed to be thin. There is no business logic in services. They are only there to juggle domain object, components and mappers.
One of things they all have in common would be that services do not affect the View layer in any direct way, and are autonomous to such an extent, that they can be ( and quit often - are ) used outside the MVC structure itself. Also such self-sustained structures make the migration to a different framework/architecture much easier, because of extremely low coupling between service and the rest of application.