【问题标题】:Better solutions for a annoying nested if statements in routes of php applicationphp 应用程序路由中烦人的嵌套 if 语句的更好解决方案
【发布时间】:2014-08-11 14:31:42
【问题描述】:

我使用的是 slim php,但这在 slim 中并不是很具体,这在不同 php 框架的路由中很常见,尤其是在使用 rest 应用程序时。

好的,这是可以在博客或归档应用程序中找到的最常见的路径

 $app->get('docs(/:year(/:month(/:day)))', function($y=0;$m=0;$d=0) use ($app){
    if ($year !== 0 && $month !==0 && $day !== 0) {
        // query database with conditions of year, month, day
    } else if ($year !== 0 && $month !== 0) {
        // query database with conditions of year, month
    } else if ($year !== 0) {
        // query database with conditions of year
    } else {
        // query database, return all
    }
 })

您还想添加get 参数以缩小结果范围,例如limitoffset

 $app->get('docs(/:year(/:month(/:day)))', function($y=0;$m=0;$d=0) use ($app){
    // additionally you optionally allowed filters like: limit, offset, all     
    $tmpLimit = $app->request()->get('limit');
    $tmpOffSet = $app->request()->get('offset');

    $limit = isset($tmpLimit) ? $tmpLimit : 10;
    $offset = isset($tmpOffSet) ? $tmpOffSet : 0;

    ... below add the code previously, and query would change according to if filters(limit,offset) has been set.
 })

还有其他解决方案吗?有太多代码告诉我我做得不对。

【问题讨论】:

  • 有条件地构造您的查询:if ($year) $where[] = 'year = ?'; ... sprintf('WHERE %s', join(' AND ', $where)) ...?
  • 我遇到的这个@deceze 的一个问题是,如果你像你提到的那样通过有条件构造的$query,我会在绑定变量时遇到问题,就像这样:gist.github.com/8566bba7235a6ba12276.git 那就是如果我只提供年份,或者只提供年份和月份。
  • if ($year) { $where[] = 'year = :year'; $bind['where'] = $year; } ... query($query, $bind) ...?!
  • @deceze 如果我发布答案,你介意吗?
  • 当然不是。来吧。

标签: php rest if-statement optimization


【解决方案1】:

我想说您应该稍微研究一下查询构建器对象。例如Doctrine DBAL 中的那个。这使您可以有条件地构造查询,而无需将字符串连接在一起(这会打开潘多拉魔盒以解决各种可能的错误和安全问题):

$queryBuilder
    ->select('b.id', 'b.title')
    ->from('blog', 'b');

if ($year) {
    $queryBuilder->where('b.year = :year')
                 ->setParameter('year', $year);
}
if ($month) {
    queryBuilder->where('b.month = :month')
                ->setParameter('month', $month);
}
if ($day) {
    $queryBuilder->where('b.day = :day')
                 ->setParameter('year', $day)
}

除此之外,您似乎将所有功能都放在了路由中,但您应该考虑将一些任务分成服务。快速浏览 slimphp 文档我找不到任何“服务”的概念,但这意味着您向 $app 添加一个单独的函数,以便您可以重用该功能。理所当然地,您将希望从多个路由中获取“博客”。它应该像 (docs here) 一样简单:

$app->findBlogPosts = function($y = null, $m = null, $d = null) use ($app) {
  // do the DB stuff here, return results
}

我将参数 de default 更改为 null,因为 0 的 $year 值没有任何意义。如果你打算说“这东西可能没有价值”,请使用该语言的“null”概念——每种编程语言都有这样的概念。

【讨论】:

  • 我想保持简单,注入服务会增加我的代码的复杂性。但我会接受您将 0 更改为 null 的建议。
  • 小心“复杂性”参数。从长远来看,添加服务可能会降低复杂性,一旦您开始将代码复制到多个路由中;-)
  • 也是我认为不允许YEAR(date_created) = :year之类的查询生成器,对吧?我已经在使用一个惯用语和 paris,可以按照您的建议进行操作,但我仍然需要使用 raw_query,因为我需要传递该函数。
  • 虽然从长远来看我同意你的观点,是的,你知道我是 angularjs 和其他完美实现 SOC 的框架的粉丝,但我仍然需要让它像这样简单。现在。
  • 听起来不错,这是最重要的。至于允许原生 SQL 函数的查询生成器;您可以通过 db->connection 运行一个字符串,但这会否定 queryBuilder 的使用。
