MySQL Varlıkları Backticking

7 Cevap php

Bana MySQL varlıkları korumak için olanak aşağıdaki yöntemi var:

public function Tick($string)
{
    $string = explode('.', str_replace('`', '', $string));

    foreach ($string as $key => $value)
    {
    	if ($value != '*')
    	{
    		$string[$key] = '`' . trim($value) . '`';
    	}
    }

    return implode('.', $string);
}

Bu ben bunu yapmak kullanımı için oldukça iyi çalışıyor.

Ancak şimdi ben de işlev çağrıları, yani korumak istiyorsanız, veritabanı, tablo, alan adları ve hatta * operatörü korur:

AVG(database.employees.salary)

Olmak gerekir:

AVG(`database`.`employees`.`salary`) and not `AVG(database`.`employees`.`salary)`

Bunu nasıl gitmeli? Ben normal ifadeler kullanmalı mıyım?

Also, how can I support more advanced stuff, from:

MAX(AVG(database.table.field1), MAX(database.table.field2))

To:

MAX(AVG(`database`.`table`.`field1`), MAX(`database`.`table`.`field2`))

Ben basit / hızlı sürede bu yöntemi tutmak istiyorum unutmayın benim veritabanında tüm varlık isimleri üzerinde oldukça çok dolaşır beri.

7 Cevap

Test case kullanarak ndp Senin için zor işi yapmak için bir regex yarattı verdi. Aşağıdaki regex bir parantez açma tarafından takip edilmez kelimelerle çevresindeki tüm sözcük sınırları yerini alacak.

