在PHP中替换字符串中的命名“参数”

时间:2012-10-05 11:54:15

标签: php string parameters preg-replace laravel

解决:

感谢@SJFrK,我的问题已经解决了。以下课程允许我做我需要的。问题在课后。

class Redirects
{
    /*
    |--------------------------------------------------------------------------
    | Placeholder Types
    |--------------------------------------------------------------------------
    |
    | An array of placeholder types thatalow specific characters in a source
    | route or URL.
    |
    */

    private static $placeholders = array(
        ':all' => '.*',
        ':alpha' => '[a-z]+',
        ':alphanum' => '[a-z0-9]+',
        ':any' => '[a-z0-9\.\-_%=]+',
        ':num' => '[0-9]+',
        ':segment' => '[a-z0-9\-\_]+',
        ':segments' => '[a-z0-9\-\_\/]+',
    );

    /*
    |--------------------------------------------------------------------------
    | Computed Replacements
    |--------------------------------------------------------------------------
    |
    | An array that contains converted source placeholders.
    |
    */

    private static $computed_replacements = array();

    /*
    |--------------------------------------------------------------------------
    | Computed Replacements
    |--------------------------------------------------------------------------
    |
    | Class-scoped string that contains a replacement route or URL from
    | site.redirects.
    |
    */

    private static $destination;

    /**
     * Check for a redirect. If it exists, then redirect to it's computed replacement.
     *
     * @return Response
     */
    public static function redirect()
    {
        // Only do this if redirects have been defined.
        if (Config::has('site.redirects') && count(Config::get('site.redirects') > 0))
        {
            $route = URI::current();

            // Get the available placeholders from static::$placeholders and
            // convert them into applicable options for the pattern.
            $available_placeholders = '';
            foreach (static::$placeholders as $placeholder => $expression)
                $available_placeholders .= ltrim("$placeholder|", ':');
            $available_placeholders = rtrim($available_placeholders, '|');

            // Define the pattern.
            $pattern = '/\((\w+):('.$available_placeholders.')\)/';

            // Get the redirects.
            $redirects = Config::get('site.redirects');

            // For each redirect, convert all the place holders and resulting
            // values, and check for a match. If one exists, then redirect to it.
            foreach ($redirects as $from => $to) {
                static::$computed_replacements = array();
                static::$destination = $to;
                $from = rtrim($from, '/');
                $to = rtrim($to, '/');

                // Convert the placeholders in $from (if any)
                $converted_placeholders = preg_replace_callback($pattern, function($captures) {
                    static::$computed_replacements[] = $captures[1];
                    return '('.static::$placeholders[":{$captures[2]}"].')';
                }, $from);

                // Get the replacements
                $converted_replacements = preg_replace_callback("#^{$converted_placeholders}$#i", function($captures) {
                    $output = static::$destination;
                    for ($c = 1, $n = count($captures); $c < $n; ++$c)
                    {
                        $value = array_shift(static::$computed_replacements);
                        $output = str_replace("<$value>", $captures[$c], $output);
                    }
                    return $output;
                }, $route);

                // If the current route matches the converted expression, then redirect.
                if (preg_match("!^{$converted_placeholders}$!i", $route))
                    return Redirect::to($converted_replacements, 301)
                        ->with('moved-from', $route)
                        ->with('moved-from-rule', "'$from': '".static::$destination."'");
            }
        }
        else return;
    }
}

引言

我正在编写Laravel(FTW)中的静态网站捆绑包,并且遇到了一些我似乎无法解决的问题。

该应用程序包含一个配置文件(config/site.php),其中包含一系列重定向查找。我们的想法是检查每个查找URI中的匹配项,然后重定向到替换URI(当然,使用301重定向) - 对于将静态HTML站点移动到我的包中的人来说非常有用。

数组的格式为:

'redirects' => array(
    '<source>' => '<destination>'
)

