Intereting Posts
как использовать SQL-соединение в PHP для выполнения запроса по двум базам данных Какова наиболее масштабируемая структура каталогов на основе PHP для большого сайта? Автозагрузка пользовательской библиотеки в Zend Framework 2.0 Загрузка Dropbox внутри скрипта Вернуть первый внешний результат из строки, используя регулярные выражения Специальные символы в URL для выбора БД PHP Замечание: преобразование массива в строку только на PHP 7 Как выполнить javascript на сервере node.js Php / json: декодировать utf8? Нужен ли RedBean первичный ключ «id»? Изменение значения внутри цикла foreach не изменяет значение в массиве, который повторяется Как запустить одиночный метод тестирования с помощью phpunit? Как отправить значения флажка методом PHP post Проверьте, существует ли свойство в магически заданных свойствах Как преобразовать строку в алфавитно-цифровое и преобразовать пробелы в тире?

Замена переменных в строке

Я работаю на многоязычном веб-сайте в PHP, и в файлах моих языков у меня часто есть строки, которые содержат несколько переменных, которые будут позже заполнены для завершения предложений.

В настоящее время я {VAR_NAME} в строку и вручную заменяя каждое вхождение своим значением соответствия при его использовании.

Итак, в основном:

{X} created a thread on {Y}

становится:

Dany created a thread on Stack Overflow

Я уже думал о sprintf но я нахожу это неудобным, потому что он зависит от порядка переменных, которые могут меняться от языка к другому.

И я уже проверил Как заменить переменную в строке со значением в php? и на данный момент я в основном использую этот метод.

Но мне интересно знать, есть ли встроенный (или, может быть, нет) удобный способ в PHP, чтобы сделать это, учитывая, что у меня уже есть переменные, названные в точности как X и Y в предыдущем примере, больше как $$ для переменной переменной ,

Поэтому вместо того, чтобы делать str_replace в строке, я мог бы вызвать такую ​​функцию:

 $X = 'Dany'; $Y = 'Stack Overflow'; $lang['example'] = '{X} created a thread on {Y}'; echo parse($lang['example']); 

будет также распечатываться:

Dany created a thread on Stack Overflow

Благодаря!

редактировать

Строки служат в качестве шаблонов и могут использоваться несколько раз с различными входами.

Таким образом, в основном "{$X} ... {$Y}" не будет делать трюк, потому что я потеряю шаблон, и строка будет инициализирована начальными значениями $X и $Y которые еще не определены ,

Я собираюсь добавить ответ здесь, потому что ни один из текущих ответов действительно не разрезал горчицу на мой взгляд. Я пойду прямо и покажу вам код, который я бы использовал для этого:

 function parse( /* string */ $subject, array $variables, /* string */ $escapeChar = '@', /* string */ $errPlaceholder = null ) { $esc = preg_quote($escapeChar); $expr = "/ $esc$esc(?=$esc*+{) | $esc{ | {(\w+)} /x"; $callback = function($match) use($variables, $escapeChar, $errPlaceholder) { switch ($match[0]) { case $escapeChar . $escapeChar: return $escapeChar; case $escapeChar . '{': return '{'; default: if (isset($variables[$match[1]])) { return $variables[$match[1]]; } return isset($errPlaceholder) ? $errPlaceholder : $match[0]; } }; return preg_replace_callback($expr, $callback, $subject); } 

Что это делает?

В двух словах:

  • Создайте регулярное выражение, используя указанный escape-символ, который будет соответствовать одной из трех последовательностей (подробнее об этом ниже)
  • preg_replace_callback() это в preg_replace_callback() , где обратный вызов обрабатывает две из этих последовательностей и обрабатывает все остальное как операцию замены.
  • Вернуть результирующую строку

Регулярное выражение