\b(\w+)\b(?!\()

Aşağıdaki gibi Tick () işlevselliği daha sonra PHP uygulanacak olacaktır:

function Tick($string) 
{
    return preg_replace( '/\b(\w+)\b(?!\()/', '`\1`', $string );
}

Bu SQL deyimi parçaları alıntı olduğunu ve onlar descibe sadece karmaşıklık varsa, bir RegEx büyük bir yaklaşımdır. Tam SQL deyimlerini ya da (örneğin "MAX (AVG (val), MAX (val2))" gibi) tabloların sadece daha karmaşık bileşenleri için bunu yapmak gerekiyorsa Öte yandan, sen tokenize ya da ayrıştırmak gerekir Dize ve doğru bu alıntı yapmak için bunun daha sofistike bir anlayışa sahip.

Düzenli ifade yaklaşımı göz önüne alındığında, daha kolay bir adım olarak işlev adını patlak bulmak ve daha sonra veritabanı / tablo / sütun isimleri teklif için geçerli kodu kullanabilirsiniz. Bu bir RE yapılabilir, ama sağ olsun tricker olacaktır.

Her iki şekilde de, ben son derece birkaç birim test durumlarda yazmak tavsiye ederim. Aslında bu yaklaşım için ideal bir durum şudur: Eğer (size kırmak istemediğiniz) çalışan bazı mevcut durumda var, testleri yazmak kolay, ve eklemek için sadece bir dava daha var.

: Sizin test basitçe olarak başlayabilirsiniz

assert '`ticked`' == Tick('ticked');
assert '`table`.`ticked`' == Tick('table.ticked');
assert 'db`.`table`.`ticked`' == Tick('db.table.ticked');

Ve sonra ekleyin:

assert 'FN(`ticked`)' == Tick('FN(ticked)');
etc.

Bu işleve tüm SQL geçmek için genellikle kötü bir fikir. Bu işe yaramazsa ne zaman tam SQL sözdizimi ayrıştırmak sürece bu şekilde, her zaman, bir davayı bulacaksınız.

SQL yapar önceki bazı soyutlama düzeyinde, adlarına keneler koydu.

Eğer dönemlerde sizin dize patlayabilir önce son karakter bir parantez olup olmadığını kontrol edin. Eğer öyleyse, bu çağrı bir fonksiyonudur.

<?php
$string = str_replace('`', '', $string)
$function = "";
if (substr($string,-1) == ")") {
  // Strip off function call first
  $opening = strpos($string, "(");
  $function = substr($string, 0, $opening+1);
  $string = substr($string, $opening+1, -1);
}

// Do your existing parsing to $string

if ($function == "") {
  // Put function back on string
  $string = $function . $string . ")";
}
?>

Eğer bir "$ dizge" değişkeninin dizide iç içe işlevler veya birden fazla fonksiyonları kullanarak gibi daha gelişmiş durumları kapsayacak gerekiyorsa, bu çok daha gelişmiş bir fonksiyonu olacak, ve bu öğeler olmak değil neden iyi kendinize sormak istiyorum uygun bir şekilde ilk olarak işaretlenmiş ve daha fazla ayrıştırma gerekmez.

EDIT: Updating for nested functions, as per original post edit To have the above function deal with multiple nested functions, you likely need something that will 'unwrap' your nested functions. I haven't tested this, but the following function might get you on the right track.

<?php
function unwrap($str) {
  $pos = strpos($str, "(");
  if ($pos === false) return $str; // There's no function call here

  $last_close = 0;
  $cur_offset = 0; // Start at the beginning
  while ($cur_offset <= strlen($str)) {
    $first_close = strpos($str, ")", $offset); // Find first deep function
    $pos = strrpos($str, "(", $first_close-1); // Find associated opening
    if ($pos > $last_close) {
      // This function is entirely after the previous function
      $ticked = Tick(substr($str, $pos+1, $first_close-$pos)); // Tick the string inside
      $str = substr($str, 0, $pos)."{".$ticked."}".substr($str,$first_close); // Replace parenthesis by curly braces temporarily
      $first_close += strlen($ticked)-($first_close-$pos); // Shift parenthesis location due to new ticks being added
    } else {
      // This function wraps other functions; don't tick it
      $str = substr($str, 0, $pos)."{".substr($str,$pos+1, $first_close-$pos)."}".substr($str,$first_close);
    }
    $last_close = $first_close;
    $offset = $first_close+1;
  }
  // Replace the curly braces with parenthesis again
  $str = str_replace(array("{","}"), array("(",")"), $str);
}

Eğer fonksiyonu ekleyerek ise bir dize sadece arayüzü ile geçen aksine, kodunuzda aramaları, tür denetimi ile ayrıştırma dize değiştirebilirsiniz:

function Tick($value) {
    if (is_object($value)) {
        $result = $value->value;
    } else {
        $result = '`'.str_replace(array('`', '.'), array('', '`.`'), $value).'`';
    }

    return $result;
}

class SqlFunction {
    var $value;
    function SqlFunction($function, $params) {
        $sane = implode(', ', array_map('Tick', $params));
        $this->value = "$function($sane)";
    }
}

function Maximum($column) {
    return new SqlFunction('MAX', array($column));
}

function Avg($column) {
    return new SqlFunction('AVG', array($column));
}

function Greatest() {
    $params = func_get_args();
    return new SqlFunction('GREATEST', $params);
}

$cases = array(
    "'simple'" => Tick('simple'),
    "'table.field'" => Tick('table.field'),
    "'table.*'" => Tick('table.*'),
    "'evil`hack'" => Tick('evil`hack'),
    "Avg('database.table.field')" => Tick(Avg('database.table.field')),
    "Greatest(Avg('table.field1'), Maximum('table.field2'))" => Tick(Greatest(Avg('table.field1'), Maximum('table.field2'))),
);

echo "<table>";
foreach ($cases as $case => $result) {
    echo "<tr><td>$case</td><td>$result</td></tr>";
}

echo "</table>";

Kodunuzu gelecekteki okuyucularına okunaklı kalan Bu herhangi bir SQL enjeksiyon mümkün önler.

Sen preg_replace_callback() Pars en az bir seviye atlamak için Tick() yöntemi ile birlikte kullanabilirsiniz:

public function tick($str) 
{
  return preg_replace_callback('/[^()]*/', array($this, '_tick_replace_callback'), $str);
}

protected function _tick_replace_callback($str) {
  $string = explode('.', str_replace('`', '', $string));
  foreach ($string as $key => $value)
  {
    if ($value != '*')
    {
      $string[$key] = '`' . trim($value) . '`';
    }
  }
  return implode('.', $string);
}

Eğer SQL sorgu üreten ya da sizin için geçirilen? Eğer sorgu oluşturma Eğer ben bütün sorgu dize ters tırnakların ya da hiç başka ne gerek sarmak istiyorum sadece parms / değerlerini geçmek olmaz.

ÖRNEK:

   function addTick($var) {
      return '`' . $var . '`';
   }

   $condition = addTick($condition);

   $SQL = 'SELECT' . $what . ' 
      FROM ' . $table . ' 
      WHERE ' . $condition . ' = ' . $constraint;

Bu sadece bir sahte ama size geçmek veya döngü kodunuzu ve oldukça sorgu dizesini ayrıştırma ve backticks ekleyerek daha sorgu dizesi inşa edebilirsiniz fikir olsun.