CakePHP 3 – 在表对象中默认使用select()中的SQL函数。

huangapple go评论58阅读模式
英文:

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 POINTs 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);
    }

huangapple
  • 本文由 发表于 2020年1月3日 19:52:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/59578158.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定