除了限制用户可以访问哪些表和字段,还常会遇到一类需求是限制用户只能访问自己的数据。
[任务]
用户登录后,可以添加订单、查看自己的订单。 我们在设计文档中设计接口如下:
添加订单 Ordr.add()(amount) -> id 查看订单 Ordr.query() -> tbl(id, userId, status, amount) Ordr.get(id) -> { 同query接口字段...} 应用逻辑 - 权限:AUTH_USER - 用户只能添加(add)、查看(get/query)订单,不可修改(set)、删除(del)订单 - 用户只能查看(get/query)属于自己的订单。 - 用户在添加订单时,必须设置amount字段,不可设置userId, status这些字段。 后端将userId字段自动设置为该用户编号,status字段自动设置为"CR"(已创建)上面接口原型描述中,get接口用”…”省略了详细的返回字段,因为返回对象的字段与query接口是一样的,两者写清楚一个即可。
实现对象型接口,我们一般写到文件php/api_objects.php中:
class AC1_Ordr extends AccessControl { protected $allowedAc = ["get", "query", "add"]; protected $requiredFields = ["amount"]; protected $readonlyFields = ["status", "userId"]; // get/query接口会回调 protected function onQuery() { $userId = $_SESSION["uid"]; $this->addCond("t0.userId={$userId}"); } // add/set接口会回调 protected function onValidate() { if ($this->ac == "add") { $userId = $_SESSION["uid"]; $_POST["userId"] = $userId; $_POST["status"] = "CR"; } } }在get/query操作中,会回调onQuery函数,在这里我们用addCond添加了一条限制:用户只能查看到自己的订单。 addCond的参数可理解为SQL语句中WHERE子句的片段;字段用”t0.userId”来表示,其中”t0”表示当前操作表”Ordr”的别名(alias)。后面会讲到联合查询(join)其它表,就可能使用其它表的别名。
add/set操作会回调onValidate函数(本例中$allowedAc中未定义”set”,因而不会有”set”操作过来)。在这个回调中常常设置$_POST[字段名]来自动完成一些字段。
注意$_SESSION["uid"]变量是在用户登录成功后设置的,由于”AC1”类是用户登录后使用的,所以必能取到该变量。
[任务]
我们把需求稍扩展一下,现在允许set/del操作,即用户可以更改和删除自己的订单。
可以这样实现:
class AC1_Ordr extends AccessControl { protected $allowedAc = ["get", "query", "add", "set", "del"]; ... // get/set/del接口会回调 protected function onValidateId() { $uid = $_SESSION["uid"]; $id = mparam("id"); $rv = queryOne("SELECT id FROM Ordr WHERE id={$id} AND userId={$uid}"); if ($rv === false) throw new MyException(E_FORBIDDEN, "not your order"); } }可通过onValidateId回调来限制get/set/del操作时,只允许访问自己的订单。
函数mparam用来取必传参数(m表示mandatory)。 函数queryOne用来查询首行数据,如果查询只有一列,则返回首行首列数据,但如果查询不到数据,就返回false. 这里如果返回false,既可能是订单id不存在,也可能是虽然存在但是是别人的订单,简单处理,我们都返回一个E_FORBIDDEN异常。
框架对异常会自动处理,一般不用特别再检查数据库操作失败之类的异常。如果返回错误对象,可抛出MyException异常:
throw new MyException(E_FORBIDDEN);错误码”E_FORBIDDEN”表示没有权限,不允许操作;常用的其它错误码还有”E_PARAM”,表示参数错误。
MyException的第二个参数是内部调试信息,第三个参数是对用户友好的报错信息,比如:
throw new MyException(E_FORBIDDEN, "order id {$id} does not belong to user {$uid}", "不是你的订单,不可操作");