Преобразование postgresql hstore в php-массив

Есть ли хороший фрагмент кода PHP, чтобы преобразовать postgresql hstore в массив php, который будет правильно переводить unquoted NULL в hstore в php NULL?

EG: предположим, что у нас есть следующая строка hstore:

"k1"=>"v1", "k2"=>NULL, "k3"=>"NULL", "k4"=>"\"v4" (aka SELECT '"k1"=>"v1","k2"=>NULL,"k3"=>"NULL","k4"=>"\\"v4"'::hstore;) 

Как мы можем преобразовать это в следующий массив php?

array ('k1' => 'v1', 'k2' => NULL, 'k3' => 'NULL', 'k4' => '\ "v4');

Я следую следующему конвертеру, но он, похоже, не обрабатывает неупомянутый NULL: https://github.com/chanmix51/Pomm/blob/master/Pomm/Converter/PgHStore.php

Я считаю, что синтаксис будет примерно таким:

 $pdo = new PDO( /*connection string*/ ); // h is the hstore column. $stmt = $pdo->query( "SELECT (each(h)).key, (each(h)).value FROM <table name>" ); $output = array(); foreach( $stmt->fetchAll( PDO::FETCH_NUM ) as $row ) { // $row[ 0 ] is the key, $row[ 1 ] is the value. $output[ $row[ 0 ] ] = $row[ 1 ]; } 
 $hstore = '"A"=>"AAA", "B"=>"BBB"'; print_r(json_decode('{' . str_replace('"=>"', '":"', $hstore) . '}', true)); 

Я попытался использовать метод PgHStore Pomm, однако он разбился на полдюжины или около того разных обстоятельств. Я не помню их всех, но вот несколько, о которых я помню:

  • Отсутствие встроенной поддержки PHP Null
  • Отсутствие надлежащего выхода из двойных кавычек
  • Не удалось избежать значений для безопасной установки PostgreSQL

В конечном итоге я получил собственное решение, придумал PHPG. Поддерживает автоматическое преобразование массивов любого типа данных, Hstores, Геометрические типы данных, Даты / Временные метки и т. Д .: https://github.com/JDBurnZ/PHPG

Если вы хотите решить эту проблему без использования внешнего класса или библиотеки, это будет очень сложно и, скорее всего, будет надежным, и вот почему:

Можно предположить, что вы могли просто взорваться на "," чтобы создать список пар ключ / значение, из которого вы могли бы разбить каждый элемент в списке на "=>" . Это работает, если все значения являются строками, но PostgreSQL Hstores поддерживают значения NULL. Значения NULL не заключаются в двойные кавычки, поэтому приведенное выше решение не будет работать надлежащим образом в этих сценариях.

