#php #methods #chaining
#php #методы #объединение в цепочку
Вопрос:
Возможно ли объединить все функции PHP в цепочку через объект / класс?
У меня это на уме, и я представляю это примерно так:
$c = new Chainer();
$c->strtolower('StackOverFlow')->ucwords(/* the value from the first function argument */)->str_replace('St', 'B', /* the value from the first function argument */);
это должно привести:
Backoverflow
Спасибо.
Комментарии:
1. Что вы подразумеваете под «всеми функциями PHP»? Все встроенные функции PHP, например, string, array, функции mysql? Я думаю, что это невозможно, потому что каждая функция должна возвращать объект.
2. В чем было бы преимущество выполнения этого вместо
$c->strtolower(ucwords(str_replace('St','B','StackOverFlow')))
? Удобочитаемость?
Ответ №1:
Взгляните на:
http://php.net/manual/en/language.oop5.magic.php
особенно:
http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods
и, вероятно:
http://php.net/manual/en/function.call-user-func-array.php
Поскольку многие опубликовали свои примеры, я тоже попробую:
<?php
class Chainer
{
protected $buffer = null;
public function __call($name, $args) {
if (method_exists($this, $name)) {
$this->buffer = call_user_func_array(array($this, $name), $args);
}
elseif (function_exists($name)) {
if ($this->buffer !== null) {
$args[] = $this->buffer;
}
$this->buffer = call_user_func_array($name, $args);
}
return $this;
}
public function strpos($needle, $offset = 0) {
return strpos($this->buffer, $needle, $offset);
}
public function __toString() {
return (string)$this->buffer;
}
}
$c = new Chainer();
echo $c->strtolower('StackOverFlow')->ucwords()->str_replace('St', 'B')->strpos('overflow'); // output: 4
Комментарии:
1. Да! Это то, что мне действительно было нужно. Никогда до конца не понимал эти волшебные функции до сих пор.
2. Это приведет к разрыву первой функции, которая не принимает параметр subject в качестве последнего аргумента
3. Правильно. Это всего лишь пример того, как
__call
можно было бы использовать.4. да, вы правы на 100%, но я всегда могу это как-то отфильтровать 😉
5. Я изменил пример кода, чтобы включить представление о том, как можно обрабатывать особые случаи. (Смотрите разделы strpos и method_exists)
Ответ №2:
Вы имеете в виду сделать str_replace('St', 'B', ucwords(strtolower('StackOverFlow')))
?
Методы, которые вы вызываете выше, являются функциями, а не методами, привязанными к какому-либо классу. Chainer
пришлось бы реализовать эти методы. Если это то, что вы хотите сделать (возможно, для другой цели, и это всего лишь пример), ваша реализация Chainer
может выглядеть следующим образом:
class Chainer {
private $string;
public function strtolower($string) {
$this->string = strtolower($string);
return $this;
}
public function ucwords() {
$this->string = ucwords($this->string);
return $this;
}
public function str_replace($from, $to) {
$this->string = str_replace($from, $to, $this->string);
return $this;
}
public function __toString() {
return $this->string;
}
}
В приведенном выше примере это несколько сработало бы, но вы бы назвали это так:
$c = new Chainer;
echo $c->strtolower('StackOverFlow')
->ucwords()
->str_replace('St', 'B')
; //Backoverflow
Обратите внимание, что вы никогда не получите значение /* the value from the first function argument */
обратно из цепочки, поскольку это не имело бы смысла. Возможно, вы могли бы сделать это с помощью глобальной переменной, но это было бы довольно отвратительно.
Дело в том, что вы можете объединять методы в цепочку, возвращая $this
каждый раз. Следующий метод вызывается для возвращаемого значения, которое является тем же объектом, потому что вы его вернули (returned $this
). Важно знать, какие методы запускают и останавливают цепочку.
Я думаю, что эта реализация имеет наибольший смысл:
class Chainer {
private $string;
public function __construct($string = '') {
$this->string = $string;
if (!strlen($string)) {
throw new Chainer_empty_string_exception;
}
}
public function strtolower() {
$this->string = strtolower($this->string);
return $this;
}
public function ucwords() {
$this->string = ucwords($this->string);
return $this;
}
public function str_replace($from, $to) {
$this->string = str_replace($from, $to, $this->string);
return $this;
}
public function __toString() {
return $this->string;
}
}
class Chainer_empty_string_exception extends Exception {
public function __construct() {
parent::__construct("Cannot create chainer with an empty string");
}
}
try {
$c = new Chainer;
echo $c->strtolower('StackOverFlow')
->ucwords()
->str_replace('St', 'B')
; //Backoverflow
}
catch (Chainer_empty_string_exception $cese) {
echo $cese->getMessage();
}
Комментарии:
1. @Phil просто совпадение, хотя это не так сложно выяснить. На самом деле мне не нравится ваша реализация, потому что вы допускаете, что строка будет установлена позже другими методами, что для меня не имеет смысла.
Ответ №3:
Вы можете сделать это при условии, что Chainer
класс выглядит примерно так
class Chainer
{
private $string;
public function __construct($string = null)
{
$this->setString($string);
}
public function setString($string)
{
$this->string = $string;
return $this;
}
public function __toString()
{
return $this->string;
}
public function strtolower($string = null)
{
if (null !== $string) {
$this->setString($string);
}
$this->string = strtolower($this->string);
return $this;
}
public function ucwords($string = null)
{
if (null !== $string) {
$this->setString($string);
}
$this->string = ucwords($this->string);
return $this;
}
public function str_replace($search, $replace, $string = null)
{
if (null !== $string) {
$this->string = $string;
}
$this->string = str_replace($search, $replace, $this->string);
return $this;
}
}
Хотя для меня это выглядит довольно глупо.
Возможно, вам удастся внедрить волшебный __call
метод, но это будет серьезной проблемой для работы с сохраненной переменной и необязательными аргументами метода.
Комментарии:
1. @metadata Пфф, почему alot получает все похвалы?