reliably convert string containing PHP array info to array [duplicate] - php

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Simulate php array language construct or parse with regexp?
suppose I have the string
$str = "array(1,3,4),array(array(4,5,6)),'this is a comma , inside a string',array('asdf' => 'lalal')";
and I try to explode this into an array by comma so that the desired end result is
$explode[0] = array(1,3,4);
$explode[1] = array(array(4,5,6));
$explode[2] = 'this is a comma , inside a string';
$explode[3] = array('asdf' => 'lalal');
simply calling explode(',',$str) is not going to cut it since there are also commas within those chunks...
is there a way to explode this reliably even if there is commas inside the desired chunks

is there a way to explode this reliably even if there is commas inside the desired chunks?
PHP by default does not provide such a function. However you have a compact subset of PHP inside your string and PHP offers some tools here: A PHP tokenizer and a PHP parser.
Therefore it's possible for your string specification to create a helper function that validates the input against allowed tokens and then parse it:
$str = "array(1,3,4),array(array(4,5,6)),'this is a comma , inside a string', array('asdf' => 'lalal')";
function explode_string($str)
{
$result = NULL;
// validate string
$isValid = FALSE;
$tokens = token_get_all(sprintf('<?php %s', $str));
array_shift($tokens);
$valid = array(305, 315, 358, 360, 371, '(', ')', ',');
foreach($tokens as $token)
{
list($index) = (array) $token;
if (!in_array($index, $valid))
{
$isValid = FALSE;
break;
}
}
if (!$isValid)
throw new InvalidArgumentException('Invalid string.');
// parse string
$return = eval(sprintf('return array(%s);', $str));
return $return;
}
echo $str, "\n";
$result = explode_string($str);
var_dump($result);
The tokens used are:
T_LNUMBER (305)
T_CONSTANT_ENCAPSED_STRING (315)
T_DOUBLE_ARROW (358)
T_ARRAY (360)
T_WHITESPACE (371)
The token index number can be given a token name by using token_name.
Which gives you (Demo):
Array
(
[0] => Array
(
[0] => 1
[1] => 3
[2] => 4
)
[1] => Array
(
[0] => Array
(
[0] => 4
[1] => 5
[2] => 6
)
)
[2] => this is a comma , inside a string
[3] => Array
(
[asdf] => lalal
)
)

You can write a simple parser:
function explode_str_arr($str) {
$str.=',';
$escape_char = '';
$str_len = strlen($str);
$cur_value = '';
$return_arr = array();
$cur_bracket_level = 0;
for ($i = 0; $i < $str_len; $i++) {
if ($escape_char) {
if ($str[$i] === $escape_char) {
$escape_char = '';
}
$cur_value.=$str[$i];
continue;
}
switch ($str[$i]) {
case '\'':
case '"':
$escape_char = $str[$i];
break;
case '(':
$cur_bracket_level++;
break;
case ')':
$cur_bracket_level--;
break;
case ',':
if (!$cur_bracket_level) {
$return_arr[] = $cur_value;
$cur_value = '';
continue 2;
}
}
$cur_value.=$str[$i];
}
return $return_arr;
}
It is ugly unicode-breaking fast code, but I think you may get the idea.

Related

How to work with a function preg_replace with a large number (1000000) of values in $patterns and $replacements arrays?

