PHP将其他数据传递给回调

时间:2017-03-02 14:23:01

标签: php string oop callback

class A {

    protected function b($string, $data) {
        return preg_replace_callback('/pattern/', [$this, 'c'], $string);
    }

    protected function c($match) {
        return $data[$match[1]];
    }

}

我希望能够从功能$data访问c

想做的事情:

  • b中定义一个匿名函数,因为b会被频繁调用,我不想每次都定义一个新函数。
  • $data绑定到A的实例,因为它将保留在内存中,直到实例被销毁。我想传递数据并在替换完成后收集它。

我还有其他选择吗?

6 个答案:

答案 0 :(得分:3)

我强烈建议您使用匿名功能。我知道你不想这样做,但这是做你想做的最简单的方式。

我做了sonse测试。您的问题中的回调以及带有匿名函数的版本。

我做的测试:InstallCert.java

结果(值为秒)

| cycles  | callable         | anonymous function |
|---------|------------------|--------------------|
| 1000    | 0.00196886062622 | 0.00189614295959   |
| 100000  | 0.187341928482   | 0.205796957016     |
| 1000000 | 1.88189315796    | 1.87302780151      |

正如您所看到的,没有太大区别。

无论如何,这是我的解决方案:

class A {
    protected function b($string, $data) {
        return preg_replace_callback('/pattern/', function($match) use ($data) {return $data[$match[1]];}, $string);
    }
}

答案 1 :(得分:1)

我知道你不想要

  

...在b中定义一个匿名函数,因为b会被调用   相当频繁,我不想每次都定义一个新功能   时间。

但我仍然为您提供一个代码版本,它完全符合您......不想要的内容。或者......它不是吗? ; - )

除此之外,我认为还可以有另一种解决方案:递归调用b方法作为回调。我还没有尝试实现它。但我当然会这样做。

我觉得,当然,我可以随心所欲地改变你最小的命名。不要把它当作个人! : - )

我开玩笑。祝你好运。

<?php

class Replacement {

    /**
     * Handler for the preg-replace matches.
     * 
     * @var Closure
     */
    private $handler;

    /**
     * 
     * @param mixed $subject The string or an array with strings to search and replace.
     * @param mixed $pattern The pattern to search for.
     * @param array $replacements Replacements list.
     * @param integer $matchPosition [optional] Match-position for which a replacement takes place.
     * @param integer $recreate [optional] If TRUE, recreate handler.
     * @return mixed
     */
    public function start($subject, $pattern, $replacements, $matchPosition = 1, $recreate = FALSE) {
        $this->createHandler($replacements, $matchPosition, $recreate);
        return preg_replace_callback($pattern, $this->handle(), $subject);
    }

    /**
     * Create a handler for the preg-replace matches.
     * A replacements list is passed.
     * 
     * Creation takes place only once in the lifecycle
     * of the instance of this class.
     * 
     * @param array $replacements Replacements list.
     * @param integer $matchPosition [optional] Match-position for which a replacement takes place.
     * @param integer $recreate [optional] If TRUE, recreate handler.
     * @return $this
     */
    public function createHandler($replacements, $matchPosition = 1, $recreate = FALSE) {
        if (!isset($this->handler) || $recreate) {
            $this->handler = function($matches) use ($replacements, $matchPosition) {
                return $replacements[$matches[$matchPosition]];
            };
        }
        return $this;
    }

    /**
     * Handle preg-replace matches.
     * 
     * @return Closure
     */
    public function handle() {
        return $this->handler;
    }

}

// Subject string.
$subject = 'Next april fools day is 04/01/2018 and the last Christmas was 12/24/2017.';

// Pattern to search for: mm/dd or yyyy.
$pattern = "|(\d{2}/\d{2}/)(\d{4})|";

// Create test object
$replacement = new Replacement();


// Display results.
echo '<pre><b>BEFORE:</b> "' . print_r($subject, true) . '"</pre>' . PHP_EOL;

//*******************************************
// TEST 1: Closure creation
//*******************************************

// Replacement data.
$replacements = [
    '04/01/' => 'the same as each year',
    '12/24/' => 'in december',
];

// Start replacement process.
$output = $replacement->start($subject, $pattern, $replacements);

// Display results.
echo '<pre><b>AFTER (test 1):</b> "' . print_r($output, true) . '"</pre>' . PHP_EOL;

//*******************************************
// TEST 2: Closure RE-creation
//*******************************************

$replacements2 = [
    '04/01/' => 'test1',
    '12/24/' => 'test2',
];

