Можно ли перезаписать функцию в PHP

Можете ли вы объявить такую ​​функцию...

function ihatefooexamples(){
  return "boo-foo!";
};

А затем переобъявите это примерно так...

if ($_GET['foolevel'] == 10){
  function ihatefooexamples(){
    return "really boo-foo";
  };
};

Можно ли таким образом перезаписать функцию?

Так или иначе?


person Mark Lalor    schedule 01.09.2010    source источник
comment
... Можно ли заменить (monkeypatch) функции PHP?   -  person outis    schedule 10.02.2012
comment
Было бы замечательно, если бы PHP был расширен так, чтобы «unset» можно было использовать для удаления определения функции перед его переопределением с помощью «function».   -  person David Spector    schedule 05.06.2019


Ответы (10)


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

Чтобы ответить на комментарии о том, что этот ответ не касается напрямую исходного вопроса. Если вы попали сюда из поиска Google, начните здесь

Доступна функция под названием override_function, которая действительно отвечает всем требованиям. Однако, учитывая, что эта функция является частью расширения Расширенный отладчик PHP, чтобы аргументировать, что override_function() предназначен для производственного использования. Поэтому я бы сказал «Нет», невозможно перезаписать функцию с намерением, которое имел в виду первоначальный вопросник.

Оригинальный ответ

Здесь вы должны воспользоваться преимуществами ООП, особенно полиморфизма.

interface Fooable
{
    public function ihatefooexamples();
}

class Foo implements Fooable
{
    public function ihatefooexamples()
    {
        return "boo-foo!";
    }
}

class FooBar implements Fooable
{
    public function ihatefooexamples()
    {
        return "really boo-foo";
    }
}

$foo = new Foo();

if (10 == $_GET['foolevel']) {
    $foo = new FooBar();
}

echo $foo->ihatefooexamples();
person Peter Bailey    schedule 01.09.2010
comment
+1 за решение реальной проблемы, а не просто за попытку ответить на точный вопрос... - person ircmaxell; 01.09.2010
comment
Единственное изменение, которое я бы сделал, это поместить $foo = new Foo() в блок else на случай, если конструктор (а) дорог, или (б) имеет побочные эффекты. - person Austin Hyde; 01.09.2010
comment
@Остин - если b когда-нибудь произойдет, значит, вы неправильно разработали свой класс. Создание объектов через new должно быть идемпотентным. И я бы сказал, что a встречается крайне редко. - person Peter Bailey; 01.09.2010
comment
В случае, когда вывод остается дурацким и только что действительно добавился к нему, я бы предпочел использовать декоратор, а не совершенно новую стратегию. - person Gordon; 02.09.2010
comment
@ Гордон Ты слишком много анализируешь. Я думаю, довольно ясно, что это всего лишь пример. - person Peter Bailey; 02.09.2010
comment
@Peter: я полностью согласен. Просто не все делают свои классы правильно, или, может быть, на самом деле есть законная причина для побочных эффектов (единственный пример, который приходит на ум, - это счетчик экземпляров). В своем коде я постоянно так перезаписываю переменные, но для всеобщего обозрения я бы сделал это не поленившись и использовал блок else. - person Austin Hyde; 02.09.2010
comment
@Питер, ну, может быть, я. Но ИМО, ОП, мог тогда выбрать более другой пример. Это не слишком надуманно, чтобы предложить Decorator здесь. Однако я не говорю, что Стратегия неверна. - person Gordon; 02.09.2010
comment
Я не согласен с этим ответом, в вопросе четко сказано функция, а не метод, ясно, что контекст не является классом. Я пришел сюда из поиска Google, на данный момент я использую устаревшую библиотеку, отличную от OO, и этот ответ просто бесполезен. - person mastazi; 18.08.2015
comment
Не отвечая на вопрос. Я использую фреймворк с объявленными глобальными функциями. Мне они не нужны, и я хочу их заменить. С этим ответом это совершенно бесполезно. - person Thomas Cheng; 07.10.2016
comment
@mastazi Спасибо, что указали мне, что это лучший ответ, который дает поиск Google по этому вопросу. Я обновил ответ информацией, которая должна помочь будущим искателям ответов с целями, схожими с вашими. - person Peter Bailey; 11.10.2016
comment
@ThomasCheng Как я только что сообщил мастази, этот ответ был обновлен, чтобы лучше подходить для таких людей, как вы. Ваше здоровье. - person Peter Bailey; 11.10.2016
comment
@Peter Bailey, спасибо за обновление, я только что перечитал свой комментарий и понял, что он был не очень дружелюбным, извините за это! - person mastazi; 17.10.2016