Hello dear programmers! I'm having a problem with the speed of the function preg_replace().
When I had little values (words) in the $patterns and $replacements arrays, the problem was not with the speed of searching and replacing in text from arrays, and when the number of the value in the arrays increased by 1.000.000 then the function preg_replace() was repeatedly slowed down. How can I search and replace in the text if I have more than 1,000,000 values (words) in the arrays? How to make a replacement as quickly as possible? Can the solution of the problem be buffered or cached? What advise, how correctly to me to act?
Here is an example of my array:
$patterns =
array
(
0 => "/\bмувосокори\b/u",
1 => "/\bмунаггас\b/u",
2 => "/\bмангит\b/u",
3 => "/\bмангития\b/u",
4 => "/\bмунфачир\b/u",
5 => "/\bмунфачира\b/u",
6 => "/\bманфиатпарасти\b/u",
7 => "/\bманфиатчу\b/u",
8 => "/\bманфиатчуи\b/u",
9 => "/\bманфиатхох\b/u",
10 => "/\bманфи\b/u",
...........................
1000000 => "/\bмусби\b/u"
)
$replacements =
array
(
0 => "мувосокорӣ",
1 => "мунағғас",
2 => "манғит",
3 => "манғития",
4 => "мунфаҷир",
5 => "мунфаҷира",
6 => "манфиатпарастӣ",
7 => "манфиатҷӯ",
8 => "манфиатҷӯӣ",
9 => "манфиатхоҳ",
10 => "манфӣ",
.....................
1000000 => "мусбӣ"
);
$text = "мувосокори мунаггас мангит мангития мунфачир манфиатпарасти...";
$result = preg_replace($patterns, $replacements, $text);
I use this javascript function in index.html file:
<script>
function response(str) {
if (str.length == 0) {
document.getElementById("text").innerHTML = "";
return;
} else {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("text").innerHTML = this.responseText;
}
};
xmlhttp.open("GET", "response.php?request=" + str, true);
xmlhttp.send();
}
}
</script>
PHP file response.php source:
<?php
$patterns = array();
$replacements = array();
$request = $_REQUEST["request"];
$response = "";
if ($request !== "") {
$start = microtime(true);
$response = preg_replace($patterns, $replacements, $request);
$stop = microtime(true);
$time_replace = $stop - $start;
}
echo $response === "" ? "" : $response."<br>Time: $time_replace";
?>
The time complexity of your algorithm is roughly O(nm) where n is the number of words in your replacement array, and m the number of words in the request.
Since all the patterns seem to look for words (\b before and after), and don't use any other regular expression syntax (only literal characters) you will get better performance by splitting the request into words and looking them up in an associative array, without the need to use regular expressions at all.
So define your pattern/replacement data as an associative array, like this:
$dict = array(
"мувосокори" => "мувосокорӣ",
"мунаггас" => "мунағғас",
"мангит" => "манғит",
"мангития" => "манғития",
"мунфачир" => "мунфаҷир",
"мунфачира" => "мунфаҷира",
"манфиатпарасти" => "манфиатпарастӣ",
"манфиатчу" => "манфиатҷӯ",
"манфиатчуи" => "манфиатҷӯӣ",
"манфиатхох" => "манфиатхоҳ",
"манфи" => "манфӣ",
...........................
"мусби" => "мусбӣ"
);
Then use preg_replace_callback to find each word in the request and look it up in the above dictionary:
$response = preg_replace_callback("/\pL+/u", function ($m) use ($dict) {
return isset($dict[$m[0]]) ? $dict[$m[0]] : $m[0];
}, $request);
The time complexity will be linear to the number of words in the request.
Handling upper/lower case
If you need to match also any variation in the capitalisation of the words, then it would be too much to store any such variation in the dictionary. Instead, you would keep the dictionary in all lowercase letters, and then use the code below. When there is a match with the dictionary, it checks what the capitalisation is of the original word, and applies the same to the replacement word:
$response = preg_replace_callback("/\pL+/u", function ($m) use ($dict) {
$word = mb_strtolower($m[0]);
if (isset($dict[$word])) {
$repl = $dict[$word];
// Check for some common ways of upper/lower case
// 1. all lower case
if ($word === $m[0]) return $repl;
// 2. all upper case
if (mb_strtoupper($word) === $m[0]) return mb_strtoupper($repl);
// 3. Only first letters are upper case
if (mb_convert_case($word, MB_CASE_TITLE) === $m[0]) return mb_convert_case($repl, MB_CASE_TITLE);
// Otherwise: check each character whether it should be upper or lower case
for ($i = 0, $len = mb_strlen($word); $i < $len; ++$i) {
$mixed[] = mb_substr($word, $i, 1) === mb_substr($m[0], $i, 1)
? mb_substr($repl, $i, 1)
: mb_strtoupper(mb_substr($repl, $i, 1));
}
return implode("", $mixed);
}
return $m[0]; // Nothing changes
}, $request);
See it run on repl.it
Converting your existing arrays
You can use a little piece of code to transform your current $patterns and $replacements arrays to the new data structure, to avoid you have to do it "manually":
foreach ($patterns as $i => $pattern) {
$dict[explode("\b", $pattern)[1]] = $replacements[$i];
}
Of course, you should not include this conversion in your code, but just run it once to produce the new array structure and then put that array literal in your code.

