如何在cakephp 3.3.13

时间:2017-03-08 06:10:49

标签: cakephp cakephp-3.x

我在客户端有一个使用st-table显示数据的表。在进行过滤操作时,我使用st-pipe插件。客户端将传递ajax get请求,如下所示:

search= {"assigned_to.user_info.full_name":"alonso","created_by.user_info.full_name":"buena"}

将其传递给服务器,控制器处理它并生成一个数组:

$matchingStatements = [
  'AssignedTos.UserInfos' => [
    'OR' => [
        "UserInfos.firstname LIKE" => "%alonso%",
        "UserInfos.lastname LIKE"  => "%alonso%"
    ]
  ],
  'CreatedBies.UserInfos' => [
    'OR' => [
        "UserInfos.firstname LIKE" => "%buena%",
        "UserInfos.lastname LIKE"  => "%buena%"
    ]
  ]
];

通过这样做,过滤器语句的数量是动态的。 这个$ matchingStatements将在foreach循环中运行,在每个传递中它将调用$ leads->匹配(...)语句,如下所示:

$userFields = [
    'fields' => ['id', 'username'],
    'UserInfos' => ['fields' => ['user_id', 'firstname', 'lastname']]
];
$option = [
    'contain' => [
        'Contacts',
        'AssignedTos' => $userFields,
        'LeadTypes',
        'LeadStatuses',
        'Sources',
        'PropertyRequirements' => ['Locations', 'PropertyCategories', 'Listings'],
        'Activities',
        'Events',
        'CreatedBies'=> $userFields,
        'UpdatedBies'=> $userFields
    ]
];

$leads = $leadsTable->find('all', $option);

foreach ($matchingStatements as $target=>$where) {
  $leads = $leads->matching($target, function ($q) use ($where) {
    return $q->andWhere($where);
  });
}
...
}

但是,似乎$ lead只会成为仅使用$ matchingStatements [0]调用匹配的结果。 所以匹配的第二个匹配('CreatedBies.UserInfos')...不适用。

示例输出类似于以下json:

"leads": [
{
    "id": 1,
    "created_by": {
        "id": 327,
        "username": "Cedric61",
        "user_info": {
            "firstname": "Buena",
            "lastname": "Causey",
            "full_name": "Buena Causey"
        }
    },
    "assigned_to": {
        "id": 1,
        "username": "Grigsby1995",
        "user_info": {
            "firstname": "Alonso",
            "lastname": "Stauffer",
            "full_name": "Alonso Stauffer"
        }
    },
},
{
    "id": 2,
    "created_by": {
        "id": 327,
        "username": "Cedric61",
        "user_info": {
            "firstname": "Buena",
            "lastname": "Causey",
            "full_name": "Buena Causey"
        }
    },
    "assigned_to": {
        "id": 2,
        "username": "ccp123",
        "user_info": {
            "firstname": "Christoper",
            "lastname": "Pedros",
            "full_name": "Christoper Pedros"
        }
    },
}
];

但它假设只输出第一个对象。

关于我应该如何获得我想要的结果的任何想法都非常感谢。

谢谢!

更新: 由于@JohnWayne:

,我将foreach循环更新为以下内容
    foreach ($matchingStatements as $target=>$where) {
        $leads = $leads->innerJoinWith($target, function ($q) use ($where) {
            return $q->andWhere($where);
        });
    }

当输出$ leads-> sql()时,它会显示两个内连接被调用但是对于第二个连接,它不应用where()条件:

...
FROM 
    leads Leads 
INNER JOIN 
    users AssignedTos 
ON 
    AssignedTos.id = (Leads.user_id) 
    INNER JOIN 
        user_infos UserInfos 
    ON 
        (UserInfos.firstname like :c0 AND AssignedTos.id = (UserInfos.user_id)) 
INNER JOIN 
    users CreatedBies 
ON 
    CreatedBies.id = (Leads.created_by_id) 
INNER JOIN 
    contacts Contacts
...

AssignedTos加入检查条件但不在CreatedBies加入。

我还缺少什么?

更新2:

如果我手动将sql更改为以下并在shell上手动运行查询是我所期望的我只是不知道如何做到这一点我cakephp:

...
    FROM 
        leads Leads 
    INNER JOIN 
        users AssignedTos
    ON 
        AssignedTos.id = (Leads.user_id) 
    INNER JOIN 
        users CreatedBies 
    ON 
        CreatedBies.id = (Leads.created_by_id) 
INNER JOIN 
    user_infos AS AssignedTosUserInfos
ON 
    (AssignedTosUserInfos.firstname like '%tamara%' AND AssignedTos.id = (AssignedTosUserInfos.user_id))