Патч обезьяны в пространстве имен php >= 5.3

Менее уклончивым методом, чем модификация интерпретатора, является исправление обезьяны.

Обезьянье исправление - это искусство замены фактической реализации аналогичным «заплаткой» из ваших собственных.

Навыки ниндзя

Прежде чем вы сможете делать обезьяньи патчи, как PHP-ниндзя, мы сначала должны понять пространства имен PHP.

Начиная с PHP 5.3 мы познакомились с пространствами имен, которые на первый взгляд могут показаться эквивалентными чему-то вроде пакетов Java, но это не совсем то же самое. Пространства имен в PHP — это способ инкапсулировать область действия путем создания иерархии фокуса, особенно для функций и констант. Цель этого раздела, откат к глобальным функциям, — объяснить.

Если вы не указываете пространство имен при вызове функции, PHP сначала ищет в текущем пространстве имен, затем перемещается вниз по иерархии, пока не найдет первую функцию, объявленную в этом пространстве имен с префиксом, и не выполнит ее. В нашем примере, если вы вызываете print_r(); из namespace My\Awesome\Namespace;, PHP сначала ищет функцию с именем My\Awesome\Namespace\print_r();, затем My\Awesome\print_r();, затем My\print_r();, пока не найдет встроенную функцию PHP в глобальном пространстве имен \print_r();.

Вы не сможете определить function print_r($object) {} в глобальном пространстве имен, потому что это вызовет конфликт имен, поскольку функция с таким именем уже существует.

Ожидайте фатальную ошибку, например:

Fatal error: Cannot redeclare print_r()

Однако ничто не мешает вам делать именно это в рамках пространства имен.

Исправление обезьяны

Скажем, у вас есть сценарий, использующий несколько вызовов print_r();.

Пример:

<?php
     print_r($some_object);
     // do some stuff
     print_r($another_object);
     // do some other stuff
     print_r($data_object);
     // do more stuff
     print_r($debug_object);

Но позже вы передумали и хотите, чтобы вывод был заключен в теги <pre></pre>. С тобой когда-нибудь случалось?

Прежде чем вы пойдете и измените каждый вызов на print_r();, подумайте вместо этого об исправлении обезьяны.

Пример:

<?php
    namespace MyNamespace {
        function print_r($object) 
        {
            echo "<pre>", \print_r($object, true), "</pre>"; 
        }

        print_r($some_object);
        // do some stuff
        print_r($another_object);
        // do some other stuff
        print_r($data_object);
        // do more stuff
        print_r($debug_object);
    }

Ваш скрипт теперь будет использовать MyNamespace\print_r(); вместо глобального \print_r();

Отлично подходит для имитации модульных тестов.

нРадость!

