PHP parse_str() 函数允许传递 shorthand 数组

PHP parse_str() function allowing passing shorthand array

我们接受从模板到标记引擎的字符串,这允许以“简单”形式传递配置。

引擎使用 parse_str() 函数的改编版本通过 PHP 解析字符串 - 因此我们可以解析以下字符串的任意组合:

config=posts_per_page:"5",default:"No questions yet -- once created they will appear here."&markup->template="{{ questions }}"

给出:

Array(
[config] => Array
    (
        [posts_per_page] => 5
        [default] => No questions yet -- once created they will appear here.
    )

[markup] => Array
    (
        [template] => {{ questions }}
    )
)

或:

config->default=all:"<p class='ml-3'>No members here yet...</p>"

获取:

Array 
[config] => Array
    (
        [default] => Array
            (
                [all] => <p class='ml-3'>No members here yet...</p>
            )

    )
)

另一个:

config=>handle:"medium"

Returns:

Array (
[config] => Array
    (
        [>handle] => medium
    )
)

字符串可以用空格(和多行空格)传递,字符串参数应该在“双引号”之间传递以保持自然间距 - 我们 运行 以下 preg_replace 在字符串上在传递给 parse_str 方法之前:

// strip white spaces from data that is not passed inside double quotes ( "data" ) ##
$string = preg_replace( '~"[^"]*"(*SKIP)(*F)|\s+~', "", $string );

到目前为止,一切顺利 - 直到我们尝试在字符串值中传递一个“定界符”,然后它才会按字面意思处理 - 例如以下字符串 returns 一个损坏的数组:

config=posts_per_page:"5",default:"No questions yet -- once created, they will appear here."&markup->template="{{ questions }}"

Returns以下数组:

Array (
[config] => Array
    (
        [posts_per_page] => 5
        [default] => No questions yet -- once created
        [ they will appear here."] => 
    )

[markup] => Array
    (
        [template] => {{ questions }}
    )
)

“,”按字面意思处理,字符串被分成额外的数组部分。

一个简单的解决方案是创建与字符串值发生冲突的可能性较低的分隔符和运算符 - 例如将“,”更改为“@@@” - 但使用的标记的一个重要部分是它很容易写入和读取 - 它的预期用例是前端开发人员将简单参数传递给模板解析器 - 这是我们试图避免 JSON 的原因之一 - 这当然是一个很好的选择传递数据,但很难读写 - 当然,这种说法是主观的,可以发表意见:)

这里是parse_str方法:

public static function parse_str( $string = null ) {

    // h::log($string);

    // delimiters ##
    $operator_assign = '=';
    $operator_array = '->';
    $delimiter_key = ':';
    $delimiter_and_property = ',';
    $delimiter_and_key = '&';

    // check for "=" delimiter ##
    if( false === strpos( $string, $operator_assign ) ){

        h::log( 'e:>Passed string format does not include asssignment operator "'.$operator_assign.'" -- '.$string );

        return false;

    }

    # result array
    $array = [];
  
    # split on outer delimiter
    $pairs = explode( $delimiter_and_key, $string );
  
    # loop through each pair
    foreach ( $pairs as $i ) {

        # split into name and value
        list( $key, $value ) = explode( $operator_assign, $i, 2 );

        // what about array values ##
        // example -- sm:medium, lg:large
        if( false !== strpos( $value, $delimiter_key ) ){

            // temp array ##
            $value_array = [];  

            // split value into an array at "," ##
            $value_pairs = explode( $delimiter_and_property, $value );

            // h::log( $value_pairs );

            # loop through each pair
            foreach ( $value_pairs as $v_pair ) {

                // h::log( $v_pair ); // 'sm:medium'

                # split into name and value
                list( $value_key, $value_value ) = explode( $delimiter_key, $v_pair, 2 );

                $value_array[ $value_key ] = $value_value;

            }

            // check if we have an array ##
            if ( is_array( $value_array ) ){

                $value = $value_array;

            }

        }
     
        // $key might be in part__part format, so check ##
        if( false !== strpos( $key, $operator_array ) ){

            // explode, max 2 parts ##
            $md_key = explode( $operator_array, $key, 2 );

            # if name already exists
            if( isset( $array[ $md_key[0] ][ $md_key[1] ] ) ) {

                # stick multiple values into an array
                if( is_array( $array[ $md_key[0] ][ $md_key[1] ] ) ) {
                
                    $array[ $md_key[0] ][ $md_key[1] ][] = $value;
                
                } else {
                
                    $array[ $md_key[0] ][ $md_key[1] ] = array( $array[ $md_key[0] ][ $md_key[1] ], $value );
                
                }

            # otherwise, simply stick it in a scalar
            } else {

                $array[ $md_key[0] ][ $md_key[1] ] = $value;

            }

        } else {

            # if name already exists
            if( isset($array[$key]) ) {

                # stick multiple values into an array
                if( is_array($array[$key]) ) {
                
                    $array[$key][] = $value;
                
                } else {
                
                    $array[$key] = array($array[$key], $value);
                
                }

            # otherwise, simply stick it in a scalar
            } else {

                $array[$key] = $value;

            }
          
        }
    }

    // h::log( $array );
  
    # return result array
    return $array;

  }

我将尝试跳过在“双引号”之间拆分字符串 - 可能通过另一个正则表达式,但也许还有其他潜在的陷阱等待可能不会使这种方法长期可行 - 很高兴接受任何帮助!

一种解决方案是更改以下内容:

来自:

$value_pairs = explode( $delimiter_and_property, $value );

至:

$value_pairs = self::quoted_explode( $value, $delimiter_and_property, '"' );

调用在另一个 SO 答案中找到的新方法(链接在评论块中):

/**
 * Regex Escape values 
*/
public static function regex_escape( $subject ) {

    return str_replace( array( '\', '^', '-', ']' ), array( '\\', '\^', '\-', '\]' ), $subject );

}

/**
 * Explode string, while respecting delimiters
 * 
 * @link 
*/
public static function quoted_explode( $subject, $delimiter = ',', $quotes = '\"' )
{
    $clauses[] = '[^'.self::regex_escape( $delimiter.$quotes ).']';

    foreach( str_split( $quotes) as $quote ) {

        $quote = self::regex_escape( $quote );
        $clauses[] = "[$quote][^$quote]*[$quote]";

    }

    $regex = '(?:'.implode('|', $clauses).')+';
    
    preg_match_all( '/'.str_replace('/', '\/', $regex).'/', $subject, $matches );

    return $matches[0];

}