Передать функции в класс - PullRequest
2 голосов
/ 14 ноября 2009

PHP база данных mysql Я создал ответ на вопрос к этому здесь, который специально о нумерации страниц

Мне нужно вызывать метод из одного класса в другом и иметь возможность изменять вызываемый метод. Вот так

class db{

    function a(){ echo 'I run a query';}
    function b(){ echo 'im the other query';}

}


class YourClass {

    var $fcn;
   $db = new db()

    function invoke(){
         call_user_func($this->fcn);
    }

} 



$instance = new YourClass;
$instance->fcn = 'db->a';
$instance->invoke();

Я хочу использовать метод 'a' из класса db в методе 'yourClass' 'invoke' Спасибо

Хорошо, это то, что я собрал из предоставленных ответов, и это работает.

    class A {

    function a(){ 
        $x = 'Method a is used';
        return $x;
        } 
    function b(){ 
        $x = 'Method b is used';
        return $x;
        } 
}

class B {

    function invoke($obj, $method){

        echo call_user_func( array( $obj, $method) );

    }

} 

$instance = new B();
$instance->invoke(new A(),"a");

Который пишет: «Метод a используется» на экране

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

class A {

    function a($var1,$var2,$var3){ 
        $x = 'the three passed values are  ' . $var1 . ' and ' . $var2 . ' and ' . $var3;
        return $x;
        } 
    function b(){ 
        $x = 'im method b';
        return $x;
        } 
}

class B {

    function invoke($obj,$arguments){

        echo call_user_func_array($obj,$arguments);

    }

} 

$arguments = array('apple','banana','pineapple');
$use_function = array(new A(),"a");

$instance = new B();
$instance->invoke($use_function,$arguments);

Это почти работает, но я получаю эти ошибки выше правильного ответа

Отсутствует аргумент 1 для A :: a (), ..... для аргументов 2 и 3, но затем ответ выводится на экран «Три пройденных значения - это яблоко, банан и ананас»

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

Ответы [ 9 ]

8 голосов
/ 14 ноября 2009

Начиная с PHP5.3, вы можете использовать замыкания или функторы для передачи методов. До этого вы могли написать анонимную функцию с помощью create_function () , но это довольно неудобно.

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

удален пример кода, так как он больше не помог после того, как ОП изменил вопрос (см. Вики)

Кроме того, вы можете захотеть взглянуть на архитектурные образцы источников данных Фаулера . Zend Framework (и почти все другие PHP-фреймворки) предлагает классы доступа к базе данных , которые вы можете использовать для этих шаблонов, и есть также класс paginator , так почему не проверяйте их, чтобы узнать, как они это сделали.

удалил РЕДАКТИРОВАТЬ 1, так как он больше не помог после того, как ОП изменил вопрос (см. Вики)

РЕДАКТИРОВАТЬ 2 Хорошо, давайте сделаем шаг за шагом подход к этому (не используя шаблон стратегии, хотя)

То, что вы спрашиваете в вопросе, может быть легко решено с помощью этого кода:

class Foo
{
    public function bar()
    {
        echo 'bar method in Foo';
    }
}

class MyInvoker
{
    protected $myObject;

    public function __construct()
    {
        $this->myObject = new Foo();
    }

    public function __call($method, $args)
    {
        $invocation = array($this->myObject, $method);
        return call_user_func_array($invocation, $args);
    }
}

С помощью этого кода вы просто вызываете соответствующие методы. Нет настройки имен методов. Нет неуклюжего дополнительного метода вызова. Нет необходимости изобретать методы. Вам это не нужно, потому что в PHP есть функция __call, которую вы только что научили отправлять все методы, не существующие в MyInvoker, в $ myObject, например. Foo.:

$invoker = new MyInvoker;
$invoker->bar(); // outputs 'bar method in Foo called'

Возможно, вы также расширили MyInvoker до подкласса Foo, например,

class MyInvoker extends Foo {}

и тогда вы можете сделать то же самое. Это не то, что вам нужно, и это показывает, насколько бессмысленно делать такие вещи. MyInvoker теперь ничего не делает сам по себе. Это пустой класс и фактически такой же, как Foo. Даже при предыдущем подходе, использующем метод __call, он ничего не делает. Вот почему я попросил вас быть более точным в отношении желаемого результата, который представляет собой Paginator.

Первая попытка:

class Paginator()
{
    // A class holding all possible queries of our application
    protected $queries;

    // A class providing access to the database, like PDO_MySql
    protected $dbConn;

    public function __construct()
    {
        $this->db      = new MyPdo();
        $this->queries = new DbQueries();
    }

    public function __call($method, $args)
    {
        $invocation = array($this->queries, $method);
        $query      = call_user_func_array($invocation, $args);
        return $this->dbConn->query($query);
    }
}