person nickl-    schedule 26.08.2012
comment
Действительно ценный подход! Спасибо, что поделился - person Nico Haase; 21.03.2016
comment
Это невероятно полезный ответ для тех, кто застрял с устаревшими библиотеками, отличными от oo! - person mastazi; 12.10.2016
comment
Я не могу заставить это работать, когда функции A, B и C определены в существующем большом включаемом файле (я не хочу их менять, потому что они используются на других страницах), и я хочу переопределить только функцию B в основном файле. файл, где B определяется в отдельном включаемом файле. Я предполагаю, что пространства имен не работают в этом случае. - person David Spector; 22.10.2018
comment
@DavidSpector вам нужно пространство имен, в котором оно повторно объявлено, что вы хотите сделать в отдельном включаемом файле, например. namespace SeparateInclude {} затем в основном файле вы снова можете использовать пространство имен namespace MainFile {}, включить новый файл, создать патч обезьяны function B() снова в основной файл, который вызывает \SeparateInclude\B();. Когда вы вызываете B(); в основном файле, теперь он вызывает \MainFile\B();, который вызывает \SeparateInclude\B();, успешно избегая \B(); из большого/исходного включаемого файла. - person nickl-; 05.06.2019
comment
Действительно ли он перемещается вниз по пространствам имен, потому что так не кажется? Кажется, что A\B\C, пытающийся использовать f, будет проверять только \A\B\C\f или \f (верхний уровень f). конкатенировать или нет текущее пространство имен в качестве префикса, или не делать этого) - вы можете подтвердить? Потому что это имело бы смысл, если бы это было так! Но это PHP, поэтому они не будут этого делать (я пробовал и много читал, это дало мне проблеск надежды - раздавите или или покажите мне, что я пропустил, пожалуйста! PHP5 был бы хорош для ответа, кстати) - person Alec Teal; 07.09.2019
comment
Есть ли способ узнать, исправлен ли вызов обезьяны? Допустим, ваш стажер помогает в проекте и не может понять, почему все его заявления имеют теги pre вокруг них. Он вырывает волосы. Затем он бросает программирование и живет монахом-будистом. Однажды он медитирует, и ему приходит в голову, что вызов функции может быть разделен по имени. Что вы ему скажете? - person 1.21 gigawatts; 24.08.2020

Посмотрите override_function, чтобы переопределить функции.

override_function — переопределяет встроенные функции

Пример:

override_function('test', '$a,$b', 'echo "DOING TEST"; return $a * $b;');
person Sarfraz    schedule 01.09.2010
comment
это хороший момент, но если функция большая, нехорошо передавать такие длинные строки! :(, еще один момент заключается в том, что это можно использовать только для встроенных функций, а не для пользовательских! - person RobertPitt; 01.09.2010
comment
@RobertPitt: Согласен, это то, что у нас есть на данный момент, и ваше решение выглядит хорошо, но это зависит от используемой версии php. - person Sarfraz; 01.09.2010
comment
Почему вы не хотите быть на версии php ‹ 5.3.0? если вы не создаете сценарий, который будет продаваться/выпускаться для широкой публики, тогда у вас есть правильная точка зрения. - person RobertPitt; 01.09.2010
comment
@RobertPitt: я использую 5.3 и надеюсь, что OP тоже использует то же самое :) - person Sarfraz; 01.09.2010
comment
Также для этого требуется, чтобы вы установили APD, и я не уверен, что это хорошо для производственных приложений, я имею в виду, зачем вам нужен отладчик, работающий в производственной среде? Это повлияет на производительность. Я также изучаю эту проблему, потому что плагины WordPress используют mysql_, когда WordPress использует mysqli_, поэтому мне нужно выяснить, как сопоставить вызовы. - person Joseph Crawford; 14.04.2015
comment
На мой взгляд, это лучше, чем принятый ответ, поскольку в ОП упоминаются функции, а не методы класса. Пальцы вверх - person mastazi; 18.08.2015
comment
@JosephCrawford APD и переопределение так же хороши, как XDebug для разработки, это не означает, что вы помещаете их на свой рабочий сервер. Ничто в исходном вопросе или ответе Сарфаза не ссылается на производственные приложения. - person amateur barista; 17.10.2016
comment
@amateurbarista прав, но это при условии, что ни один из кодов, к которым они добавляют вызовы override_function, не попадает в производство. Это должен быть временный тестовый код или обернутый проверками среды, чтобы код не запускался в рабочей среде. В противном случае он попытается запуститься на производстве, где APD не установлен, и вызовет массу проблем. Это было моей единственной заботой, ну, а не установкой APD на рабочие серверы. - person Joseph Crawford; 18.10.2016
comment
Я попытался использовать функцию переопределения, но это часть PECL, и PECL необходимо сначала настроить. - person ; 28.12.2017
comment
Есть ли способ узнать, была ли перезаписана функция? - person 1.21 gigawatts; 24.08.2020

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

лучше всего использовать анонимные функции, например

$ihatefooexamples = function()
{
  return "boo-foo!";
}

//...
unset($ihatefooexamples);
$ihatefooexamples = function()
{
   return "really boo-foo";
}

http://php.net/manual/en/functions.anonymous.php

