an open source technology and e-commerce related site

Fix for deprecated preg_replace error in Zend Framework under PHP 5.5

Written by Matthew Cooper on September 09, 2013

The following error is thrown in Zend Framework 1 when calling Zend_Filter_Word_SeparatorToCamelCase::filter() under PHP 5.5:

E_DEPRECATED preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead

This is caused by the the Zend_Filter_PregReplace class passing unvalidated parameters to the preg_replace function in the filter method.

  1. return preg_replace($this->_matchPattern, $this->_replacement, $value);
  2.  

Here we see that the $this->_mathPattern property being passed to preg_replace is actually an array of regular expressions:

  1. (
  2. [0] => #(\\-)(\\p{L}{1})#e
  3. [1] => #(^\\p{Ll}{1})#e
  4. )
  5.  

Notice the e's at the end of each regex which indicate that whatever we pass into the replace parameter (the second parameter of the preg_replace function) gets interpreted and executed as PHP code. That means you could pass it something like this:

  1. $var = preg_replace('/[1-9]*/e', 'strtolower($_GET["some_http_var_from_form"])', '' );
  2.  

Whereby if $_GET["some_http_var_from_form"] is not filtered properly somebody could send any number of arbitrarily dangerous commands like unlink, phpinfo, exec and they would be executed as is. Since this is a rather obvious security issue it's strange that the use of the /e modifier wasn't deprecated a long time ago along with the ereg* group of functions.

Since the issue probably will not be addressed in Zend Framework 1 because most of the development is going into Zend Framework 2 we need a simple workaround.

One easy fix would be to suppress the error with an ampersand like so:

  1. return @preg_replace($this->_matchPattern, $this->_replacement, $value);
  2.  

That is bad practice however, and getting back to the problem itself the issue really doesn't have anything to do with the Zend_Filter_PregReplace class but rather the Zend_Filter_Word_SeparatorToCamelCase class which I was using to manually convert strings like "blah-blah-blah" to a camel case variant. Taking a look at that class we see exactly where the problem originates.

  1. class Zend_Filter_Word_SeparatorToCamelCase extends Zend_Filter_Word_Separator_Abstract
  2. {
  3. //......
  4. public function filter($value)
  5. {
  6. // a unicode safe way of converting characters to \x00\x00 notation
  7. $pregQuotedSeparator = preg_quote($this->_separator, '#');
  8.  
  9. if (self::isUnicodeSupportEnabled()) {
  10. parent::setMatchPattern(
  11. // note the e modifier at the end
  12. '#('.$pregQuotedSeparator.')(\p{L}{1})#e','#(^\p{Ll}{1})#e'
  13. )
  14. );
  15. parent::setReplacement(array("strtoupper('\\2')","strtoupper('\\1')"));
  16. } else {
  17. parent::setMatchPattern(
  18. '#('.$pregQuotedSeparator.')([A-Za-z]{1})#e','#(^[A-Za-z]{1})#e'
  19. )
  20. );
  21. parent::setReplacement(array("strtoupper('\\2')","strtoupper('\\1')"));
  22. }
  23.  
  24. return parent::filter($value);
  25. }
  26. //......
  27. }
  28.  

So taking some ideas from Zend Framework 2 here is more robust work around to the deprecated error. I would suggest you test and even consider moving the Zend_Filter_Word_SeparatorToCamelCase functionality into your own library so as not to change the core of Zend Framework 1 at all.

  1. public function filter($value) {
  2.  
  3. $pregQuotedSeparator = preg_quote($this->_separator, '#');
  4.  
  5. $patterns = array(
  6. '#(' . $pregQuotedSeparator . ')([A-Za-z]{1})#',
  7. '#(^[A-Za-z]{1})#',
  8. );
  9. $replacements = array(
  10. function ($matches) {
  11. return strtoupper($matches[2]);
  12. },
  13. function ($matches) {
  14. return strtoupper($matches[1]);
  15. },
  16. );
  17.  
  18. $filtered = $value;
  19. foreach ($patterns as $index => $pattern) {
  20. $filtered = preg_replace_callback($pattern, $replacements[$index], $filtered);
  21. }
  22. return $filtered;
  23. }
  24.  

Notice that we call the recommended preg_replace_callback function and pass it some simple lambdas as the second parameter. Now everything works and we don't get a deprecated error.

Read more about the preg_replace function here and the preg_replace_callback function here.

I hope you found this article interesting; if you did I would love to hear from you.
Follow me on Twitter @debugthat. Thanks!