Регулярное выражение соответствует любой из этих трех последовательностей:

  • Два появления escape-символа, за которыми следуют ноль или более случаев escape-символа, за которым следует открывающая фигурная скобка. Расходуются только первые два появления escape-символа. Это заменяется одним вхождением escape-символа.
  • Единственное появление escape-символа, за которым следует открывающая фигурная скобка. Это заменяется буквальной открытой фигурной скобкой.
  • Открывающая фигурная скобка, за которой следуют один или несколько символов слова perl (альфа-число и символ подчеркивания), а затем закрывающая фигурная скобка. Это рассматривается как местозаполнитель, и поиск выполняется для имени между фигурными скобками в массиве $variables , если он найден, тогда возвращаем значение замены, если нет, то возвращаем значение $errPlaceholder – по умолчанию это значение равно null , что рассматривается как частный случай, и возвращается исходный placeholder (т.е. строка не изменяется).

Почему это лучше?

Чтобы понять, почему это лучше, давайте посмотрим на подходы к замене другими ответами. За одним исключением (единственным недостатком которого является совместимость с PHP <5.4 и слегка неочевидное поведение), они делятся на две категории:

  • strtr() – Это не обеспечивает механизм для обработки escape-символа. Что делать, если ваша строка ввода нуждается в литерале {X} ? strtr() не учитывает это, и он будет заменен значением $X
  • str_replace() – это страдает от той же проблемы, что и strtr() , и еще одна проблема. Когда вы вызываете str_replace() аргументом массива для аргументов поиска / замены, он ведет себя так, как если бы вы вызывали его несколько раз – по одному для каждого из пар пар замены. Это означает, что если одна из ваших строк замены содержит значение, которое появляется позже в массиве поиска, вы также замените это.

Чтобы продемонстрировать эту проблему с помощью str_replace() , рассмотрите следующий код:

 $pairs = array('A' => 'B', 'B' => 'C'); echo str_replace(array_keys($pairs), array_values($pairs), 'AB'); 

Теперь вы, вероятно, ожидаете, что вывод здесь будет BC но на самом деле это будет CC ( demo ) – это потому, что первая итерация заменила A на B , а на второй итерации строка темы была BB – поэтому оба этих события из B были заменены на C

Эта проблема также выдает оценку производительности, которая может быть не сразу очевидной – поскольку каждая пара обрабатывается отдельно, операция O(n) , для каждой пары замещений выполняется поиск всей строки и выполняется операция замены. Если у вас была очень большая сюжетная строка и много пар замен, это значительная операция, проходящая под капотом.

Возможно, это соображение производительности не является проблемой – вам понадобится очень большая строка и множество пар замен, прежде чем вы получите значимое замедление, но все равно стоит вспомнить. Также стоит помнить, что регулярное выражение имеет собственные штрафы за производительность, поэтому в целом это соображение не должно включаться в процесс принятия решений.

Вместо этого мы используем preg_replace_callback() . Это посещает любую часть строки, которая ищет совпадения ровно один раз, в пределах предоставленного регулярного выражения. Я добавляю этот классификатор, потому что, если вы напишете выражение, которое вызывает катастрофическое обратное отслеживание, то оно будет значительно больше одного раза, но в этом случае это не должно быть проблемой (чтобы избежать этого, я сделал единственное повторение в выражении притяжательное ).

Мы используем preg_replace_callback() вместо preg_replace() чтобы мы могли применять пользовательскую логику, ища строку замены.

Что это позволяет делать

Исходный пример из вопроса

 $X = 'Dany'; $Y = 'Stack Overflow'; $lang['example'] = '{X} created a thread on {Y}'; echo parse($lang['example']); 

Это становится:

 $pairs = array( 'X' = 'Dany', 'Y' = 'Stack Overflow', ); $lang['example'] = '{X} created a thread on {Y}'; echo parse($lang['example'], $pairs); // Dany created a thread on Stack Overflow 

Что-то более продвинутое

Теперь предположим, что у нас есть:

 $lang['example'] = '{X} created a thread on {Y} and it contained {X}'; // Dany created a thread on Stack Overflow and it contained Dany 

… и мы хотим, чтобы второй {X} появился буквально в результирующей строке. Используя escape-символ по умолчанию @ , мы бы изменили его на:

 $lang['example'] = '{X} created a thread on {Y} and it contained @{X}'; // Dany created a thread on Stack Overflow and it contained {X} 

Хорошо, хорошо выглядит до сих пор. Но что, если этот @ должен был быть буквальным?

 $lang['example'] = '{X} created a thread on {Y} and it contained @@{X}'; // Dany created a thread on Stack Overflow and it contained @Dany 

