英文:
CakePHP 3 - Use SQL function in select() by default in table object
问题
我正在为一个具有一些 POINT
列的数据库系统设置系统。我使用了示例中的食谱来构建自定义类型,似乎正在工作。但是,要处理这些 POINT
,我需要以特殊方式进行 SELECT
:
SELECT ST_AsText(location) as location ...
这在查询构建器中并不难:
$this->Houses->find()->select(['location' => 'ST_AsText(location)'])
然而,我更希望这个操作默认就发生。
我考虑使用 beforeFind
事件,但是我找不到以下伪代码的正确函数:
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
if 'location' in query->getSelectedFields():
replace 'location' by 'location' => 'ST_AsText(location)'
}
当字段要被包含时,我如何用函数替换字段? 最好是在我还没有从控制器中调用 ->select(...)
时也能实现。
CakePHP 论坛上的类似旧问题: https://discourse.cakephp.org/t/read-data-from-spatial-mysql-field-point-polygon/2124
英文:
I am setting up a system for a database with some POINT
columns. I used the cookbook example to build a custom type and it seems to be working. However, to process the POINT
s I need to SELECT
them in a special way:
SELECT ST_AsText(location) as location ...
This isn't hard with the query builder:
$this->Houses->find()->select(['location' => 'ST_AsText(location)'])
However, I would prefer to have this happen by default.
I was thinking of using the beforeFind
event, but I cannot find the right functions for the following pseudo-code:
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
if 'location' in query->getSelectedFields():
replace 'location' by 'location' => 'ST_AsText(location)'
}
How can I have a field be replaced by a function when it is going to be included? Ideally even when I haven't called ->select(...)
yet from a controller.
Older comparable question on CakePHP discourse: https://discourse.cakephp.org/t/read-data-from-spatial-mysql-field-point-polygon/2124
答案1
得分: 0
找到了我的解决方案在 https://stackoverflow.com/questions/48685273/cakephp-3-5-always-apply-astext-mysql-function-to-spatial-field。
这对于 CakePHP 3.8 有效。我决定将事件处理放在一个行为中,以便轻松重用:
// src/Model/Behavior/SpatialBehavior.php
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\ORM\Table;
use Cake\Event\Event;
use ArrayObject;
use Cake\ORM\Query;
/**
* Spatial behavior
*
* Make sure spatial columns are loaded as text when needed.
*
* @property \Cake\ORM\Table $_table
*/
class SpatialBehavior extends Behavior
{
/**
* Default configuration.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* Callback before each find is executed
*
* @param Event $event
* @param Query $query
* @param ArrayObject $options
* @param type $primary
*/
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
$query->traverse(function (&$value) use ($query)
{
if (is_array($value) && empty($value))
{
// Built up standard query when ->select() was never used
//$query->all(); // Execute query to learn columns
$query->select($this->_table);
}
$defaultTypes = $query->getDefaultTypes();
foreach ($value as $key => $field)
{
if (in_array($defaultTypes[$field], ['point', 'polygon']))
{
$value[$key] = $query->func()->ST_AsText([
$this->_table->aliasField($field) => 'identifier'
]);
}
}
$query->select($value);
}, ['select']);
}
}
在我的表对象中:
class HousesTable extends Table
{
public function initialize(array $config)
{
//...
$this->addBehavior('Spatial');
//...
}
}
英文:
Found my solution in https://stackoverflow.com/questions/48685273/cakephp-3-5-always-apply-astext-mysql-function-to-spatial-field.
This works well for CakePHP 3.8. I decided to put the event handling inside a behavior to make it easy to reuse:
<?php
// src/Model/Behavior/SpatialBehavior.php
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\ORM\Table;
use Cake\Event\Event;
use ArrayObject;
use Cake\ORM\Query;
/**
* Spatial behavior
*
* Make sure spatial columns are loaded as text when needed.
*
* @property \Cake\ORM\Table $_table
*/
class SpatialBehavior extends Behavior
{
/**
* Default configuration.
*
* @var array
*/
protected $_defaultConfig = [];
/**
* Callback before each find is executed
*
* @param Event $event
* @param Query $query
* @param ArrayObject $options
* @param type $primary
*/
public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
{
$query->traverse(function (&$value) use ($query)
{
if (is_array($value) && empty($value))
{
// Built up standard query when ->select() was never used
//$query->all(); // Execute query to learn columns
$query->select($this->_table);
}
$defaultTypes = $query->getDefaultTypes();
foreach ($value as $key => $field)
{
if (in_array($defaultTypes[$field], ['point', 'polygon']))
{
$value[$key] = $query->func()->ST_AsText([
$this->_table->aliasField($field) => 'identifier'
]);
}
}
$query->select($value);
}, ['select']);
}
}
In my table object:
class HousesTable extends Table
{
public function initialize(array $config)
{
//...
$this->addBehavior('Spatial');
//...
}
答案2
得分: 0
我正在使用 Cake 4,但也包含一些自 Cake 2 时代的旧代码。我正在使用的列的默认类型是 String 类型,所以之前提到的解决方案不起作用。
我找到的解决方案如下:
public function beforeFind(\Cake\Event\EventInterface $event, \Cake\ORM\Query $query, \ArrayObject $options, $primary): void
{
// ->sql() 转换查询,当未设置选择字段时设置它们
$query->sql();
$select = $query->clause('select');
$fields = array_keys($select);
foreach ($fields as $field) {
if (in_array($field, [geometry fields])) {
$select[$field] = 'ST_AsText(DeliveryFees.polygon)';
}
}
$query->select($select, overwrite: true);
}
英文:
I'm using cake 4 with some legacy code since cake 2, the $query->getDefaultTypes();
for the column I am using is a String type, so that solution didn't work...
The solution I found was this:
public function beforeFind(\Cake\Event\EventInterface $event, \Cake\ORM\Query $query, \ArrayObject $options, $primary): void
{
// ->sql() transforms the query, setting the select fields when it is not setted
$query->sql();
$select = $query->clause('select');
$fields = array_keys($select);
foreach ($fields as $field) {
if (in_array($field, [geometry fields])) {
$select[$field] = 'ST_AsText(DeliveryFees.polygon)';
}
}
$query->select($select, overwrite: true);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论