Php regex returning repeats in nested arrays

I'm trying to get a list of all occurrences of a file being included in a php script.
I'm reading in the entire file, which contains this:
<?php
echo 'Hello there';
include 'some_functions.php';
echo 'Trying to find some includes.';
include 'include_me.php';
echo 'Testtest.';
?>
Then, I run this code on that file:
if (preg_match_all ("/(include.*?;){1}/is", $this->file_contents, $matches))
{
print_r($matches);
}
When I run this match, I get the expected results... which are the two include sections, but I also get repeats of the exact same thing, or random chunks of the include statement. Here is an example of the output:
Array (
[0] => Array ( [0] => include 'some_functions.php'; [1] => include 'include_me.php'; )
[1] => Array ( [0] => include 'some_functions.php'; [1] => include 'include_me.php'; ) )
As you can see, it's nesting arrays with the same result multiple times. I need 1 item in the array for each include statement, no repeats, no nested arrays.
I'm having some trouble with these regular expressions, so some guidance would be nice. Thank you for your time.
what about this one
<?php
preg_match_all( "/include(_once)?\s*\(?\s*(\"|')(.*?)\.php(\"|')\s*\)?\s*;?/i", $this->file_contents, $matches );
// for file names
print_r( $matches[3] );
// for full lines
print_r( $matches[0] );
?>
if you want a better and clean way, then the only way is php's token_get_all
<?php
$tokens = token_get_all( $this->file_contents );
$files = array();
$index = 0;
$found = false;
foreach( $tokens as $token ) {
// in php 5.2+ Line numbers are returned in element 2
$token = ( is_string( $token ) ) ? array( -1, $token, 0 ) : $token;
switch( $token[0] ) {
case T_INCLUDE:
case T_INCLUDE_ONCE:
case T_REQUIRE:
case T_REQUIRE_ONCE:
$found = true;
if ( isset( $token[2] ) ) {
$index = $token[2];
}
$files[$index] = null;
break;
case T_COMMENT:
case T_DOC_COMMENT:
case T_WHITESPACE:
break;
default:
if ( $found && $token[1] === ";" ) {
$found = false;
if ( !isset( $token[2] ) ) {
$index++;
}
}
if ( $found ) {
if ( in_array( $token[1], array( "(", ")" ) ) ) {
continue;
}
if ( $found ) {
$files[$index] .= $token[1];
}
}
break;
}
}
// if your php version is above 5.2
// $files index will be line numbers
print_r( $files );
?>
Use get_included_files(), or the built-in tokenizer if the script is not included
I'm searching through a string of another files contents and not the
current file
Then your best bet is the tokenizer. Try this:
$scriptPath = '/full/path/to/your/script.php';
$tokens = token_get_all(file_get_contents($scriptPath));
$matches = array();
$incMode = null;
foreach($tokens as $token){
// ";" should end include stm.
if($incMode && ($token === ';')){
$matches[] = $incMode;
$incMode = array();
}
// keep track of the code if inside include statement
if($incMode){
$incMode[1] .= is_array($token) ? $token[1] : $token;
continue;
}
if(!is_array($token))
continue;
// start of include stm.
if(in_array($token[0], array(T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE)))
$incMode = array(token_name($token[0]), '');
}
print_r($matches); // array(token name, code)
Please read, how works preg_match_all
First item in array - it return all text, which is in regular expression.
Next items in array - that's texts from regular expression (in parenthesises).
You should use $matches[1]

PHP, string of keys access values