Обратите внимание, что регулярное выражение предназначено только для того, чтобы обратить внимание на escape-последовательности, которые непосредственно предшествуют открывающей фигурной скобке. Это означает, что вам не нужно избегать escape-символа, если он не появится сразу перед заполнителем.

Замечание об использовании массива в качестве аргумента

В исходном примере кода используются переменные, названные так же, как и заполнители в строке. Mine использует массив с именованными ключами. Для этого есть две очень веские причины:

  1. Ясность и безопасность – намного проще увидеть, что в итоге будет заменено, и вы не рискуете случайно заменить переменные, которые вы не хотите раскрывать. Было бы неплохо, если бы кто-то мог просто прокормить {dbPass} и посмотреть ваш пароль базы данных, не так ли?
  2. Область действия – невозможно импортировать переменные из области вызова, если вызывающий объект не является глобальной областью. Это делает функцию бесполезной, если вызвана из другой функции, а импорт данных из другой области – очень плохая практика.

Если вы действительно хотите использовать именованные переменные из текущей области (и я не рекомендую это из-за вышеупомянутых проблем безопасности), вы можете передать результат вызова get_defined_vars() во второй аргумент.

Заметка о выборе escape-символа

Вы заметите, что я выбрал @ как escape-символ по умолчанию. Вы можете использовать любой символ (или последовательность символов, его может быть несколько), передав его третьему аргументу – и у вас может возникнуть соблазн использовать \ так как это то, что используют многие языки, но держитесь, прежде чем делать это .

Причина, по которой вы не хотите использовать \ связана с тем, что многие языки используют ее как свой собственный escape-символ, а это означает, что когда вы хотите указать свой escape-символ, например, в строковом литерале PHP, вы столкнетесь с этой проблемой:

 $lang['example'] = '\\{X}'; // results in {X} $lang['example'] = '\\\{X}'; // results in \Dany $lang['example'] = '\\\\{X}'; // results in \Dany 

Это может привести к кошмару читаемости и некоторому неочевидному поведению со сложными шаблонами. Выберите escape-символ, который не используется каким-либо другим языком (например, если вы используете этот метод для генерации фрагментов HTML, не используйте & как символ escape).

Подводить итоги

То, что вы делаете, имеет крайние случаи. Чтобы решить проблему должным образом, вам нужно использовать инструмент, способный обрабатывать эти кромки – и когда дело доходит до строковых манипуляций, инструмент для работы чаще всего является регулярным выражением.

Вот портативное решение, использующее переменные переменные. Ура!

 $string = "I need to replace {X} and {Y}"; $X = 'something'; $Y = 'something else'; preg_match_all('/\{(.*?)\}/', $string, $matches); foreach ($matches[1] as $value) { $string = str_replace('{'.$value.'}', ${$value}, $string); } 

Сначала вы настраиваете свою строку и свои замены. Затем вы выполняете регулярное выражение, чтобы получить массив совпадений (строки в {и}, включая эти скобки). Наконец, вы обходите вокруг них и заменяете их теми переменными, которые вы создали выше, используя переменные переменные. Прекрасный!


Просто подумал, что я обновил бы это с помощью другого варианта, даже если вы отметили его как правильное. Вам не нужно использовать переменные переменные, и массив можно использовать в нем.

 $map = array( 'X' => 'something', 'Y' => 'something else' ); preg_match_all('/\{(.*?)\}/', $string, $matches); foreach ($matches[1] as $value) { $string = str_replace('{'.$value.'}', $map[$value], $string); } 

Это позволит вам создать функцию со следующей подписью:

 public function parse($string, $map); // Probably what I'd do tbh 

