PHP 多个 __toString 方法,在运行时切换
PHP multiple __toString methods, switched in the runtime
同一个 class 我需要不同的 __toString()
。
例子
我有包含名字和姓氏的人 class。根据当前上下文,我希望以不同的顺序、格式等显示它。想象三种情况:
- 名字在前,然后是 space 和姓氏
Thomas Müller
- 姓在前且大写,然后是 space 和名字
MÜLLER Thomas
- 姓氏在前,然后是逗号,然后是 space 和名字
Müller, Thomas
我可以为每个显示创建 public 方法。
<meta charset='utf-8'/>
<?php
class Person
{
protected $firstname;
protected $surname;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
public function toStringWithFirstnameFirst()
{
return $this->firstname . " " . $this->surname;
}
public function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->surname, MB_CASE_UPPER, "UTF-8");
return $surnameConverted . " " . $this->firstname;
}
public function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return $this->surname . ", " . $this->firstname;
}
}
$person = new Person("Thomas", "Müller");
echo $person->toStringWithFirstnameFirst() . "<br/>";
echo $person->toStringWithSurnameFirstUppercase() . "<br/>";
echo $person->toStringWithSurnameFirstAndFirstnameAfterComma() . "<br/>";
?>
但我坚持 DescriptiveButVeryLongToStringMethodNames
。
我希望在代码中只包含 echo $person;
。
解决方案:将 class 状态存储在静态成员中
我的第一个解决方案是将 switch-case
放在 __toString()
方法中。条件语句取决于存储在静态变量self::$chosenToStringMethod
中的class状态。所以我需要静态方法来设置 class 状态以及 class 作为枚举的常量。
<meta charset='utf-8'/>
<?php
class Person
{
protected $firstname;
protected $surname;
const PRINT_FIRSTNAME_FIRST = 1;
const PRINT_SURNAME_FIRST_UPPERCASE = 2;
const PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA = 3;
static private $chosenToStringMethod;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
static public function setToStringMethod($choice)
{
self::$chosenToStringMethod = $choice;
}
private function toStringWithFirstnameFirst()
{
return $this->firstname . " " . $this->surname;
}
private function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->surname, MB_CASE_UPPER, "UTF-8");
return $surnameConverted . " " . $this->firstname;
}
private function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return $this->surname . ", " . $this->firstname;
}
public function __toString()
{
switch (self::$chosenToStringMethod) {
case self::PRINT_FIRSTNAME_FIRST:
return $this->toStringWithFirstnameFirst();
break;
case self::PRINT_SURNAME_FIRST_UPPERCASE:
return $this->toStringWithSurnameFirstUppercase();
break;
case self::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA:
return $this->toStringWithSurnameFirstAndFirstnameAfterComma();
break;
default:
return "No __toString method set";
break;
}
}
}
$person = new Person("Thomas", "Müller");
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_FIRSTNAME_FIRST);
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_SURNAME_FIRST_UPPERCASE);
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA);
echo $person . "<br/>";
?>
我看到了这个解决方案的一些缺点:
Person
class 越来越重了
switch-case
语句可以隐藏一些错误
我希望 Person
class 只包含它自己的功能而不是所有类型的 toString。我宁愿有一些可以动态注入的模式 __toString()
.
好的,我假设你问的原因是你想保持干净,但是没有办法将 __toString() 方法设置为现有对象,所以最好解决方案是拆分功能
首先创建一个 PersonRender:
class PersonRender
{
const PRINT_FIRSTNAME_FIRST = 1;
const PRINT_SURNAME_FIRST_UPPERCASE = 2;
const PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA = 3;
static public $chosenToStringMethod;
private $person;
public function __construct($person)
{
$this->person = $person;
}
public function render()
{
switch (self::$chosenToStringMethod)
{
case self::PRINT_SURNAME_FIRST_UPPERCASE :
return $this->toStringWithSurnameFirstUppercase();
case self::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA :
return $this->toStringWithSurnameFirstAndFirstnameAfterComma();
default :
return $this->toStringWithFirstnameFirst();
}
}
private function toStringWithFirstnameFirst()
{
return "{$this->person->firstname} {$this->person->surname}";
}
private function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->person->surname, MB_CASE_UPPER, "UTF-8");
return "{$surnameConverted} {$this->person->firstname}";
}
private function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return "{$this->person->surname}, {$this->person->firstname}";
}
}
然后是人物 class:
class Person
{
public $firstname, $surname;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
public function __toString() {
$render = new PersonRender($this);
return $render->render();
}
}
和一个小测试:
$person = new Person('Foo', 'Bar');
echo $person;
echo '<hr />';
PersonRender::$chosenToStringMethod = PersonRender::PRINT_SURNAME_FIRST_UPPERCASE;
echo $person;
编辑 1
为了保持代码整洁,Person 实体 class 当然应该有私有属性 firstname 和 surename 以及方法 set 和 get
在我看来,最简洁的方法是只为名字和姓氏提供 getter,并在需要时手动 assemble 字符串。在您当前的解决方案中,您只是用不必要的 classes 污染了您的工作空间,并使 class Person
比应有的更复杂。
同一个 class 我需要不同的 __toString()
。
例子
我有包含名字和姓氏的人 class。根据当前上下文,我希望以不同的顺序、格式等显示它。想象三种情况:
- 名字在前,然后是 space 和姓氏
Thomas Müller
- 姓在前且大写,然后是 space 和名字
MÜLLER Thomas
- 姓氏在前,然后是逗号,然后是 space 和名字
Müller, Thomas
我可以为每个显示创建 public 方法。
<meta charset='utf-8'/>
<?php
class Person
{
protected $firstname;
protected $surname;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
public function toStringWithFirstnameFirst()
{
return $this->firstname . " " . $this->surname;
}
public function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->surname, MB_CASE_UPPER, "UTF-8");
return $surnameConverted . " " . $this->firstname;
}
public function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return $this->surname . ", " . $this->firstname;
}
}
$person = new Person("Thomas", "Müller");
echo $person->toStringWithFirstnameFirst() . "<br/>";
echo $person->toStringWithSurnameFirstUppercase() . "<br/>";
echo $person->toStringWithSurnameFirstAndFirstnameAfterComma() . "<br/>";
?>
但我坚持 DescriptiveButVeryLongToStringMethodNames
。
我希望在代码中只包含 echo $person;
。
解决方案:将 class 状态存储在静态成员中
我的第一个解决方案是将 switch-case
放在 __toString()
方法中。条件语句取决于存储在静态变量self::$chosenToStringMethod
中的class状态。所以我需要静态方法来设置 class 状态以及 class 作为枚举的常量。
<meta charset='utf-8'/>
<?php
class Person
{
protected $firstname;
protected $surname;
const PRINT_FIRSTNAME_FIRST = 1;
const PRINT_SURNAME_FIRST_UPPERCASE = 2;
const PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA = 3;
static private $chosenToStringMethod;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
static public function setToStringMethod($choice)
{
self::$chosenToStringMethod = $choice;
}
private function toStringWithFirstnameFirst()
{
return $this->firstname . " " . $this->surname;
}
private function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->surname, MB_CASE_UPPER, "UTF-8");
return $surnameConverted . " " . $this->firstname;
}
private function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return $this->surname . ", " . $this->firstname;
}
public function __toString()
{
switch (self::$chosenToStringMethod) {
case self::PRINT_FIRSTNAME_FIRST:
return $this->toStringWithFirstnameFirst();
break;
case self::PRINT_SURNAME_FIRST_UPPERCASE:
return $this->toStringWithSurnameFirstUppercase();
break;
case self::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA:
return $this->toStringWithSurnameFirstAndFirstnameAfterComma();
break;
default:
return "No __toString method set";
break;
}
}
}
$person = new Person("Thomas", "Müller");
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_FIRSTNAME_FIRST);
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_SURNAME_FIRST_UPPERCASE);
echo $person . "<br/>";
Person::setToStringMethod(Person::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA);
echo $person . "<br/>";
?>
我看到了这个解决方案的一些缺点:
Person
class 越来越重了switch-case
语句可以隐藏一些错误
我希望 Person
class 只包含它自己的功能而不是所有类型的 toString。我宁愿有一些可以动态注入的模式 __toString()
.
好的,我假设你问的原因是你想保持干净,但是没有办法将 __toString() 方法设置为现有对象,所以最好解决方案是拆分功能
首先创建一个 PersonRender:
class PersonRender
{
const PRINT_FIRSTNAME_FIRST = 1;
const PRINT_SURNAME_FIRST_UPPERCASE = 2;
const PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA = 3;
static public $chosenToStringMethod;
private $person;
public function __construct($person)
{
$this->person = $person;
}
public function render()
{
switch (self::$chosenToStringMethod)
{
case self::PRINT_SURNAME_FIRST_UPPERCASE :
return $this->toStringWithSurnameFirstUppercase();
case self::PRINT_SURNAME_FIRST_FIRSTNAME_AFTER_COMMA :
return $this->toStringWithSurnameFirstAndFirstnameAfterComma();
default :
return $this->toStringWithFirstnameFirst();
}
}
private function toStringWithFirstnameFirst()
{
return "{$this->person->firstname} {$this->person->surname}";
}
private function toStringWithSurnameFirstUppercase()
{
$surnameConverted = mb_convert_case($this->person->surname, MB_CASE_UPPER, "UTF-8");
return "{$surnameConverted} {$this->person->firstname}";
}
private function toStringWithSurnameFirstAndFirstnameAfterComma()
{
return "{$this->person->surname}, {$this->person->firstname}";
}
}
然后是人物 class:
class Person
{
public $firstname, $surname;
public function __construct($firstname, $surname)
{
$this->firstname = $firstname;
$this->surname = $surname;
}
public function __toString() {
$render = new PersonRender($this);
return $render->render();
}
}
和一个小测试:
$person = new Person('Foo', 'Bar');
echo $person;
echo '<hr />';
PersonRender::$chosenToStringMethod = PersonRender::PRINT_SURNAME_FIRST_UPPERCASE;
echo $person;
编辑 1 为了保持代码整洁,Person 实体 class 当然应该有私有属性 firstname 和 surename 以及方法 set 和 get
在我看来,最简洁的方法是只为名字和姓氏提供 getter,并在需要时手动 assemble 字符串。在您当前的解决方案中,您只是用不必要的 classes 污染了您的工作空间,并使 class Person
比应有的更复杂。