我有一个app/controllers/UsersController.php
,它在索引操作中执行一个简单的Users::find('all');
。
路径/users/index
呈现用户数据的纯HTML输出。路径/users/index.json
呈现HTML输出的JSON等价物,这很好,只是它还公开了密码(经过散列处理,但仍然…)
我认为有两种选择可以避免这种情况:
- 在查找器中明确指定
fields
- 过滤
Media::render()
并取消设置任何敏感数据
从长远来看,我觉得2号可能更容易保持。有什么意见吗?还有第三种更好的选择吗?
这就是我实现#2的方式:
<?php
namespace app'controllers;
use 'lithium'net'http'Media;
class UsersController extends 'lithium'action'Controller {
protected function _init() {
Media::applyFilter('render', function($self, $params, $chain) {
if ($params['options']['type'] === 'json') {
foreach ($params['data']['users'] as $user) {
$user->set([
'password' => null,
'salt' => null
]);
}
}
return $chain->next($self, $params, $chain);
});
parent::_init();
}
}
?>
如有任何建议,我们将不胜感激。
这个问题可能有很多答案和方法,具体取决于您的应用程序、可维护性、架构的优雅性等。。。如果您只想删除用户密码等合理的字段,那么您的解决方案就可以完成这项工作。
但是!
过滤Media::render()
似乎根本不是一个好主意。您在这里混合了各种关注点,最终会得到一个臃肿的过滤器,您可以在其中调整对象以删除您不想在json响应中暴露的内容。
如果你每次都要为应用程序中的每个控制器点上字段,那么使用字段可能不够好。更糟糕的是,如果您的实体有30多个字段,并且根据当前用户,显示不同的信息(OMG)!你最终会得到一个臃肿的控制器,在这里,你再次混合了关注点和责任:find()
负责读取你的数据,而fields
只是改变你的数据的表示(某种视图)。
那么呢?我们能做什么?
复制控制器逻辑
您可以通过将控制器中的过滤逻辑封装到if ($this->request->is('json')) { ... }
中来分离它这意味着,如果请求是html
或json
(公共api),则相同的控制器动作的响应不同
这也不好:)
一个稍微好一点的方法是通过使用重复的控制器来进行拆分=>第一个集合负责jsonapi,第二个集合负责响应html的"经典"控制器
通过添加一个controllers/api
名称空间,并在json
请求/响应的情况下重新配置Dispatcher以使用此路径,您可以使用Lithium轻松完成此操作。
li3_jbuilder
在某些情况下,我对复制控制器不太满意。更好的方法是使用MVC
的V
部分,但这次是呈现json响应,并将其作为第一类对象处理:json视图
这可以通过调整Media
类配置并具有回退机制(如果找不到*.json.php
,则json_encode
是没有筛选字段的对象)来轻松完成
我为Lithium构建了li3_jbuilder,以便轻松构建json响应、嵌套对象、使用助手,并将"表示"方面移动到视图层
Jbuilder的灵感来自Rails的Jbuilder。仅供参考,ruby社区也有RABL。
演示者模式
虽然这种方法看起来很简单,但还有另一种有趣的、更面向对象的方法:使用Presenter模式(或Decorator)
用户模型与UserPresenter类(普通的旧php类)相关联,负责提供要"呈现"的对象,尤其是在json响应中(或应用程序中的任何位置)
演示者也可以帮助您清理复杂的视图逻辑,可测试且非常灵活
演示者需要了解模型及其将要处理的视图,以便将它们传递给initialize
方法,并将它们分配给实例变量
只需在谷歌上搜索"Presenter模式"或"Rails presenters"(我使用的唯一使用该模式的框架),就可以了解更多关于主题的信息
明确指定fields
有几个优点:
- 您无法获得不需要的数据,因此可能会更快
- 如果你忘了取消设置,就不会意外泄露数据
- 当您指定需要哪些字段时,如果
JSON
格式发生更改,您将收到早期警告
这与在SQL中不执行SELECT * FROM
的原理类似。
我也遇到了同样的问题,当你在路径中添加.json时,我正在打印电子邮件和密码。
所以,由于我使用MySql,并且我在所有模型中都声明了我的$_schema,所以我做了一个小技巧。。。我在所有想要从数据库请求的字段中添加了一个"public"=>true,并在所有查询中使用它,如下所示:
$users = Users::find(array('fields' => Users::publicFields()));
publicFields方法如下所示:
public static function publicFields() {
$self = static::_object();
$className = $self->meta()['name'];
$schema = $self->schema();
$fields = array_filter($schema->fields(), function($var) {
return !empty($var['public']);
});
$names = array_keys($fields);
for ($i = 0, $iMax = count($names); $i < $iMax; $i++) {
$names[$i] = $className . '.' . $names[$i];
}
return $names;
}
此处相同。unset(var)方法非常肮脏且危险。
我需要所有视图中的users对象来呈现主菜单并进行一些用户交互。Controller::Render方法通过$this->set()提供了这个额外的类。
在我的用户控制器中,我创建了一个新的php类"DSMember"。这个对象占用了我在视图中需要的一些公共属性。此处不提供密码和安全相关内容。
因此,在用户表示逻辑(DS=Display)和核心相关内容之间有了清晰的界限。
class DSMember
{
public $id;
public $profile;
public $uuid;
public $messages;
function __construct ($user) //$user is the Users::Object
{
$this->id = $user->id;
$this->uuid = $user->uuid;
$this->profile = $user->user_profile;
$this->messages = $user->messages;
}
}
渲染方法重载::
public function render (array $options = array())
{
if ($this->session)
{
$member = new DSMember ($this->member);
$this->set (compact ('member'));
}
parent::render ($options);
}
因此,DSMember对象在所有HTML视图和JSON呈现输出中都可用。热门用户模型是隐藏的。
上面提到了很多不同的方法。对于我的应用程序来说,这种方式似乎很好。