在数据库中排序ip地址时,我使用MySQL提供的INET_ATON函数。
现在,当我试图这样做在CakePHP 3(我有点新的)我失败了。
这是我得到的:
class Ip extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* @var array
*/
protected $_accessible = [
'*' => true,
'id' => false,
];
protected $_virtual = ['ip_address'];
protected function _getIpAddress()
{
$vc = (new FunctionsBuilder())->inet_aton(['ip' => 'literal']);
return $vc;
}
}
这是我的Controller方法
<?php
namespace AppController;
use AppControllerAppController;
use CakeORMTableRegistry;
use CakeValidationValidator;
use CakeDatabaseFunctionsBuilder;
/**
* Ips Controller
*
* @property AppModelTableIpsTable $Ips
*/
class IpsController extends AppController
{
/**
* Index method
*
* @return void
*/
public function index()
{
$this->paginate = [
'contain' => ['Users'],
'order' => [(new FunctionsBuilder())->inet_aton(['ip' => 'literal'])]
];
$this->set('ips', $this->paginate($this->Ips));
$this->set('_serialize', ['ips']);
}
通过知道对虚拟属性进行排序可能不会工作,我不确定我是否使用了正确的方法来完成这项工作。
有没有人推荐实现这个场景的最佳实践?
使用额外的列代替
我个人会将IP的整数表示形式存储在单独的列中,并使用该列进行排序。这不仅可以为DBMS提供更好的性能,因为它可以使用索引,还可以避免处理函数表达式所需的额外逻辑,并允许您像往常一样使用Pagination组件。
在视图中,只需使用新列的名称,并手动定义要显示排序链接的名称,如
<?= $this->Paginator->sort('ip_int', 'IP') ?>
<标题> 使用表达式通过选项或自定义查询
在当前状态下,分页不能处理函数表达式,只能处理列名。至少在与分页视图帮助器结合使用时是这样的!虽然您可以为order
选项定义表达式,如
$this->paginate['order'] = [
new CakeDatabaseExpressionOrderClauseExpression(
$this->Ips->query()->func()->inet_aton(['ip' => 'literal']),
'ASC'
)
];
或使用自定义查询,如
$query = $this->Ips->find();
$query->orderAsc($query->func()->inet_aton(['ip' => 'literal']));
$this->set('ips', $this->paginate($query));
您必须使用一点自定义逻辑才能使其与视图一起工作,因为分页帮助器被设计为仅处理列名(字符串)。
view helper的动态排序
现在,如果您也想使用分页视图帮助器,则必须创建一些逻辑来评估帮助器生成的排序列和方向,并手动配置选项/查询,类似于上面的示例。
这里是一个基本的例子,使用Query::orderAsc()
和Query::orderDesc()
方法,可以在使用函数表达式时定义方向(表达式不能通过Query::order()
与其他值组合)。
$query = $this->Ips->find();
$sort = $this->request->query('sort');
if ($sort === null || $sort === 'ip') {
$method = 'orderAsc';
if ($this->request->query('direction') === 'desc') {
$method = 'orderDesc';
}
$query->{$method}($query->func()->inet_aton(['ip' => 'literal']));
}
$this->set('articles', $this->paginate($query));
这应该是不言自明的,首先检查Paginator helper生成的sort
键是否被传递,如果它不是或它是您的IP列,则应用您的自定义订单,相对于可能设置的direction
键。如果sort
不是您的IP列,只需忽略它,Paginator将拾取值并计算并像往常一样使用它们。
类似地,您可以在手动创建OrderClauseExpression
实例时设置方向,但我个人可能会坚持使用自定义查询和函数构建器。
再干一点?
如果你想让它更易于重用,你可能需要研究一个自定义/扩展分页器组件、自定义查找器和行为。
实体是数据容器
最后但并非最不重要的是,你的实体与所有这些无关,它只是一个数据容器,所以放弃虚拟属性,它们无论如何都不能在查找中使用。
<标题>参见- Cookbook> Controllers> Components> Pagination> Using Controller::paginate()
- Cookbook>数据库访问&ORM>实体>创建虚拟属性
- Cookbook>数据库访问&检索数据&结果集>自定义查找器方法
- Cookbook>数据库访问&ORM>查询生成器>选择数据