<source>中,用户可以包含以下内容:

  1. 正则表达式
  2. Laravel风格的占位符:(:any)(:all)(:num)(:alpha) - 我添加了最后一个。
  3. 例如,用户可以使用以下重定向(请注意,我已选择使用<destination>的角度parenthises,因为它更美观 - 它被转换回其正则表达式等效[见类下文]):

    '(:all).html' => '<1>'
    

    这会将以.html结尾的任何页面指向不包含它的路由。示例:http://example.com/about.html会重定向到http://example.com/about

    问题

    我希望能够“命名”每个参数以便于阅读(和令人愉快的编码)。我想要做的是在<source>中为每个占位符命名(当然这是可选的),并定义一个占位符类型。例如:

    '(name:all).html' => '<name>'
    

    现在,考虑到占位符可以命名,它们自然可以在<destination>内的任何顺序,例如(注意目标URI中的顺序更改):

    'Products/(name:any)-(category:any)-(id:num).html' => 'products/<category>/<id>/<name>/overview'
    

    使用常规占位符语法,我可以将其解释为:

    'Products/(:any)-(:any)-(:num).html' => 'products/<2>/<3>/<1>'
    

    无论如何这都是后备 - 我需要找出如何使用preg_replace将名称替换为相应的捕获组。但似乎不可能使用命名参数/捕获:

    已命名的参数

    一个简单的方法是在preg_replace中使用命名参数,但是(据我所知)PHP不支持它。

    使用这种方法可以让我使用相同类型的替换工具来完成任务 - 所以没有它可用有点令人失望。

    尽管如此,我很乐意回复一些更复杂的事情(无论如何这都是好事)。事情是,我不知道如何 - 并且解决方案不是来找我。我查看过Silex RouteCompiler类,但完全不了解它。我觉得,考虑到Laravel的主干基于Symfony组件集(很像Silex),可能有更好的方法来实现我的需要。

    有没有人有同样的要求,也许找到了解决方案?这里的任何帮助都会非常棒!提前谢谢。

    CURRENT CLASS

    这是处理重定向的类的源代码。我只需在Redirects::handle()文件中拨打routes.php

    class Redirects
    {
        private static $placeholders = array(
            ':any' => '[a-zA-Z0-9\.\-_%=]+',
            ':num' => '[0-9]+',
            ':alpha' => '[a-z]+', //added
            ':all' => '.*',
        );
    
        private static $replacement_identifiers = array(
            '(\<([0-9]+)\>)' => '$$1',
        );
    
        /**
         * Create the applicable regex placeholders
         *
         * @param  string  &$from
         * @param  string  &$to
         * @return void
         */
        protected static function placeholders(string &$from, string &$to)
        {
            // Replace the <source> with a match expression
            foreach (static::$placeholders as $placeholder => $expression)
                $from = str_replace("({$placeholder})", "({$expression})", $from);
    
            // Replace the <destination> with a replacement expression
            foreach (static::$replacement_identifiers as $identifier => $expression)
                if (preg_match($identifier, $to))
                    $to = preg_replace($identifier, $expression, $to);
        }
    
        /**
         * Return the response of any redirects, or void if none
         *
         * @return Response|void
         */
        public static function handle()
        {
            $route = URI::current();
            if (Config::has('site.redirects'))
            {
                $redirects = Config::get('site.redirects');
                foreach ($redirects as $from => $to) {
                    $from = rtrim($from, '/');
                    static::placeholders($from, $to);
                    if (preg_match("!^{$from}$!i", $route))
                    {
                        if (strpos($to, '$') !== false and strpos($from, '(') !== false)
                            $to = preg_replace("!^{$from}$!i", $to, $route);
                        return Redirect::to($to, 301)->with('Moved-From', $route);
                    }
                    else return;
                }
            }
            else return;
        }
    }
    

1 个答案:

答案 0 :(得分:1)

这是一个测试用例,它可以满足您的需求,也许您可​​以将它合并到您的课程中?:

<?php
class Redirects {
private static $placeholders = array(
    ':any' => '[a-zA-Z0-9\.\-_%=]+',
    ':num' => '[0-9]+',
    ':alpha' => '[a-z]+', //added
    ':all' => '.*',
);

private static $tmp;
private static $tmpValue;

public static function handle() {
    $case = array('Products/(name:any)-(category:any)-(id:num).html' => 'products/<category>/<id>/<name>/overview');
    $test = 'Products/productName-categoryName-123.html';       // products/categoryName/123/productName/overview

    $pattern = '/\(\s*?(\w*?)\s*?:\s*?(\w*?)\s*?\)/';

    foreach ($case as $k => $v) {
        self::$tmp = array();
        self::$tmpValue = $v;

        $step1 = preg_replace_callback($pattern, array(self, 'replace_step1'), $k);
        $step2 = preg_replace_callback('#' . $step1 . '#', array(self, 'replace_step2'), $test);

        print 'case: ' . $k . '<br>';
        print 'step1: ' . $step1 . '<br>';
        print 'step2: ' . $step2 . '<br>';
    }
}

private static function replace_step1($matches) {
    self::$tmp[] = $matches[1];

    return '(' . self::$placeholders[':' . $matches[2]] . ')';
}

private static function replace_step2($matches) {
    $str = self::$tmpValue;

    for ($i = 1, $n = count($matches); $i < $n; ++$i) {
        $value = array_shift(self::$tmp);

        $str = str_replace('<' . $value . '>', $matches[$i], $str);
    }

    return $str;
}
}

Redirects::handle();

它首先用真正的PREG占位符替换您的命名占位符,并将它们存储在数组$tmp中。然后检查测试字符串$test是否与该模式$step1匹配,并根据$tmp数组替换它们。