在PHP中将字符串解析为数组

5

我刚接触PHP,但找不到合适的答案。

$whatever = "array('Test','Blah')";
echo $parsed[2]; //This will be "Blah"

我想创建一个名为$parsed的变量,其中包含$whatever的值,但是它是一个有效的数组,而不是一个字符串。
我知道我可以通过去掉周围的引号来创建该数组,如下所示:
$whatever = array('Test','Blah');

在我正在处理的实际代码中,这并不可行。此外,在我的实际代码中,数组是多维的,因此涉及字符替换的一些操作可能是不切实际的,但如果这是最佳选项,我不排除它。
总之,最佳方法是如何在PHP中将字符串解析为数组?

2
那个格式是固定的吗?如果可以使用其他格式,你应该看一下JSON。你的JSON数据将如下所示:['Test','Blah'] - vstm
2
我想知道你是如何得到这个带有array的字符串的。依赖于var_export作为一个天真的序列化器难道不是一个聪明的想法吗?它不能被覆盖,比如使用更合理的选择(serializejson_encode)吗? - raina77ow
6个回答

13

使用eval函数:http://php.net/manual/en/function.eval.php

$whatever = "array('Test','Blah')";
$parsed = eval("return " . $whatever . ";");
echo $parsed[1]; //This will be "Blah"

要小心检查$whatever变量的内容,因为任何PHP代码都可以被执行。


@Florent 如果 OP 知道只有数组会被解析,那么使用 eval 就可以了,不是吗? - galymzhan
这个完美地运行了,谢谢!是的,只有类似数组的字符串会通过这里。 - UserIsCorrupt
可以,那样可以实现,但使用eval()不安全,如果可能的话请使用正则表达式,参见我的答案。 - GajendraSinghParihar
我认为正则表达式不太适合这种情况,因为它很快就会变得非常复杂:array('test',"test2",array('test3','test4\'quoted\'')) - SirDarius

5
更加安全(无需使用eval)的方法是:
$whatever = "array('Test','Blah')";

$search = array("array", "(", ")", "'");
$parsed = explode(',',str_replace($search, '', $whatever));

echo $parsed[1];

这将删除所有不必要的文本,然后使用逗号分隔符拆分字符串。

2
这个无法处理递归数组。 - SirDarius

1

这是我一直在开发的东西。目前还没有单元测试,但似乎运行得相当不错。我不支持在数组结构内使用函数、对象实例化、条件语句等。因为对于我的用例来说,我不需要支持这些。但是您可以随意添加所需的功能。

/**
 * A class used convert string representations or php arrays to an array without using eval()
 */
class ArrayTokenScanner
{
    /** @var array  */
    protected $arrayKeys = [];

    /**
     * @param string $string   e.g. array('foo' => 123, 'bar' => [0 => 123, 1 => 12345])
     *
     * @return array
     */
    public function scan($string)
    {
        // Remove whitespace and semi colons
        $sanitized = trim($string, " \t\n\r\0\x0B;");
        if(preg_match('/^(\[|array\().*(\]|\))$/', $sanitized)) {
            if($tokens = $this->tokenize("<?php {$sanitized}")) {
                $this->initialize($tokens);
                return $this->parse($tokens);
            }
        }

        // Given array format is invalid
        throw new InvalidArgumentException("Invalid array format.");
    }

    /**
     * @param array $tokens
     */
    protected function initialize(array $tokens)
    {
        $this->arrayKeys = [];
        while($current = current($tokens)) {
            $next = next($tokens);
            if($next[0] === T_DOUBLE_ARROW) {
                $this->arrayKeys[] = $current[1];
            }
        }
    }

    /**
     * @param array $tokens
     * @return array
     */
    protected function parse(array &$tokens)
    {
        $array = [];
        $token = current($tokens);
        if(in_array($token[0], [T_ARRAY, T_BRACKET_OPEN])) {

            // Is array!
            $assoc = false;
            $index = 0;
            $discriminator = ($token[0] === T_ARRAY) ? T_ARRAY_CLOSE : T_BRACKET_CLOSE;
            while($token = $this->until($tokens, $discriminator)) {


                // Skip arrow ( => )
                if(in_array($token[0], [T_DOUBLE_ARROW])) {
                    continue;
                }

                // Reset associative array key
                if($token[0] === T_COMMA_SEPARATOR) {
                    $assoc = false;
                    continue;
                }

                // Look for array keys
                $next = next($tokens);
                prev($tokens);
                if($next[0] === T_DOUBLE_ARROW) {
                    // Is assoc key
                    $assoc = $token[1];
                    if(preg_match('/^-?(0|[1-9][0-9]*)$/', $assoc)) {
                        $index = $assoc = (int) $assoc;
                    }
                    continue;
                }

                // Parse array contents recursively
                if(in_array($token[0], [T_ARRAY, T_BRACKET_OPEN])) {
                    $array[($assoc !== false) ? $assoc : $this->createKey($index)] = $this->parse($tokens);
                    continue;
                }

                // Parse atomic string
                if(in_array($token[0], [T_STRING, T_NUM_STRING, T_CONSTANT_ENCAPSED_STRING])) {
                    $array[($assoc !== false) ? $assoc : $this->createKey($index)] = $this->parseAtomic($token[1]);
                }

                // Parse atomic number
                if(in_array($token[0], [T_LNUMBER, T_DNUMBER])) {

                    // Check if number is negative
                    $prev = prev($tokens);
                    $value = $token[1];
                    if($prev[0] === T_MINUS) {
                        $value = "-{$value}";
                    }
                    next($tokens);

                    $array[($assoc !== false) ? $assoc : $this->createKey($index)] = $this->parseAtomic($value);
                }

                // Increment index unless a associative key is used. In this case we want too reuse the current value.
                if(!is_string($assoc)) {
                    $index++;
                }
            }

            return $array;
        }
    }