Если вы используете 5.4, и вам bindTo() возможность использования встроенной переменной интерполяции PHP в строке, вы можете использовать метод bindTo() Closure следующим образом:

 // Strings use interpolation, but have to return themselves from an anon func $strings = [ 'en' => [ 'message_sent' => function() { return "You just sent a message to $this->recipient that said: $this->message."; } ], 'es' => [ 'message_sent' => function() { return "Acabas de enviar un mensaje a $this->recipient que dijo: $this->message."; } ] ]; class LocalizationScope { private $data; public function __construct($data) { $this->data = $data; } public function __get($param) { if(isset($this->data[$param])) { return $this->data[$param]; } return ''; } } // Bind the string anon func to an object of the array data passed in and invoke (returns string) function localize($stringCb, $data) { return $stringCb->bindTo(new LocalizationScope($data))->__invoke(); } // Demo foreach($strings as $str) { var_dump(localize($str['message_sent'], array( 'recipient' => 'Jeff Atwood', 'message' => 'The project should be done in 6 to 8 weeks.' ))); } //string(93) "You just sent a message to Jeff Atwood that said: The project should be done in 6 to 8 weeks." //string(95) "Acabas de enviar un mensaje a Jeff Atwood que dijo: The project should be done in 6 to 8 weeks." 

( Демо-версия Codepad )

Возможно, он немного взломан, и мне не нравится использовать $this в этом случае. Но вы получаете дополнительное преимущество от использования интерполяции переменных PHP (что позволяет вам делать такие вещи, как экранирование, которых трудно достичь с помощью регулярных выражений).


EDIT: добавлен LocalizationScope , который добавляет еще одно преимущество: никаких предупреждений, если анонимные функции локализации пытаются получить доступ к данным, которые не были предоставлены.

strtr , вероятно, лучший выбор для такого рода вещей, потому что он заменяет самые длинные ключи:

 $repls = array( 'X' => 'Dany', 'Y' => 'Stack Overflow', ); foreach($data as $key => $value) $repls['{' . $key . '}'] = $value; $result = strtr($text, $repls); 

(подумайте о ситуациях, когда у вас есть такие ключи, как XX и X)


И если вы не хотите использовать массив и вместо этого выставляете все переменные из текущей области:

 $repls = get_defined_vars(); 

Если ваша единственная проблема с sprintf – это порядок аргументов, вы можете использовать опцию обмена аргументами.

