在Mapper中查找一个对象(findById)或者查找所有相关的对象(findAll())很简单,但要查找符合特定条件的对象时就需要创建特定的SQL语句来实现查询了。
比如查找user表中score大于10,查找user表中age等于18的Sql语句:
SELECT * FROM `user` WHERE `score` > 10;
SELECT * FROM `user` WHERE `age` = 18;
虽然我们可以创建很多不同的SQL语句来实现,但这不够灵活,而且会产生重复代码。
那么我们可以使用标记对象来实现比较灵活的条件查询。
标记对象(Identity Object,也叫数据传输对象DTO):封装了数据库查询的中的条件,这样不同的条件可以在运行时构成不同的数据库Sql语句。
比如可以用如下代码构建上面两条Select语句的Where部分:
$identityObject->field('score')->gt(10);
$identityObject->field('age')->eq(18);
下面为实现代码:
Field类:
/**
* 字段域类
* @author happen
*/
class Field {
// 字段名
private $name = null;
// 比较条件
private $comp = array();
public function __construct($name) {
$this->name = $name;
}
public function addTest($operator, $value) {
$this->comp[] = array('name' => $this->name, 'operator' => $operator, 'value' => $value);
}
public function getComps() {
return $this->comp;
}
public function isIncomplete() {
return empty($this->comp);
}
}
IdentityObject类:
namespace demo\mapper;
/**
* 标记对象,也叫DTO
* @author happen
*
*/
class IdentityObject {
protected $fields = array();
protected $currentField = null;
protected $enforce = array();
public function __construct($field = null, $enforce = null) {
if (!is_null($enforce)) {
$this->enforce = $enforce;
}
if (!is_null($field)) {
$this->field($field);
}
}
/**
* 添加字段
* @param string $fieldName
* @throws Exception
*/
public function field($fieldName) {
if (!$this->isVoid() && $this->currentField->isIncomplete()) {
// 字段不为空且字段条件不完整
throw new Exception('Incomplete field');
}
$this->enforceField($fileName);
// 添加字段
if (isset($this->fields[$fieldName])) {
$this->currentField = $this->fields[$fieldName];
} else {
$this->currentField = new Field($fieldName);
$this->fields[$fieldName] = $this->currentField;
}
return $this;
}
public function eq($value) {
return $this->opeartor('=', $value);
}
public function gt($value) {
return $this->opeartor('>', $value);
}
public function lt($value) {
return $this->opeartor('<', $value) ;
}
/**
* 给字段添加操作
* @param string $symbol
* @param string $value
* @throws Exception
*/
private function opeartor($symbol, $value) {
if ($this->isVoid()) {
throw new Exception('no object fileds defined!');
}
$this->currentField->addTest($symbol, $value);
return $this;
}
public function isVoid() {
return empty($this->fields);
}
/**
* 检验字段是否合法
* @param string $fileName
* @throws Exception
*/
public function enforceField($fileName) {
if (!in_array($fileName, $this->enforce) && !empty( $this->enforce)) {
// 非法字段
$forcelist = implode( ', ', $this->enforce );
throw new Exception("{$fileName} not a legal field ($forcelist)");
}
}
/**
* 得到对象的字段
*/
public function getObjectFields() {
return $this->enforce;
}
/**
* 得到所有字段的条件查询
*/
public function getComps() {
$ret = array();
foreach ($this->fields as $field) {
$ret = array_merge($ret, $field->getComps());
}
return $ret;
}
public function __toString() {
$ret = array();
foreach($this->getComps() as $compdata) {
$ret[] = "{$compdata['name']} {$compdata['operator']} {$compdata['value']}";
}
return implode( " AND ", $ret );
}
}
接下来就是一个使用例子:
$idobj = new IdentityObject();
$idobj->field("name")->eq("hello")->field("start")->gt(time())->lt(time()+(24*60*60))->getComps();
echo $idobj;
输出结果为:
name = "hello" AND start > 1385127028 AND start < 1385213428
一个具体的IdentityObject子类(强制了合法字段):
/**
* ClassroomIdentityObject
* @author happen
*
*/
class ClassroomIdentityObject extends IdentityObject {
public function __construct($field = null) {
// 添加强制合法字段
parent::__construct($filed, array('id', 'name'));
}
}
如果字段不合法,则会抛出异常。
标记对象能够定义出各种SQL而不需要直接写SQL进行数据库查询,而且也对用户隐藏了数据库SQL细节,能够更灵活地组合出不同的查询条件。