person RobertPitt    schedule 01.09.2010
comment
Вам действительно нужен unset? Я знаю, что это неплохо, просто любопытно (я не пробовал этот конкретный случай)... - person ircmaxell; 01.09.2010
comment
@ircmax Вам не нужно использовать unset - person NullUserException; 01.09.2010
comment
Я понятия не имею об анонимных функциях, я просто знаю, как их создавать, но я никогда не играл с ними, я понимаю, что удаление может быть дорогостоящим, если злоупотреблять, но в зависимости от размера функции это может быть лучше! - person RobertPitt; 01.09.2010
comment
Я не знаю, почему это не получило должного признания. Это самый чистый, удобный и разумный подход. ОП не требовал ООП-подхода (на самом деле, как раз наоборот), а сторонние функции просто не работают, поэтому это единственное решение. - person Christian; 05.09.2011
comment
Это не переопределяет какую-либо функцию, а просто меняет значение переменной. у вас был бы тот же результат, если бы часть закрытия (анонимная функция) была полностью исключена. Это не ответ на вопрос! - person nickl-; 26.08.2012

Вы не можете повторно объявить какие-либо функции в PHP. Однако вы можете переопределить их. Ознакомьтесь с переопределяющими функциями, а также переименование функций, чтобы сохранить функцию, которую вы переопределяете, если хотите.

Итак, имейте в виду, что когда вы переопределяете функцию, вы ее теряете. Возможно, вы захотите сохранить его, но под другим именем. Просто говорю.

Кроме того, если это функции в классах, которые вы хотите переопределить, вам просто нужно создать подкласс и повторно объявить функцию в вашем классе без необходимости выполнять rename_function и override_function.

Пример:

rename_function('mysql_connect', 'original_mysql_connect' );
override_function('mysql_connect', '$a,$b', 'echo "DOING MY FUNCTION INSTEAD"; return $a * $b;');
person tomlikestorock    schedule 01.09.2010
comment
Это требует расширения APD PECL и устарело. Последняя версия от 2004 года. pecl.php.net/package/apd - person jperelli; 26.07.2013

Я бы включил все функции одного корпуса в include файл, а остальные в другой include.

Например, simple.inc будет содержать function boofoo() { simple }, а really.inc будет содержать function boofoo() { really }.

Это помогает удобочитаемости/сопровождению вашей программы, имея все функции одного типа в одном и том же inc.

Затем в верхней части основного модуля

  if ($_GET['foolevel'] == 10) {
    include "really.inc";
  }
  else {
    include "simple.inc";
  }
person Breaking not so bad    schedule 01.09.2010
comment
Если это необходимо (так не кажется), можно добавить $iam = "really" или $iam = "simple" вверху включаемых файлов. - person Breaking not so bad; 05.09.2011
comment
Я думаю, что это лучший ответ на этот вопрос без ООП, поскольку он показывает процедурную реализацию шаблона состояния. (Хотя я бы предпочел реализацию, в которой операторы if заменены циклом foreach.) Я успешно использовал этот шаблон. Выяснить, какая функция используется, можно с помощью регистрации. Сообщения журнала имеют уровень, который больше не используется в производственной или приемочной среде. - person Loek Bergman; 24.01.2016

Вы можете использовать расширение PECL

  • runkit_function_redefine — заменить определение функции новой реализацией.

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

person Gordon    schedule 01.09.2010

Нет, это будет проблемой. Функции переменных PHP

person Chris    schedule 01.09.2010

В зависимости от ситуации, когда вам это нужно, возможно, вы можете использовать такие анонимные функции:

$greet = function($name)
{
    echo('Hello ' . $name);
};

$greet('World');

... затем вы можете установить новую функцию для данной переменной в любое время

person esud    schedule 02.09.2017

Решение для связанного случая, когда у вас есть включаемый файл A, который вы можете редактировать и хотите переопределить некоторые из его функций во включаемом файле B (или в основном файле):

Основной файл:

<?php
$Override=true; // An argument used in A.php
include ("A.php");
include ("B.php");
F1();
?>

Включить файл А:

<?php
if (!@$Override) {
   function F1 () {echo "This is F1() in A";}
}
?>

Включить файл Б:

<?php
   function F1 () {echo "This is F1() in B";}
?>

При переходе к основному файлу отображается сообщение «Это F1() в B».

person David Spector    schedule 22.10.2018