Pulling my hair out with this one, please help
I have an array $address
$access=sprintf("['results'][1]['address_components'][1]['long_name']");
I want to be able be able get data from array using the string i.e
$home=$address[$access]
Still having probs
print_r($address['results'][1]['address_components'][1]);
$key=sprintf("[results][1][address_components][1][long_name]");
printf("key=%s\n", $key);
$home = eval($address . $key);
exit;
--- Returns
Array
(
[long_name] => High St
[short_name] => A4151
[types] => Array
(
[0] => route
)
)
key=[results][1][address_components][1][long_name]
PHP Parse error: syntax error, unexpected '[', expecting '(' in /media/www.h.com.dev/postCode/post.php(72) : eval()'d code on line 1
If you don't have any user-provided input in $access, you can safely use eval()...
$home = eval("return \$address{$access}");
Note that when doing an eval(), you are passing code as a string. Therefore, you need to ensure that $address is passed as an actual string, not as a variable (use single quotes ' or escape the dollar sign \$ in the double-quoted string); as for $access, you want that to be parsed as code so simply concatenate it.
If you do have user-provided input, you have to parse $access. You can parse $access using token_get_all().
function array_get_node($array, $nodePath) {
$nodePath = '<?php ' . $nodePath;
$tokens = token_get_all($nodePath);
array_shift($tokens);
$current = $array;
$moved = false;
var_dump($tokens);
$tokCount = count($tokens);
for($i = 0; $i < $tokCount; $i++) {
if($tokens[$i] === '[' && isset($tokens[$i+2])
&& $tokens[$i+2] === ']' && is_array($tokens[$i+1])) {
$node = null;
switch($tokens[$i+1][0]) {
case T_LNUMBER:
$node = (int) $tokens[$i+1][1];
break;
case T_CONSTANT_ENCAPSED_STRING:
$node = preg_replace('#^[\'"](.*)[\'"]$#', '\1', $tokens[$i+1][1]);
break;
case T_STRING:
$node = $tokens[$i+1][1];
break;
default:
return null;
break;
}
if(!isset($current[$node])) return null;
$current = &$current[$node];
$moved = true;
$i+=2;
}
}
if($moved)
return $current;
return null;
}
If you trust $access you could use eval. If not, you have to parse $access...

Preparing a multi-dimensional array for an ExtJS tree control

please dont close this question as repeated one..........
I am new to php.
I am developing one tree grid in extjs.were i need to display field in tree format.so for front end i need to send the encoded data.
I want 2 functions for encoding and decoding a string variable,multidimensional array,variables with a set of delimiters.
For example if i am having an array.........
array(
array( "task" => "rose",
"duration" => 1.25,
"user" => 15
),
array( "task" => "daisy",
"duration" => 0.75,
"user" => 25,
),
array( "task" => "orchid",
"duration" => 1.15,
"user" => 7
),
array( "task" => "sunflower",
"duration" => 1.50,
"user" => 70
)
);
i want to encode all the array fields or single field as..........
array(
array( "task" => "rose",
"duration" => 1.25,
"user" => 15
),
array( "task" => "daisy",
"duration" => 0.75,
"user" => 25$sbaa,
),
array( "task" => "orchid",
"duration" => 1.15,
"user" => 7$!ass,
),
array( "task" => "sunflower",
"duration" => 1.50,
"user" => 70$!abc
)
);
So like this only i need to encode string,variables with delimiters.........
later all the encoded values to be decoded before its taken back to back end.....for this i have to use this plugin..........
encode.class.php...........
<?php
/*-------------------------
Author: Jonathan Pulice
Date: July 26th, 2005
Name: JPEncodeClass v1
Desc: Encoder and decoder using patterns.
-------------------------*/
class Protector
{
var $Pattern = "";
var $PatternFlip = "";
var $ToEncode = "";
var $ToDecode = "";
var $Decoded = "";
var $Encoded = "";
var $Bug = false;
var $DecodePattern = "";
function Debug($on = true)
{
$this->Bug = $on;
}
function Encode()
{
$ar = explode(":", $this->Pattern);
$enc = $this->ToEncode;
if ($this->Bug) echo "<!-- BEGIN ENCODING -->\n";
foreach ($ar as $num => $ltr)
{
switch ($ltr)
{
case "E":
$enc = base64_encode($enc);
break;
case "D":
$enc = base64_decode($enc);
break;
case "R":
$enc = strrev($enc);
break;
case "I":
$enc = $this->InvertCase($enc);
break;
}
if ($this->Bug) echo "<!-- {$ltr}: {$enc} -->\n";
}
if ($this->Bug) echo "<!-------------------->\n\n";
#$this->Encoded = ($enc == $this->Str) ? "<font color='red'>No Encoding/Decoding Pattern Detected!</font>" : $enc;
return $this->Encoded;
}
function Decode()
{
$pattern = ($this->DecodePattern != "") ? $this->DecodePattern : $this->Pattern;
//Reverse the pattern
$this->PatternFlip($pattern);
//make into an array
$ar = explode(":", $this->PatternFlip);
$t = ($this->Encoded == "") ? $this->ToDecode : $this->Encoded;
if ($this->Bug) echo "<!-- BEGIN DECODING -->\n";
foreach ($ar as $num => $ltr)
{
switch ($ltr)
{
case "E":
$t = base64_encode($t);
break;
case "D":
$t = base64_decode($t);
break;
case "R":
$t = strrev($t);
break;
case "I":
$t = $this->InvertCase($t);
break;
}
if ($this->Bug) echo "<!-- {$ltr}: {$t} -->\n";
}
if ($this->Bug) echo "<!-------------------->\n\n";
$this->Decoded = ($t == $this->Encoded) ? "<font color='red'>No Encoding/Decoding Pattern Detected!</font>" : $t;
return $this->Decoded;
}
function MakePattern($len = 10)
{
//possible letters
// E - Base64 Encode
// R - Reverse String
// I - Inverse Case
$poss = array('E','R', 'I');
//generate a string
for ( $i = 0 ; $i < $len ; $i++ )
{
$tmp[] = $poss[ rand(0,2) ];
}
//echo $str. "<br>";
//fix useless pattern section RR II
$str = implode(":", $tmp);
//fix
$str = str_replace( 'R:R:R:R:R:R' , 'R:E:R:E:R:E' , $str );
$str = str_replace( 'R:R:R:R:R' , 'R:E:R:E:R' , $str );
$str = str_replace( 'R:R:R:R' , 'R:E:R:E' , $str );
$str = str_replace( 'R:R:R' , 'R:E:R' , $str );
$str = str_replace( 'R:R' , 'R:E' , $str );
//fix
$str = str_replace( 'I:I:I:I:I:I' , 'I:E:I:E:I:E' , $str );
$str = str_replace( 'I:I:I:I:I' , 'I:E:I:E:I' , $str );
$str = str_replace( 'I:I:I:I' , 'I:E:I:E' , $str );
$str = str_replace( 'I:I:I' , 'I:E:I' , $str );
$str = str_replace( 'I:I' , 'I:E' , $str );
//string is good, set as pattern
$this->Pattern = $str;
return $this->Pattern; //if we need it
}
function PatternFlip($pattern)
{
//reverse the pattern
$str = strrev($pattern);
$ar = explode(":", $str);
foreach ($ar as $num => $ltr)
{
switch ($ltr)
{
case "E":
$tmp[] = "D";
break;
case "D":
$tmp[] = "E";
break;
case "R":
$tmp[] = "R";
break;
case "I":
$tmp[] = "I";
break;
}
}
$rev = implode(":", $tmp);
$this->PatternFlip = $rev;
return $this->PatternFlip;
}
// This is my custom Case Invertor!
// if you would like to use this in a script, please credit it to me, thank you
function InvertCase($str)
{
//Do initial conversion
$new = strtoupper( $str );
//spluit into arrays
$s = str_split( $str );
$n = str_split( $new );
//now we step through each letter, and if its the same as before, we swap it out
for ($i = 0; $i < count($s); $i++)
{
if ( $s[$i] === $n[$i] ) //SWAP THE LETTER
{
//ge the letter
$num = ord( $n[$i] );
//see if the ord is in the alpha ranges ( 65 - 90 | 97 - 122 )
if ( ( $num >= 65 AND $num <= 90 ) OR ( $num >= 97 AND $num <= 122 ) )
{
if ($num < 97 ) { $num = $num + 32; }
else { $num = $num - 32; }
$newchr = chr($num);
$n[$i] = $newchr;
}
}
}
//join the new string back together
$newstr = implode("", $n);
return $newstr;
}
}
?>
............
from this plugin i need to use encode and decode functions for my functions.........
if anyone can help me on this.......it will be very much useful for me.......
Why don't you use json_encode? Just do
$str=json_encode($array);
Then, send the data, and at the other end do
$array=json_decode($str);
Okay, let's break down the problem.
You have an array. Each element in the array is a hash. One (or more) of the values in that hash has to be encoded using that horrible abomination of a library. But the library can't process arrays.
We'll have to process the array ourselves.
<rant>
Before we begin, I'd just like to again express how horribly that "Protector" code is designed. It's written for PHP4 and is effectively spaghetti code wrapped in a class. It mis-uses properties, almost as if the user had some sort of mis-remembering about how Java's instance variables work and somehow thought it would be appropriate or sane to use PHP in the same way. If the author of the code doesn't look back on that revolting chunk of bytes now with utter disdain, something is severely wrong with him.
</rant>
I'm going to base my knowledge of this class on this copy of it, as the one you've provided is formatted even worse than the original.
First, let's create a list of inner array keys that we need to encode.
$keys_to_encode = array( 'user' );
Your example encoding lists only the user key as encodable. If you need to encode others, just add more elements to that array.
Now, let's prepare our "Protector." It seems to want you to either specify a pattern, or use the MakePattern method to have it create one. We're going to manually-specify one because MakePattern can come up with effectively useless combinations.
$stupid = new Protector();
$stupid->Pattern = 'E:I:E:R:D:I:E';
This will base64-encode, flip the case, base64 again, reverse it, un-base64, flip the case, and then re-base64. Be aware that PHP's base64 decoder "correctly" ignores bad padding and unexpected characters, which is the only reason the base64-reverse-unbase64 thing will work. The resulting string will look like gibberish to people that don't know what base64 looks like, and un-base64 to gibberish for people that to know what base64 looks like.
If you need to encode the values in a certain way, you'd do so by just changing the pattern. It's important that you either hard-code the pattern or store it somewhere with the data, because without it, you'll have a hard time doing the decode. (I mean, it can be done by hand, but you don't want to have to do that.) I expect that your boss is going to give you specific instructions on the pattern, given that you don't have an option in using this utter failure at a class.
Now it's time to process our data. Let's pretend that $in contains your original array.
$out = array();
foreach($in as $k => $target) {
foreach($keys_to_encode as $target_key) {
$stupid->ToEncode = $target[ $target_key ];
$target[ $target_key ] = $stupid->Encode();
}
$out[$k] = $target;
}
This loops through the array of hashes, then inside each hash, only the keys we want to encode are encoded. The encoded hash is placed in a new array, $out. This is what you'd pass to your tree widget.
Decoding is just as easy. You've stated that your goal is letting users edit certain data in the tree widget, and you seem to want to protect the underlying keys. This tells me that you probably only need to deal with one of the hashes at a time. Therefore, I'm going to write this decode sample using only one value, $whatever.
$stupidest = new Protector();
$stupidest->Pattern = 'E:I:E:R:D:I:E'; // SAME PATTERN!
$stupidest->ToDecode = $whatever;
$decoded = $stupidest->Decode();
Again, it's critical that you use the same decode pattern here.
As for the actual interaction with the tree widget, you're on your own there. I know only enough about ExtJS to know that it exists and that the GUI creators for it are really fun to play with.