    /**
     * @param array $tokens
     * @param int|string $discriminator
     *
     * @return array|false
     */
    protected function until(array &$tokens, $discriminator)
    {
        $next = next($tokens);
        if($next === false or $next[0] === $discriminator) {
            return false;
        }

        return $next;
    }

    protected function createKey(&$index)
    {
        do {
            if(!in_array($index, $this->arrayKeys, true)) {
                return $index;
            }
        } while(++$index);
    }

    /**
     * @param $string
     * @return array|false
     */
    protected function tokenize($string)
    {
        $tokens = token_get_all($string);
        if(is_array($tokens)) {

            // Filter tokens
            $tokens = array_values(array_filter($tokens, [$this, 'accept']));

            // Normalize token format, make syntax characters look like tokens for consistent parsing
            return $this->normalize($tokens);

        }

        return false;
    }

    /**
     * Method used to accept or deny tokens so that we only have to deal with the allowed tokens
     *
     * @param array|string $value    A token or syntax character
     * @return bool
     */
    protected function accept($value)
    {
        if(is_string($value)) {
            // Allowed syntax characters: comma's and brackets.
            return in_array($value, [',', '[', ']', ')', '-']);
        }
        if(!in_array($value[0], [T_ARRAY, T_CONSTANT_ENCAPSED_STRING, T_DOUBLE_ARROW, T_STRING, T_NUM_STRING, T_LNUMBER, T_DNUMBER])) {
            // Token did not match requirement. The token is not listed in the collection above.
            return false;
        }
        // Token is accepted.
        return true;
    }

    /**
     * Normalize tokens so that each allowed syntax character looks like a token for consistent parsing.
     *
     * @param array $tokens
     *
     * @return array
     */
    protected function normalize(array $tokens)
    {
        // Define some constants for consistency. These characters are not "real" tokens.
        defined('T_MINUS')           ?: define('T_MINUS',           '-');
        defined('T_BRACKET_OPEN')    ?: define('T_BRACKET_OPEN',    '[');
        defined('T_BRACKET_CLOSE')   ?: define('T_BRACKET_CLOSE',   ']');
        defined('T_COMMA_SEPARATOR') ?: define('T_COMMA_SEPARATOR', ',');
        defined('T_ARRAY_CLOSE')     ?: define('T_ARRAY_CLOSE',     ')');

        // Normalize the token array
        return array_map( function($token) {

            // If the token is a syntax character ($token[0] will be string) than use the token (= $token[0]) as value (= $token[1]) as well.
            return [
                0 => $token[0],
                1 => (is_string($token[0])) ? $token[0] : $token[1]
            ];

        }, $tokens);
    }

    /**
     * @param $value
     *
     * @return mixed
     */
    protected function parseAtomic($value)
    {
        // If the parameter type is a string than it will be enclosed with quotes
        if(preg_match('/^["\'].*["\']$/', $value)) {
            // is (already) a string
            return $value;
        }

        // Parse integer
        if(preg_match('/^-?(0|[1-9][0-9]*)$/', $value)) {
            return (int) $value;
        }

        // Parse other sorts of numeric values (floats, scientific notation etc)
        if(is_numeric($value)) {
            return  (float) $value;
        }

        // Parse bool
        if(in_array(strtolower($value), ['true', 'false'])) {
            return ($value == 'true') ? true : false;
        }

        // Parse null
        if(strtolower($value) === 'null') {
            return null;
        }

        // Use string for any remaining values.
        // For example, bitsets are not supported. 0x2,1x2 etc
        return $value;
    }
}

使用示例:

$tokenScanner = new ArrayTokenScanner();
$array = $tokenScanner->scan('[array("foo" => -123, "foobie" => "5x2", "bar" => \'456\', 111 => 12, "bar", ["null" => null], "bool" => false), 123 => E_ERROR];');
$arrayExport = preg_replace('/[\s\t]+/', ' ', var_export($array, true));
echo stripslashes($arrayExport) . PHP_EOL;
$array2 = $tokenScanner->scan('[array("foo" => 123, "foobie" => "5x2", "bar" => \'456\', 111 => 12, "bar", ["null" => null], "bool" => false), 123 => E_ERROR];');
$arrayExport = preg_replace('/[\s\t]+/', ' ', var_export($array, true));
echo stripslashes($arrayExport);

1

eval()是有害的。它性能差,而且不安全

因此,如果您的数组不太复杂,请使用正则表达式

$subject = "array('Test','Blah','Blah2','Blah3')";
$pattern = "/'(.*?)'/";
preg_match_all($pattern, $subject, $matches);
echo "<pre>";print_r($matches[1]);

它不能用于递归数组,但是您可以付出一些努力来实现。 - GajendraSinghParihar
请小心检查两种不同的引号字符 ' 和 "。 - SirDarius
另外,在字符串中要小心转义引号 :) - SirDarius

0

你可以写

$strCreateArray = "$whatever = " . "array('Test','Blah')" . ";";

eval( $strCreateArray );

0

如果无法使用 eval。根据数组是否始终具有相同的格式(从不多维),使用替换函数来删除array(以及所有引号。然后,在逗号上做字符串分割。

难道您不能为这些数组取消此格式吗?对我来说没有任何意义将数组存储在这样的方式中。序列化或json是更好的选择。


我承认我更喜欢使用eval而不是自己编写PHP数据结构解析器。 - raina77ow
@raina77ow,我希望每个人都能在eval可用且不违反公司政策的情况下使用它。 - René

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接