Из документа ( http://php.net/manual/en/function.sprintf.php ):

 $format = 'The %2$s contains %1$d monkeys'; echo sprintf($format, $num, $location); 

gettext – широко используемая универсальная система локализации, которая делает именно то, что вы хотите. Существуют библиотеки для большинства языков программирования, а PHP имеет встроенный движок . Он управляется po-файлами, простым текстовым форматом, для которого существует множество редакторов, и совместим с синтаксисом sprintf.

У него даже есть некоторые функции для работы с такими вещами, как сложные множественные числа, которые есть у некоторых языков.

Вот несколько примеров того, что он делает. Обратите внимание, что _ () является псевдонимом для gettext ():

  • echo _('Hello world'); // будет выводить hello world на текущий выбранный язык
  • echo sprintf(_("%s has created a thread on %s"), $name, $site); // переводит строку и передает ее sprintf ()
  • echo sprintf(_("%2$s has created a thread on %1$s"), $site, $name); // то же, что и выше, но с измененным порядком параметров.

Если у вас больше нескольких строк, вы должны обязательно использовать существующий движок, а не писать свой собственный. Добавление нового языка – это всего лишь вопрос перевода списка строк, и большинство профессиональных инструментов перевода также могут работать с этим файловым форматом.

Проверьте Википедию и документацию по PHP для базового обзора того, как это работает:

Google обнаруживает, что кучи документации и ваш любимый репозиторий программного обеспечения, скорее всего, содержат несколько инструментов для управления po-файлами.

Некоторые из которых я использовал:

  • poedit : Очень легкий и простой. Хорошо, если у вас нет слишком много материала для перевода, и вы не хотите тратить время на размышления о том, как это работает.
  • Virtaal : немного сложнее и имеет немного кривой обучения, но также и некоторые приятные функции, которые облегчают вашу жизнь. Хорошо, если вам нужно много перевести.
  • GlotPress – это веб-приложение (от людей WordPress), которое позволяет совместное редактирование файлов базы данных перевода.

Почему бы не использовать str_replace? Если вы хотите его как шаблон.

 echo str_replace(array('{X}', '{Y}'), array($X, $Y), $lang['example']); 

для каждого случая этого, что вам нужно

str_replace был создан для этого в первую очередь.

Как насчет определения «переменных» частей как массива с ключами, соответствующими заполнителям в вашей строке?

 $string = "{X} created a thread on {Y}"; $values = array( 'X' => "Danny", 'Y' => "Stack Overflow", ); echo str_replace( array_map(function($v) { return '{'.$v.'}'; }, array_keys($values)), array_values($values), $string ); 

Почему вы не можете использовать строку шаблона внутри функции?

 function threadTemplate($x, $y) { return "{$x} created a thread on {$y}"; } echo threadTemplate($foo, $bar); 

Просто:

 $X = 'Dany'; $Y = 'Stack Overflow'; $lang['example'] = "{$X} created a thread on {$Y}"; 

Следовательно:

 echo $lang['example']; 

Вывод:

 Dany created a thread on Stack Overflow 

Как вы просили.

ОБНОВИТЬ:

Согласно комментариям OP о том, чтобы сделать решение более портативным:

Каждый раз, когда вы проводите разбор пар,

 class MyParser { function parse($vstr) { return "{$x} created a thread on {$y}"; } } 

Таким образом, если происходит следующее:

 $X = 3; $Y = 4; $a = new MyParser(); $lang['example'] = $a->parse($X, $Y); echo $lang['example']; 

Который вернется:

 3 created a thread on 4; 

И, двойная проверка:

 $X = 'Steve'; $Y = 10.9; $lang['example'] = $a->parse($X, $Y); 

Будет печать:

 Steve created a thread on 10.9; 

По желанию.

ОБНОВЛЕНИЕ 2:

В соответствии с комментариями OP о повышении переносимости:

 class MyParser { function parse($vstr) { return "{$vstr}"; } } $a = new MyParser(); $X = 3; $Y = 4; $vstr = "{$X} created a thread on {$Y}"; $a = new MyParser(); $lang['example'] = $a->parse($vstr); echo $lang['example']; 

Выведет результаты, приведенные ранее.

Пытаться

 $lang['example'] = "$X created a thread on $Y"; 

EDIT: Основываясь на последней информации

Возможно, вам нужно посмотреть на функцию sprintf ()

Тогда вы могли бы определить строку шаблона как это

 $template_string = '%s created a thread on %s'; $X = 'Fred'; $Y = 'Sunday'; echo sprintf( $template_string, $X, $Y ); 

$template_string не изменяется, но позже в вашем коде, когда вы назначили разные значения для $X и $Y вы все равно можете использовать echo sprintf( $template_string, $X, $Y );

См. Руководство по PHP

просто бросая другое решение при использовании ассоциативных массивов. Это будет проходить через ассоциативный массив и либо заменить шаблон, либо оставить его пустым.

пример:

 $list = array(); $list['X'] = 'Dany'; $list['Y'] = 'Stack Overflow'; $str = '{X} created a thread on {Y}'; $newstring = textReplaceContent($str,$list); function textReplaceContent($contents, $list) { while (list($key, $val) = each($list)) { $key = "{" . $key . "}"; if ($val) { $contents = str_replace($key, $val, $contents); } else { $contents = str_replace($key, "", $contents); } } $final = preg_replace('/\[\w+\]/', '', $contents); return ($final); } 

возможно, для других кодеров нужна функция синтаксического анализа;

 $X = 'Dany'; $Y = 'Stack Overflow'; $lang['example'] = "{X} created a thread on {Y}"; function parse($var) { $return_value = $var; $matches_value = preg_match_all('/\{(.*?)\}/',$var,$matches); if($matches_value > 0){ foreach($matches[1] as $match){ if(isset($GLOBALS[$match])){ // !empty($GLOBALS[$match]) -- you choose $return_value = str_replace('{'.$match.'}',$GLOBALS[$match],$return_value); } } } return $return_value; } echo parse($lang['example']); 

если переменные $ X и $ Y заданы всегда. с eval:

 $X = 'Dany'; $Y = 'Stack Overflow'; $lang['example'] = "{X} created a thread on {Y}"; $lang['example'] = preg_replace('/\\{(.*?)\\}/', '\\$\\1',$lang['example']); eval("\$return_value = \"".$lang['example']."\";"); echo $return_value;