mysqli bind_param中可变长度参数列表的最佳实践



我正在寻找处理 mysqli bind_param方法的可变长度参数的最佳方法/模式/习语。

我在 PHP 中创建了一个类似于 mysqli_stmt::bind_param 的可变参数函数。因此,第一个必需参数需要一个字符串,其他参数是可选的,但第一个参数的字符串长度必须等于可选参数的数量。

函数定义:

function bind_param_test($types, ...$args){
    $typesArr = str_split($types);
    foreach($args as $i => $n) {
        switch ($typesArr[$i]) {
            case 'd':
                echo "Decimal: " . $n . '<br>';
                break;
            case 's':
                echo "String: " . $n . '<br>';
                break;
            default:
                break;
        }
    }
}

现在我正在尝试使用它。

// ------------- CASE 1 ---------------------------
// In this case I have 5 arguments. So I can do this: 
$a = 10;
$b = 20;
$c = 'hello';
$d = 'cheese';
$e = 500;
if($a && $b && $c && $d && $e){
    bind_param_test('ddssd', $a, $b, $c, $d, $e);
}
/* OUTPUT:
Decimal: 10
Decimal: 20
String: hello
String: cheese
Decimal: 500
 */
echo '<hr>';

请参阅下面的案例 2 和 3。

// ------------- CASE 2 ---------------------------
// I'm using 4 variables
$e = null;
if($a && $b && $c && $d && !$e){
    bind_param_test('ddss', $a, $b, $c, $d);
}
/* OUTPUT:
Decimal: 10
Decimal: 20
String: hello
String: cheese
 */
echo '<hr>';
// ------------- CASE 3 ---------------------------
// 3 variables
$d = null;
$e = null;
if($a && $b && $c && !$d && !$e){
    bind_param_test('dds', $a, $b, $c);
}
/* OUTPUT:
Decimal: 10
Decimal: 20
String: hello  */
echo '<hr>';

案例 2 和 3 是硬编码的,因此不是 DRY。我想让它变干,以防万一 4。请参阅案例 4。

案例 4:通过数组处理 mysqli bind_param的模式

// ------------- CASE 4 ---------------------------
// I want to have a more generic approach.
$argumentList = array(
    'a' => 10,
    'b' => 20,
    'c' => null,
    'd' => null,
    'e' => null,
);
$argumentList = array_filter($argumentList);
$actionStr = '';
foreach($argumentList as $k => $v){
    if(is_numeric($v)){
        $actionStr .= 'd';
    }
    if(is_string($v)){
        $actionStr .= 's';
    }
}
$varList = array_values($argumentList);
$param_arr = array();
$param_arr[] = $actionStr;
$param_arr = (array_merge($param_arr, $varList));
call_user_func_array('bind_param_test', $param_arr);
/* OUTPUT:
Decimal: 10
Decimal: 20  */

问题:你能找到比案例 4 更好的方法吗?我问是因为我认为案例4很神秘。恐怕会让其他开发人员感到惊讶。

我想

回答我自己的问题。我只是想改善这个案子。这是我的进步。

$argumentList = array(
    'a' => 10,
    'b' => 20,
    'c' => null,
    'd' => null,
    'e' => null,
);
$argumentList = array_filter($argumentList);
$mysqli_param_types = '';
foreach($argumentList as $k => $v){
    if(is_numeric($v)){
        $actionStr .= 'd';
    }
    if(is_string($v)){
        $actionStr .= 's';
    }
}
// instead of array_values() and array_merge()
// its more clear to iterate over argumentList (even if its twice)
$mysqli_reference_params[] =& $actionStr;
foreach($argumentList as &$var) {
    $mysqli_reference_params[] =& $var;
}
call_user_func_array('bind_param_test',  $mysqli_reference_params);
/* OUTPUT:
Decimal: 10
Decimal: 20  */

好吧,为什么这很重要?这很重要,因为我希望有一个与bind_param和可变长度参数列表配合良好的模式。

成语:mysqli 中的可变长度预准备语句

结合

预准备语句处理 mysqli 中可变长度参数列表的习语。在这里看到一个真正的代码习语。