Поскольку ни одно из других решений, похоже, не работает отлично, у меня был довольно консервативный подход из двух частей:

 protected function toPostgresHStoreLiteral(array $array) { return join(',', F\map($array, function ($value, $key) { switch (strtolower(gettype($value))) { case 'null' : case 'boolean' : $value = $value ? 'true' : ''; break; case 'object' : if (!is_callable([$value, '__toString'])) { throw new \InvalidArgumentException(sprintf('Cannot cast object of type %s to string', get_class($value))); } // deliberate fallthrough case 'integer' : case 'double' : case 'string' : settype($value, 'string'); break; default : throw new \InvalidArgumentException(sprintf('Cannot store values of type %s in an hstore', gettype($value))); } return call_user_func_array('sprintf', array_reduce( [$key, $value], function (array $args, $value) { return array_merge($args, [sprintf('"%s"', addcslashes($value, '"\\'))]); }, ['%s=>%s'] )); })); } 

Этот метод форматирует массив в строку литерала hstore, готовую для вставки в запрос. Это может быть немного более функциональным по стилю, чем нужно, извините за это. ^ _ ^ ;; Зависит от PHP 5.4+ и функционального php .

Чтобы получить значения hstore из Postgres, я использую JSON как средний человек:

 SELECT array_to_json(hstore_to_array(value)) AS value ... 

Это получает JSON-кодированный массив, который можно преобразовать в обычный массив PHP, используя это:

 protected function postgresJsonHstoreToArray($json) { $values = json_decode($json, true); $array = []; for ($i = 0, $length = count($values); $i < $length; $i++) { $key = $values[$i]; $value = $values[++$i]; $array[$key] = $value; } return $array; } 

Это зависит от Postgres 9.2+ или 9.1 с файлом json_91 .

Это скопировано прямо из какого-то пользовательского кода, не обязательно очень обобщенного, но уже выдержало некоторое использование реального мира.

У меня была такая же проблема, поэтому я придумал следующее решение:

 function hstore2array($data) { preg_match_all('/(?:"((?:\\\\"|[^"])+)"|(\w+))\s*=>\s*(?:"((?:\\\\"|[^"])*)"|(NULL))/ms', $data, $matches, PREG_SET_ORDER); $hstore = array(); foreach ($matches as $set) { $key = $set[1] ? $set[1] : $set[2]; $val = $set[4]=='NULL' ? null : $set[3]; $hstore[$key] = $val; } return $hstore; } 

Он правильно распознал бы следующую строку:

 "a"=>"1", "b"=>"", "c"=>NULL, "d"=>"Some \"quoted\" value" 

Установлен преобразователь Hommore Pomm.

Применение:

hstore (<some_array_or_object>, false) преобразует вход в действительный строковый литерал hStore и возвращает его:

 hstore(array('k1' => 'v1', 'k2' => 'v2')) => "k1"=>"v1","k2"=>"v2" 

hstore (<some_array_or_object>) преобразует вход в действительный hStore, с одним кавычком, за которым следует :: hstore

 hstore(array('k1' => 'v1', 'k2' => 'v2')) => '"k1"=>"v1","k2"=>"v2"'::hstore 

hstore (<some_string>) преобразует из строки hstore (как она исходит из запроса) в массив

 hstore('"k1"=>"v1","k2"=>"v2"') => array('k1' => 'v1', 'k2' => 'v2') 

Он обрабатывает NULL (в обоих направлениях) и правильно экранирует / отменяет ключи и значения.

 <?php /** * mixed hstore(mixed $input[, bool $prepared = false]) * Convert from hstore string to array, or from array/object to hstore. * Inner arrays/objects are serialized but deserialization is up to you; you * are expected to keep track of which fields you sent as non-scalars. * * @param mixed $input A string (from hstore) or an array/object * @param type $prepared Array or object to convert to hstore string * @return mixed Depends on the input */ function hstore($input, $prepared=false) { if (is_string($input)) { if ($input === 'NULL') { $output = NULL; } else { $re = '_("|^)(.*?[^\\\\"])"=>"(.*?[^\\\\"])("|$)_s'; preg_match_all($re, $input, $pairs); $mid = $pairs ? array_combine($pairs[2], $pairs[3]) : array(); foreach ($mid as $k => $v) { $output[trim($k, '"')] = stripslashes($v); } } } elseif (is_null($input)) { $output = $prepared ? 'NULL::hstore' : 'NULL'; } elseif (!is_scalar($input)) { foreach ((array)$input as $k => $v) { !is_scalar($v) && ($v = serialize($v)); $entries[] = '"' . addslashes($k) . '"=>' . '"' . addslashes($v) . '"'; } $mid = empty($entries) ? '' : join(', ', $entries); $output = $prepared ? "'{$mid}'::hstore" : $mid; } return $output; } ?> 

edit: добавлен 's' в регулярное выражение, чтобы правильно обрабатывать перевод строки внутри ключей или значений

Попробуй это:

 <?php function encode_hstore($array) { if (!$array) return NULL; if (!is_array($array)) return $array; $expr = array(); foreach ($array as $key => $val) { $search = array('\\', "'", '"'); $replace = array('\\\\', "''", '\"'); $key = str_replace($search, $replace, $key); $val = $val === NULL ? 'NULL' : '"'.str_replace($search, $replace, $val).'"'; $expr[] = sprintf('"%s"=>%s', $key, $val); } return sprintf("'%s'::hstore", implode(',', $expr)); } function decode_hstore($hstore) { if (!$hstore || !preg_match_all('/"(.+)(?<!\\\)"=>(""|NULL|".+(?<!\\\)"),?/U', $hstore, $match, PREG_SET_ORDER)) return array(); $array = array(); foreach ($match as $set) { list(, $k, $v) = $set; $v = $v === 'NULL' ? NULL : substr($v, 1, -1); $search = array('\"', '\\\\'); $replace = array('"', '\\'); $k = str_replace($search, $replace, $k); if ($v !== NULL) $v = str_replace($search, $replace, $v); $array[$k] = $v; } return $array; } $dsn = 'pgsql:host=127.0.0.1;dbname=test'; $user = 'user'; $pass = 'pass'; $pdo = new \PDO($dsn, $user, $pass); $data = array( 'k1' => 'v1', 'k2' => NULL, 'k3' => 'NULL', 'k4' => '"v4', 'k5' => 'a\'b"c\\d,e', 'k6' => '"k1"=>"v1", ', 'k7"=>' => 'v7', 'k8' => '', ); var_dump($data); $expr = encode_hstore($data); echo $expr . PHP_EOL; $encoded = $pdo->query("select {$expr}")->fetchColumn(); echo $encoded . PHP_EOL; $decoded = decode_hstore($encoded); var_dump($decoded); 

Другое решение:

 function convertHstoreToArray($hstoreString) { $hstoreArray = array(); // explode array elements $keyValueStringsArray = explode(', ', $hstoreString); foreach($keyValueStringsArray as $keyValueString) { // trim first and last " $keyValueString = substr($keyValueString, 1, -1); // explode key and value $keyValueArray = explode('"=>"', $keyValueString); $hstoreArray[$keyValueArray[0]] = $keyValueArray[1]; } return $hstoreArray; } 

или

 function convertHstoreToArray($hstoreString) { $hstoreArray = array(); // explode array elements $keyValueStringsArray = explode(', ', $hstoreString); foreach($keyValueStringsArray as $keyValueString) { // explode key and value $keyValueArray = explode('=>', $keyValueString); $key = $keyValueArray[0]; // trim first and last " $key = substr($key, 1, -1); $value = $keyValueArray[1]; if ($value === 'NULL') { $value = null; } else { // trim first and last " $value = substr($value, 1, -1); } $hstoreArray[$key] = $value; } return $hstoreArray; } 

Если вам нужно преобразовать строку в PHP (не в db-запросе), вы можете использовать следующий подготовленный оператор (на основе решения cwallenpoole):

select (each(h)).key, (each(h)).value from (select ?::hstore as h) as s

Вы можете использовать любую переменную PHP в этом запросе:

$st = $db->prepare("select (each(h)).key, (each(h)).value from (select ?::hstore as h) as s"); $st->execute(array('"abc"=>"123", "def"=>"654"'); $out = $st->fetchAll(PDO::FETCH_KEY_PAIR);

На всякий случай кто-то еще заинтересован в этом: с 9.2 существует функция hstore_to_json, которая может использоваться в предложениях select для преобразования содержимого hstore в JSON. Это будет выглядеть так:

 SELECT id, hstore_to_json(my_hstore_field) AS myField FROM mytable WHERE ... 

Тогда в PHP просто используйте

 json_decode($row['myField']) 

декодировать его в php-массив …