cakeHPP3在模型(基于帐户的云服务)中使用请求/身份验证数据的最佳实践


cakePHP 3 best practises for using request/auth data in model (account based cloud service)

我正在创建一个云服务,其中有多个帐户,每个帐户都有多个用户。用户只能登录到自己的帐户。每个帐户都有一个唯一的别名。(例如abc)。如果用户转到他们的子域(例如abc.cloud.service),他们就可以登录到自己的帐户,只看到分配给该帐户的内容。登录操作非常好。

我添加了一个htaccess重定向规则,它将子域转换为查询。

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{HTTP_HOST} ^([a-z0-9]+)'.cloud'.service$ [NC]
RewriteRule ^ index.php?alias=%1 [L]

因此,在所有控制器中,我都可以使用别名作为查询。为了保证用户只能看到他们的内容,所有模型都与一个帐户相关。每次我想对其中一个模型执行查找操作时,我都希望确保结果属于正确的帐户。我在beforeFind中这样做。

// beforeFind of models which are related to an account
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary) {
    if ($primary) {
        $query->contain(['Accounts']);
    }
}

为了获得正确的帐户,我在Accounts模型中有一个beforeFind,用于检查查询。

// beforeFind of the Accounts model
public function beforeFind (Event $event, Query $query, ArrayObject $options, $primary) {
    if (isset($_GET['alias'])) {
        $query->where(['Accounts.alias' => $_GET['alias']]);
    }
}

这工作得非常好,因为我所有的查找操作都只返回该帐户允许的数据。但我知道我所做的可能不被称为最佳实践问题1:那么有更好的方法吗?我不知道如何访问模型中的查询数据

问题2:当我保存任何类型的内容时,我需要确保将其分配给正确的帐户。

选项1(由于发生次数过多而无效)根据用户的帐户id,我可以在所有保存调用之前手动分配帐户id。但我会有太多保存调用,所以我想在这里有一个更好的解决方案。

选项2(就是我现在正在做的)我在beforeSave中添加了一些代码,保存到与帐户相关的所有模型:

// beforeSave in models related to Accounts model
public function beforeSave(Event $event, Entity $entity, ArrayObject $options)
{
    if (!isset($_SESSION['Auth']['User']['account_id'])) {
        $entity->account_id = $_SESSION['Auth']['User']['account_id'];
    }
}

再次运行得很好,但我也知道cakeHP不建议直接访问会话变量问题2,这里有更好的解决方案吗?我没有找到访问模型中的身份验证数据的有效解决方案

听起来您正在创建的是所谓的"多租户"应用程序。

有一个CakePHP 3的插件正在开发中,它将为您处理这个问题,并且与您已经实现的方法非常相似,但是内置了一些额外的功能。该插件使用子域作为查询(然后在所有表上使用后续的"account_id"的foreign_key,这样你就不必像现在这样在.htaccess中附加别名并使用全局_GET。

请在此处查看:https://github.com/pronique/multitenant

首先,我认为访问模型中的授权信息不是最佳实践,因为它没有在控制器逻辑和业务逻辑之间实现良好的关注点分离。

我想说,在这里使用的最好的情况是调度过滤器,也许类似于:

class SubdomainFilter extends DispatcherFilter
{
    public function beforeFilter(Event $event)
    {
        if (isset($_GET['alias'])) {
            $event->data['request']->subdomain_alias = $_GET['alias'];
        }
    }
}

然后在bootstrap.hp:中初始化您的新过滤器

DispatchFactory::add('SubdomainFilter');

最后,您可以访问子域别名,将其作为附加到控制器中请求对象的属性,作为参数传递到模型函数中:

class FooController extends AppController
{
    public function exampleEndpoint() {
        $your_table->findFunction($this->request->subdomain_alias);
        $your_table->saveFunction($this->request->subdomain_alias, $data);
}

您可以在这里阅读更多关于CakePHP 3中的Dispatch Filters的信息。