$sql = "    SELECT  p.`productID`,
                    p.`productCode`,
                    p.`productName`,
                    p.`productDescription`,
                    p.`productPrice`,
                    p.`productRating`,
                    p.`productDateTime`
            FROM `products` as p
            WHERE `p`.`productID` IS NOT NULL ";
        if($searchCode){
            $sql .= "AND p.`productCode` = ? ";
        }
        if($searchName){
            $sql .= "AND p.`productName` = ? ";
        }
        if($searchDescription) {
            $sql .= "AND p.`productDescription` = ? ";
        }
        if($searchPrice) {
            $sql .= "AND p.`productPrice` = ? ";
        }
        if($searchRating) {
            $sql .= "AND p.`productRating` = ? ";
        }
        if($searchDateTime) {
            $sql .= "AND p.`productDateTime` = ? ";
        }
        // Create mysqli_stmt
        $statement = $mysqli->prepare($sql);
        if ($statement instanceof mysqli_stmt === false) {
            return null;
        }
        // Handle search variables through bind_param()
        $bindParamVarList = array(
            'productCode' => $searchCode,
            'productName' => $searchName,
            'productDescription' => $searchDescription,
            'productPrice' => $searchPrice,
            'productRating' => $searchRating,
            'productDateTime' => $searchDateTime,
        );
        $bindParamVarList = array_filter($bindParamVarList);
        if($bindParamVarList){
            $types = '';
            foreach($bindParamVarList as &$v){
                if(is_numeric($v)){
                    $types .= 'd';
                    $v = (float)$v;
                    continue;
                }
                if(is_string($v)){
                    $types .= 's';
                    continue;
                }
            }
            // call_user_func_array needs references and not values
            $mysqli_reference_params = array();
            $mysqli_reference_params[] =& $types;
            foreach($bindParamVarList as &$bindParamVar) {
                $mysqli_reference_params[] =& $bindParamVar;
            }
            call_user_func_array(array($statement,  'bind_param'),  $mysqli_reference_params);
        }
        $statement->execute();
        $statement->store_result();
        $amount = $statement->num_rows;
        $statement->bind_result($productID,
                                $productCode,
                                $productName,
                                $productDescription,
                                $productPrice,
                                $productRating,
                                $productDateTime
        );
        $products = array();
        $productsSet = array();
        while($statement->fetch()){
            $product = array();
            $product['productID'] = $productID;
            $product['productCode'] = $productCode;
            $product['productName'] = $productName;
            $product['productDescription'] = $productDescription;
            $product['productPrice'] = $productPrice;
            $product['productRating'] = $productRating;
            $product['productDateTime'] = $productDateTime;
            $products[] = $product;
        }
        // JavaScript is only able to work with indexed Array's
        $productsSet[] = $products;
        $productsSet[] = $amount;

您可以减少处理所需所有类型的数组,并对不允许的类型抛出异常。此外,更现代的PHP支持数组销毁...$array[$var1, $var2] = $array

当我通过$actionStr = 'xyz'时,您还应该抛出异常。

这个例子是根据您的顺序is_numericis_string'123'123处理为数字:

// ------------- CASE 4 ---------------------------
$argumentList = array(
  'a' => 'ten',
  'b' => 20,
  'c' => '0',
  'd' => null,
  'e' => 0,
//  'e' => false,   // throws Exception
//  'f' => [1,2,3], // throws Exception
);

[$argumentList, $actionStr] = array_reduce
(
  $argumentList,
  function(array $aggr, $v) : array
  {
    if(null === $v)
      return $aggr;
    if(is_numeric($v))
    {
      $aggr[0][] = $v;
      $aggr[1]  .= 'd';
      return $aggr;
    }
    if(is_string($v))
    {
      $aggr[0][] = $v;
      $aggr[1]  .= 's';
      return $aggr;
    }
    throw new InvalidArgumentException('Type ' . gettype($v) . ' not allowed.');
  },
  [[], '']
);
doSomething($actionStr, ...$argumentList);
如果您希望

'123'作为字符串而不是数字处理,请先检查is_string

相关内容

  • 没有找到相关文章

最新更新