С помощью этого кода наш Paginator создает все, что ему нужно, внутри себя, тесно связывая класс соединения db и все запросы, и это позволяет вам вызывать их, например, так:

$paginator = new Paginator;
// assuming we have something like getImageCount() in DbQueries
echo $paginator->getImageCount();

В таком случае Paginator распознает, что он не знает getImageCount (), и вызовет метод __call. Метод __call попытается вызвать метод getImageCount () в DbQueries. Поскольку он существует, он вернет запрос, который, в свою очередь, передается соединению базы данных для его выполнения. Отлично, вы бы сказали, но это не так. На самом деле это ужасно. Ответственность вашего пагинатора заключается в подсчете предметов в таблице и получении предметов из этой таблицы в определенном диапазоне и количестве. Но сейчас он не делает ничего подобного. Он совершенно не замечает происходящего, поэтому давайте попробуем новый класс:

class Paginator
{
    protected $dbConn;
    protected $itemCount;

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

    public function countItems($query)
    {
        $this->itemCount = $this->dbConn->query('select count(*) from (?)', $query);
        return $this->itemCount;
    }

    public function fetchItems($query, $offset = 0, $limit = 20)
    {
          $sql = sprintf('select * from (?) LIMIT %d, %d', $offset, $limit);
          return $this->dbConn->query($sql, $query);
    }
}

Намного лучше. Теперь наш Paginator является агрегатом, а не составным, то есть он не создает экземпляры объектов внутри себя, а требует, чтобы они передавались ему в конструкторе. Это называется внедрение зависимостей (и также обеспечивает слабую связь , когда dbConn использует интерфейс ), что сделает ваше приложение более удобным в обслуживании, поскольку это легко обменять компоненты сейчас. Это также пригодится, когда Unit Testing ваш код.

Кроме того, ваш Paginator теперь концентрируется на том, что он должен делать: подсчитывать и извлекать элементы произвольного запроса. Нет необходимости передавать методы. Нет необходимости в неясном вызове метода. Вы бы использовали это так:

$paginator = new Paginator($dbConn);
$query     = $dbQueries->findImagesUploadedLastWeek(); // returns SQL query string
$images    = $paginator->countItems($query);
if($images > 0) {
    $images = $paginator->fetchItems($query);
}

И это все. Ну, почти. Вы должны были бы сделать нумерацию страниц, конечно. Но это должно быть довольно тривиально, если вы расширяете то, что у вас уже есть выше. Свойство $ imageCount является подсказкой, куда идти дальше.

В любом случае, надеюсь, что я смогу пролить немного света.

P.S. Вызовы $this->dbConn->query($sql, $query) - это, конечно, фиктивный код. Не ожидайте, что сможете скопировать и вставить его и заставить его работать. Кроме того, вы должны убедиться, что запросы, добавленные в Paginator SQL, безопасны для использования. Вы не хотели бы, чтобы кто-то вставлял запрос, который удаляет все ваши строки базы данных. Никогда не доверяйте пользовательскому вводу.