INNER JOIN
    user_infos AS UpdatedBiesUserInfos
ON
    (UpdatedBiesUserInfos.firstname like '%gertude%' AND UpdatedBies.id = (UpdatedBiesUserInfos.user_id))   
INNER JOIN
    user_infos AS CreatedBiesUserInfos
ON
    (CreatedBiesUserInfos.firstname like '%gertude%' AND CreatedBies.id = (CreatedBiesUserInfos.user_id))
INNER JOIN 
    contacts Contacts 
ON 
    Contacts.id = (Leads.contact_id) 
...

3 个答案:

答案 0 :(得分:0)

$leads = $leadsTable->find('all', $option);

然后收集一个列表中的所有条件

$myList = [];
foreach ($matchingStatements as $key => $val) {
  $myList[$key] = $val;
}

然后进行一次匹配

  $leads->matching($target, function ($q) use ($myList) {
    return $q->andWhere(['myField IN' => $myList]);
  });

如果你有更多关系而不是 - >匹配使用 - > innerJoinWith喜欢

$leads->innerJoinWith('TableName', function ($q) {
    return $q->where(['TableName.FieldName IN' => 'conditions']);
});

等等

更多信息Cakephp 3.x Inner Join With

答案 1 :(得分:0)

最后我找到了一个解决方案,我只是不确定它是否优雅。 我决定使用join()方法并手动构建sql join。 当客户端传递搜索谓词时,它会将其转换为一个数组,该数组将声明以下sql foreach找到的谓词:

...    
INNER JOIN 
        associated_table AssociatedTable
    ON 
        AssociatedTable.id = (MainTable.user_id) 
    INNER JOIN 
        deeper_table AS AssociatedTableDeeperTable
    ON 
        (AssociatedTableDeeperTable.field like '%something%' AND AssociatedTable.id = (AssociatedTableDeeperTable.user_id))
...

并将其添加到要传递给join(...)的谓词数组中:

$myJoins = [];
foreach ( ... ) {
    $search_text = ... //taken from client-side passed data
    $table = ... // table = 'AssociatedTable' take from client-side passed data that has been normalized
    $myJoins[$table] = [
        'table'      => 'associated_table',
        'type'       => 'INNER',
        'conditions' => "$table.id = main_table.id"
    ];
    $myJoins["{$table}DeeperTable"] = [
        'table'      => 'deeper_table',
        'type'       => 'INNER',
        'conditions' => "({$table}DeeperTable.firstname LIKE '%{$search_text}%' AND $table.id = ({$table}DeeperTable.user_id))"
    ];
}

$leads = $leadsTable->find('all',...)
if (count($myJoins)>0) {
    $leads->join($myJoins);
}

如果有人有更好的答案,那就知道了。 谢谢!

答案 2 :(得分:0)

SQL注入漏洞

首先要做的事情。从您的示例中可以看出,控制器对构建条件数组的确切做法并不清楚,因此我要指出在处理用户提供的表时应用白名单或类似技术非常重要/列名称,因为key => value条件数组中的键按原样插入到查询中!

使用唯一关联别名

从SQL输出判断,问题是非唯一UserInfos关联别名,您不能多次包含同一个别名,查询构建器没有任何自动重命名功能或类似

如果您希望能够使用速记匹配和/或连接方法,那么您可以通过将关联重命名为类似于您在工作SQL示例中显示的内容来解决问题,例如{{ 1}},AssignedTosUserInfos

CreatedBiesUserInfos

条件阵列需要相应地改变:

// ...

class AssignedTosTable extends Table
{
    public function initialize(array $config)
    {
        // ...

        $this->belongsTo('AssignedTosUserInfos', [
            'className' => 'UserInfos'
        ]);
    }
}

共享表的动态关联别名

如果您没有$matchingStatements = [ 'AssignedTos.AssignedTosUserInfos' => [ 'OR' => [ "AssignedTosUserInfos.firstname LIKE" => "%alonso%", "AssignedTosUserInfos.lastname LIKE" => "%alonso%" ] ], // ... ]; AssignedTos等关联的具体表类,并且您正在使用共享的CreatedBies类,则可以创建关联使用当前表别名动态别名,这样就可以根据为父关联设置的别名创建正确的关联:

UsersTable

如果父级,即// ... class UsersTable extends Table { public function initialize(array $config) { // ... $this->belongsTo($this->registryAlias() . 'UserInfos', [ 'className' => 'UserInfos' ]); } } 类,将创建如下的关联:

LeadsTable

然后上面的$this->belongsTo('AssignedTos', [ 'className' => 'Users' ]); 类中的动态关联将被命名为UsersTable。对于AssignedTosUserInfos关联,它将是CreatedBies,依此类推。