// Start replacement process.
$output2 = $replacement->start($subject, $pattern, $replacements2, 1, TRUE);

// Display results.
echo '<pre><b>AFTER (test 2):</b> "' . print_r($output2, true) . '"</pre>' . PHP_EOL;

编辑1:

哦,只是忘了:我的示例中的主题字符串和模式来自PHP文档。我稍微更改了主题字符串,以便更好地理解它。无论如何,PHP人员可以比我更好地解释模式(我不是正则表达式专家):

对于递归解决方案,请参阅

编辑2(20.06。&#39; 17):

我更改了代码:现在通过读取$recreate bool值按需重新创建闭包。您可以通过检查数组值自初始表单后是否已更改来决定是否在开始替换之前进行重新创建。

答案 2 :(得分:0)

您可以在类上使用静态字段以避免使用内存或使用非静态字段,并在返回之前将其清除。像这样:

class A { 

    protected static $_data;

    protected function b($string, $data) {
        self::$_data = $data;
        $return = preg_replace_callback('/pattern/', [self, 'c'], $string);
        unset(self::$_data);
        return $return;
    }

    protected function c($match) {
        return array_shift(self::$_data);
    }

}

答案 3 :(得分:0)

我会创建一个具有$data属性的单独类,因此您可以在对象上设置它:

interface IReplacer
{
    public function handler(array $matches);
}

class DataReplacer implements IReplacer
{
    private $data;

    public function __construct(array $data = null)
    {
        if (!is_null($data)) {
            $this->data = $data;
        }
    }

    public function setData(array $data)
    {
        $this->data = $data;

        return $this;
    }

    public function clearData()
    {
        $this->data = null;

        return $this;
    }

    public function handler(array $matches)
    {
        if (!isset($this->data[$matches[0]])) {
            // Do what ever is more plausable here.
            // Throw an exception or provide default value.
            return 'default value';
        }

        return $this->data[$matches[0]];
    }
}

然后,您将此类的对象作为依赖项注入A类:

class A
{
    private $replacer;

    public function __construct(IReplacer $replacer)
    {
        $this->replacer = $replacer;
    }

    public function b($string, array $data)
    {
        $result = preg_replace_callback(
            '/pattern/',
            [$this->replacer->setData($data), 'handler'],
            $string
        );

        // Clear data so it is not hanging around in the memory.
        $this->replacer->clearData();

        return $result;
    }
}

有了这个,你就可以使用它(为了简单测试,我公开了方法b):

$a = new A(new DataReplacer);

$string = 'This is example with pattern';

var_dump($a->b($string, ['pattern' => 'foo']));
var_dump($a->b($string, ['pattern' => 'bar']));
var_dump($a->b($string, []));

这是working demo

答案 4 :(得分:0)

您可以考虑使用两种策略来访问数据变量:

  1. 在正则表达式中隐藏其内容。例如:

    $string = "Goodbye cruel world!";
    $replacements = [ "Goodbye" => "Hello", "cruel" => "nice" ];
    //Build your regex with named capturing groups e.g. 
    $result = preg_replace_callback("/(?P<Hello>Goodbye)|(?P<nice>cruel)/" , function ($match) {
            $filtered = array_filter($match,function ($v,$k) { return $v != null && !is_numeric($k); }, ARRAY_FILTER_USE_BOTH);
            return array_keys($filtered)[0];
    }, $string);
    
  2. 如果所有其他方法都失败了,那么总会有回溯:

    protected function b($string, $data) {
        return preg_replace_callback('/pattern/', [$this, 'c'], $string);
    }
    
    protected function c($match) {
        //Second argument of the function that is 3rd on the call stack
        // 0 is this function 1 is the preg_replace_callback function so 2 is b
        $data = debug_backtrace()[2]["args"][2]; 
    return $data[$match[1]];
    }
    
  3. 注意:捕获组可以拥有的名称有非常严格的限制,因此它可能不是多次可行的选择。但是我不认为回溯会提供比绑定函数更好的改进。

    我建议你这么做:

    protected function b($string, $data) {
        return preg_replace_callback('/pattern/', function ($match) use ($data) {
           return $data[$match[1]];
        }, $string);
    }
    

    只是嵌入函数。 PHP优化将阻止$data数组被复制,除非它实际被修改。

答案 5 :(得分:-2)

试试这个

class A {

    private $data;

    protected function b($string, $data) {
        $this->data = $data;
        return preg_replace_callback('/pattern/', [$this, 'c'], $string);
    }

    protected function c($match) {
        return $this->data[$match[1]];
    }

}