Начиная с 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 Да, Внедрение зависимостей обычно является предпочтительной стратегией. Хотя эта тема является предпочтительной, и, возможно, ее сейчас слишком много, чтобы ее можно было полностью понять, но она того стоит. Пока этого должно быть достаточно, если вы попытаетесь отдать предпочтение агрегации над композицией. Ваши классы должны делать только то, за что они отвечают, и получать любые зависимости через конструктор.