Convert PostgreSQL array to PHP array

I have trouble reading Postgresql arrays in PHP. I have tried explode(), but this breaks arrays containing commas in strings, and str_getcsv() but it's also no good as PostgreSQL doesn't quote the Japanese strings.
Not working:
explode(',', trim($pgArray['key'], '{}'));
str_getcsv( trim($pgArray['key'], '{}') );
Example:
// print_r() on PostgreSQL returned data: Array ( [strings] => {または, "some string without a comma", "a string, with a comma"} )
// Output: Array ( [0] => または [1] => "some string without a comma" [2] => "a string [3] => with a comma" )
explode(',', trim($pgArray['strings'], '{}'));
// Output: Array ( [0] => [1] => some string without a comma [2] => a string, with a comma )
print_r(str_getcsv( trim($pgArray['strings'], '{}') ));
If you have PostgreSQL 9.2 you can do something like this:
SELECT array_to_json(pg_array_result) AS new_name FROM tbl1;
The result will return the array as JSON
Then on the php side issue:
$array = json_decode($returned_field);
You can also convert back. Here are the JSON functions page
As neither of these solutions work with multidimentional arrays, so I offer here my recursive solution that works with arrays of any complexity:
function pg_array_parse($s, $start = 0, &$end = null)
{
if (empty($s) || $s[0] != '{') return null;
$return = array();
$string = false;
$quote='';
$len = strlen($s);
$v = '';
for ($i = $start + 1; $i < $len; $i++) {
$ch = $s[$i];
if (!$string && $ch == '}') {
if ($v !== '' || !empty($return)) {
$return[] = $v;
}
$end = $i;
break;
} elseif (!$string && $ch == '{') {
$v = pg_array_parse($s, $i, $i);
} elseif (!$string && $ch == ','){
$return[] = $v;
$v = '';
} elseif (!$string && ($ch == '"' || $ch == "'")) {
$string = true;
$quote = $ch;
} elseif ($string && $ch == $quote && $s[$i - 1] == "\\") {
$v = substr($v, 0, -1) . $ch;
} elseif ($string && $ch == $quote && $s[$i - 1] != "\\") {
$string = false;
} else {
$v .= $ch;
}
}
return $return;
}
I haven't tested it too much, but looks like it works.
Here you have my tests with results:
var_export(pg_array_parse('{1,2,3,4,5}'));echo "\n";
/*
array (
0 => '1',
1 => '2',
2 => '3',
3 => '4',
4 => '5',
)
*/
var_export(pg_array_parse('{{1,2},{3,4},{5}}'));echo "\n";
/*
array (
0 =>
array (
0 => '1',
1 => '2',
),
1 =>
array (
0 => '3',
1 => '4',
),
2 =>
array (
0 => '5',
),
)
*/
var_export(pg_array_parse('{dfasdf,"qw,,e{q\"we",\'qrer\'}'));echo "\n";
/*
array (
0 => 'dfasdf',
1 => 'qw,,e{q"we',
2 => 'qrer',
)
*/
var_export(pg_array_parse('{,}'));echo "\n";
/*
array (
0 => '',
1 => '',
)
*/
var_export(pg_array_parse('{}'));echo "\n";
/*
array (
)
*/
var_export(pg_array_parse(null));echo "\n";
// NULL
var_export(pg_array_parse(''));echo "\n";
// NULL
P.S.: I know this is a very old post, but I couldn't find any solution for postgresql pre 9.2
Reliable function to parse PostgreSQL (one-dimensional) array literal into PHP array, using regular expressions:
function pg_array_parse($literal)
{
if ($literal == '') return;
preg_match_all('/(?<=^\{|,)(([^,"{]*)|\s*"((?:[^"\\\\]|\\\\(?:.|[0-9]+|x[0-9a-f]+))*)"\s*)(,|(?<!^\{)(?=\}$))/i', $literal, $matches, PREG_SET_ORDER);
$values = [];
foreach ($matches as $match) {
$values[] = $match[3] != '' ? stripcslashes($match[3]) : (strtolower($match[2]) == 'null' ? null : $match[2]);
}
return $values;
}
print_r(pg_array_parse('{blah,blah blah,123,,"blah \\"\\\\ ,{\100\x40\t\daő\ő",NULL}'));
// Array
// (
// [0] => blah
// [1] => blah blah
// [2] => 123
// [3] =>
// [4] => blah "\ ,{## daőő
// [5] =>
// )
var_dump(pg_array_parse('{,}'));
// array(2) {
// [0] =>
// string(0) ""
// [1] =>
// string(0) ""
// }
print_r(pg_array_parse('{}'));
var_dump(pg_array_parse(null));
var_dump(pg_array_parse(''));
// Array
// (
// )
// NULL
// NULL
print_r(pg_array_parse('{または, "some string without a comma", "a string, with a comma"}'));
// Array
// (
// [0] => または
// [1] => some string without a comma
// [2] => a string, with a comma
// )
If you can foresee what kind text data you can expect in this field, you can use array_to_string function. It's available in 9.1
E.g. I exactly know that my array field labes will never have symbol '\n'. So I convert array labes into string using function array_to_string
SELECT
...
array_to_string( labels, chr(10) ) as labes
FROM
...
Now I can split this string using PHP function explode:
$phpLabels = explode( $pgLabes, "\n" );
You can use any sequence of characters to separate elements of array.
SQL:
SELECT
array_to_string( labels, '<--###DELIMITER###-->' ) as labes
PHP:
$phpLabels = explode( '<--###DELIMITER###-->', $pgLabes );
As #Kelt mentioned:
Postgresql arrays look like this: {1,2,3,4}
You can just simply replace first { and last } with [ and ]
respectively and then json_decode that.
But his solution works only for one-dimensional arrays.
Here the solution either for one-dimensional and multidimensional arrays:
$postgresArray = '{{1,2},{3,4}}';
$phpArray = json_decode(str_replace(['{', '}'], ['[', ']'], $postgresArray)); // [[1,2],[3,4]]
To cast back:
$phpArray=[[1,2],[3,4]];
$postgresArray=str_replace(['[', ']'], ['{', '}'], json_encode($phpArray));
Based on the answers in the thread i created two simple php functions that can be of use:
private function pgArray_decode(string $pgArray){
return explode(',', trim($pgArray, '{}'));
}
private function pgArray_encode(array $array){
$jsonArray = json_encode($array, true);
$jsonArray = str_replace('[','{',$jsonArray);
$jsonArray = str_replace(']','}',$jsonArray);
return $jsonArray;
}
I tried the array_to_json answer, but unfortunalety this results in an unknown function error.
Using the dbal query builder on a postgres 9.2 database with something like ->addSelect('array_agg(a.name) as account_name'), I got as result a string like { "name 1", "name 2", "name 3" }
There are only quotes around the array parts if they contain special characters like whitespace or punctuation.
So if there are quotes, I make the string a valid json string and then use the build-in parse json function. Otherwise I use explode.
$data = str_replace(array("\r\n", "\r", "\n"), "", trim($postgresArray,'{}'));
if (strpos($data, '"') === 0) {
$data = '[' . $data . ']';
$result = json_decode($data);
} else {
$result = explode(',', $data);
}
If you have control of the query that's hitting the database, why don't you just use unnest() to get the results as rows instead of Postgres-arrays? From there, you can natively get a PHP-array.
$result = pg_query('SELECT unnest(myArrayColumn) FROM someTable;');
if ( $result === false ) {
throw new Exception("Something went wrong.");
}
$array = pg_fetch_all($result);
This sidesteps the overhead and maintenance-issues you'd incur by trying to convert the array's string-representation yourself.
I can see you are using explode(',', trim($pgArray, '{}'));
But explode is used to Split a string by string (and you are supplying it an array!!). something like ..
$string = "A string, with, commas";
$arr = explode(',', $string);
What are you trying to do with array? if you want to concatenate have a look on implode
OR not sure if it is possible for you to specify the delimiter other than a comma? array_to_string(anyarray, text)
Postgresql arrays look like this: {1,2,3,4}
You can just simply replace first { and last } with [ and ] respectively and then json_decode that.
$x = '{1,2,3,4}';
$y = json_decode('[' . substr($x, 1, -1) . ']'); // [1, 2, 3, 4]
To cast back the other way would be mirror opposite:
$y = [1, 2, 3, 4];
$x = '{' . substr(json_encode($y), 1, -1) . '}';
A simple and fast function for converting deep PostgreSQL array string to JSON string without using pg connection.
function pgToArray(string $subject) : array
{
if ($subject === '{}') {
return array();
}
$matches = null;
// find all elements;
// quoted: {"1{\"23\"},abc"}
// unquoted: {abc,123.5,TRUE,true}
// and empty elements {,,}
preg_match_all( '/\"((?<=\\\\).|[^\"])*\"|[^,{}]+|(?={[,}])|(?=,[,}])/', $subject,$matches,PREG_OFFSET_CAPTURE);
$subject = str_replace(["{","}"],["[","]"],$subject); // converting delimiters to JSON
$matches = array_reverse($matches[0]);
foreach ($matches as $match) {
$item = trim($match[0]);
$replace = null;
if ((strpos($item,"{") !== false) || (strpos($item,"}") !== false)) {
// restoring replaced '{' and '}' inside string
$replace = $match[0];
} elseif (in_array($item,["NULL","TRUE","FALSE"])) {
$replace = strtolower($item);
} elseif ($item === "" || ($item[0] !== '"' && !in_array($item,["null","true","false"]) && !is_float($item))) {
$replace = '"' . $item . '"'; // adding quotes to string element
}
if ($replace) { // concatenate modified element instead of old element
$subject = substr($subject, 0, $match[1]) . $replace . substr($subject, $match[1] + strlen($match[0]));
}
}
return json_decode($subject, true);
}

Categories