PHP 多个 __toString 方法,在运行时切换

PHP multiple __toString methods, switched in the runtime

同一个 class 我需要不同的 __toString()

例子

我有包含名字和姓氏的人 class。根据当前上下文,我希望以不同的顺序、格式等显示它。想象三种情况:

我可以为每个显示创建 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 只包含它自己的功能而不是所有类型的 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 比应有的更复杂。