【解决方案2】:

您可以使用中间件(Route-Middleware/Middleware) 和/或钩子(Hooks-Overview) 来简化您的路线。

路由中间件:

$mw = function($app) {
  return function() use($app) {
     $tmpLimit = $app->request()->get('limit');
     $tmpOffSet = $app->request()->get('offset');
     $app->docsLimit = isset($tmpLimit) ? $tmpLimit : 10;
     $app->docsOffSet = isset($tmpOffSet) ? $tmpOffSet : 0; 
  };
};

路线:

$app->get('/docs(/:year(/:month(/:day)))', $mw($app),function($y=0,$m=0,$d=0) use ($app) {

    echo $app->docsLimit."<br/>";
    echo $app->docsOffSet."<br/>";

    ... your previously code
});

挂钩:

$app->hook('slim.before.router', function() use($app){
    if (strpos($app->request()->getPath(), '/docs') === 0) {
        $tmpLimit = $app->request()->get('limit');
        $tmpOffSet = $app->request()->get('offset');
        $app->docsLimit = isset($tmpLimit) ? $tmpLimit : 10;
        $app->docsOffSet = isset($tmpOffSet) ? $tmpOffSet : 0;
    }
});

路线:

$app->get('/docs(/:year(/:month(/:day)))', function($y=0,$m=0,$d=0) use ($app) {

    echo $app->docsLimit."<br/>";
    echo $app->docsOffSet."<br/>";

    ... your previously code
});

【讨论】:

    【解决方案3】:

    感谢@deceze@ramon指导我重构代码的全过程,

    首先我想到了有条件地构造 SQL 语句,这让我想到了:

    if ($year !== 0) {
       $where[] = 'YEAR(date_created) = :year';
       $bind['where'] = $year; 
    }
    ... // if month, if year..
    ...
    $query = sprintf('SELECT * FROM table WHERE %s', join(' AND ', $where));
    Document::raw_query($query, $bind)->find_array();
    

    根据@ramon 的说法,这将或可能导致问题,他建议使用查询构建器, 这样:

    我可以这样做:

    $queryBuilder
    ->select('b.id', 'b.title')
    ->from('blog', 'b');
    

    查看巴黎提供的基本 where 方法,如其文档中所见,我无法在活动记录库的 where 方法中传递本机 sql 函数,但挖掘更多,我意识到它是建立在最重要的是,如果可以通过 where 子句传递函数,我只需要查看文档,然后在我的 IDE 中玩了一会儿,我发现 where_raw 存在。

    我想出了这个......

    $where = array();
    $bind = array();
    
    if (!is_null($day)) {
        $where[] = 'DAY(`date_created`) = ?';
        $bind[] = $day;
    }
    
    if (!is_null($month)) {
        $where[] = 'MONTH(`date_created`) = ?';
        $bind[] = $month;
    }
    
    if (!is_null($year)) {
        $where[] = 'YEAR(`date_created`) = ?';
        $bind[] = $year;
    
        $query = sprintf('%s', join(' AND ', $where));
        $foo = Foo::where_raw($query, $bind)
                ->order_by_desc('date_created')
                ->offset($offset)
                ->limit($limit)
                ->find_array();
    } else {
        $documents = Foo::order_by_desc('date_created')
                ->offset($offset)
                ->limit($limit)
                ->find_array();
    }
    

    我对接受用户的输入有点信心,orm 在执行前准备好查询,我认为这会做一些安全检查。

    【讨论】:

    • @Vaidas 的解决方案更好
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 2014-02-03
    • 2017-08-23
    • 1970-01-01
    相关资源
    最近更新 更多