P.P.S. $query должно быть строкой запроса SQL. Проверьте руководство по PHP для PDO :: prepare . В целом, это обеспечивает лучшую производительность и безопасность для подготовки оператора перед его выполнением. Страница в руководстве даст вам подсказку о ? в запросах вызовов. Если вы не хотите использовать PDO, просто используйте sprintf() или str_replace(), чтобы заменить ? на $query, например. $this->dbConn->query(sprintf('SELECT count(*) from (%s)', $query) но имейте в виду, что это не имеет ни одного из преимуществ подготовленного оператора и потенциально открывает дверь для SQL-инъекций уязвимостей.

P.P.P.S Да, Внедрение зависимостей обычно является предпочтительной стратегией. Хотя эта тема является предпочтительной, и, возможно, ее сейчас слишком много, чтобы ее можно было полностью понять, но она того стоит. Пока этого должно быть достаточно, если вы попытаетесь отдать предпочтение агрегации над композицией. Ваши классы должны делать только то, за что они отвечают, и получать любые зависимости через конструктор.

2 голосов
/ 14 ноября 2009

Вот два способа сделать это:

class YourClass {
    var $fcn;
    function invoke($arguments){
       //one way:
       $this->{$this->fcn}($arguments);
       //another way:
       call_user_func_array(array($this, $this->fcn), $arguments);
    }
    function a(){ 
       echo 'I am a()';
    }
} 

$instance = new YourClass;
$instance->fcn = 'a';
$instance->invoke();

Это выведет «Я - ()» из класса.

1 голос
/ 14 ноября 2009

ты почти там

    class db {
        function a(){ echo 'I run a query';}
        function b(){ echo 'im the other query';}
    }


    class YourClass {
        var $fcn;
        function __construct() {
             $this->db = new db();
        }
        function invoke() {
             call_user_func(array(
                $this->{$this->fcn[0]}, 
                $this->fcn[1]
            ));
        }

    }

    $instance = new YourClass;

    $instance->fcn = array('db', 'a');
    $instance->invoke();
    $instance->fcn = array('db', 'b');
    $instance->invoke();

синтаксис довольно причудливый, но работает

// edit: из вашего комментария похоже, что самый простой вариант - передать имя метода в виде строки, например

 class Paginator {
      function __consturct($db, $counter_func) ...

      function get_count() {
            $args = func_get_args();
            return call_user_func_array(
                  array($this->db, $this->counter_func),
                  $args);
      }
 }

 new Paginator($db, 'get_num_products');
0 голосов
/ 14 ноября 2009

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

class DbObserver implements SplObserver
{
   public function update(SplSubject $subject)  // Required
   {
       $method = $subject->getObserverMethod();
       $args   = $subject->getObserverArgs();
       $this->$method($args);
   }

   private function a($args) 
   {
       echo 'I run query ' . $args[0] . '<br />';
   }

   private function b($args)
   {
       echo 'I run query ' . $args[0] . ' because ' . $args[1] . '<br />';
   }

   private function c() 
   {
       echo 'I have no argument' . '<br />';
   }
}

class ObserverObserver implements SplObserver
{
    public function update(SplSubject $subject)  // Required
    {
        if (count($subject->getAttached()) > 1) {
            echo 'I saw that<br />';
        } else {
            echo 'Nothing happened<br />';
        }
    }
}

class DbSubject implements SplSubject
{
    private $observerMethod;
    private $observerArgs = array();
    private $attached     = array();

    public function notify()  // Required
    {
        foreach ($this->attached as $each) {
            $each->update($this);
        }
    }

    public function attach(SplObserver $observer)  // Required 
    {
        $this->attached[] = $observer;
    }

    public function detach(SplObserver $observer)  // Required
    {
        $key = array_keys($this->attached, $observer);
        unset($this->attached[$key[0]]);
    }

    public function setObserverMethod($method, $args = array())
    {
        $this->observerMethod = $method;
        $this->observerArgs = $args;
        return $this;
    }

    public function getObserverMethod()
    {
        return $this->observerMethod;
    }

    public function getObserverArgs()
    {
        return $this->observerArgs;
    }

    public function getAttached()
    {
        return $this->attached;
    }
}

$db_subj = new DbSubject;
$db_obs  = new DbObserver;
$db_subj->attach($db_obs);
$args = array('A');
$db_subj->setObserverMethod('a', $args)->notify();
$args = array('B', 'I can');
$db_subj->setObserverMethod('b', $args)->notify();
$obsvr = new ObserverObserver;
$db_subj->attach($obsvr);
$db_subj->setObserverMethod('c')->notify();
$db_subj->detach($db_obs);
$db_subj->notify();

/**
I run query A
I run query B because I can
I have no argument
I saw that
Nothing happened
**/
0 голосов
/ 14 ноября 2009

Вам необходимо внести это изменение:

$arguments = array('apple','banana','pineapple');
$a = new A();
$use_function = array(&$a,"a"); // Make this changes to your code

$instance = new B();
$instance->invoke($use_function,$arguments);
0 голосов
/ 14 ноября 2009
class DB {
   function a(){ echo 'I run a query';}
   function b(){ echo 'im the other query';}
}

class B {

    protected $db;
    private $method;

    function __constructor($db) { $this->db; }

    function invoke($m){
        $this->method = $m;
        //  Non static call
        call_user_func( array( $this->db, $this->method ) );
    }
}

$db = new DB();

$b = new B($db);
$b->invoke('a');

Я немного изменил свой первоначальный ответ. Вы также можете проверить этот пост, это может помочь:

База данных и ООП практики в PHP

0 голосов
/ 14 ноября 2009
class A {
   function a(){ echo 'I run a query';}
   function b(){ echo 'im the other query';}
}

class B {

    function Test() {
        invoke(new $A(), "a");
    }

    function invoke($obj, $method){
        //  Non static call
        call_user_func( array( $obj, $method ) );

        //  Static call
        //call_user_func( array( 'ClassName', 'method' ) );
    }
} 

Надеюсь, это поможет.

0 голосов
/ 14 ноября 2009

Я предполагаю, что вы используете php здесь. Php поддерживает функции переменных , которые могут решить вашу проблему, но, насколько мне известно, не поддерживает делегаты / указатели на функции.

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

0 голосов
/ 14 ноября 2009

Вы спрашиваете, есть ли в PHP функциональные ссылки? Это не так. Но он позволяет вызывать функции, помещая их имя в строку или массив имени класса и имени метода. См. call_user_func() для описания и функции переменных .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...