mwloda
mwloda

Reputation: 491

PHP multiple __toString methods, switched in the runtime

I need different __toString() for the same class.

Example

I have the Person class that contains a firstname and a surname. According to current context I wish to display it with different order, formatting etc. Imagine three scenarios:

I can create public methods for each display.

<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/>";
?>

But I stuck with DescriptiveButVeryLongToStringMethodNames. I wish to have simply echo $person; in the code.

Solution: store the class state in the static members

My first solution is to place switch-case inside __toString() method. Conditional statement depends on the class state stored in the static variable self::$chosenToStringMethod. So I need static method to set the class state and also class constants that serve as enums.

<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/>";
?>

I see some disadvantages of this solution:

I wish Person class containing only its own functionality not all kinds of toStrings. I would rather have some pattern that can dynamically inject __toString().

Upvotes: 2

Views: 1002

Answers (2)

Basti Funck
Basti Funck

Reputation: 1439

In my opinion the most clean way would be to just provide getter for firstname and surname and manually assemble the strings where you need them. In your current solutions you are just polluting your workspace with unnecessary classes and make the class Person more complex than it should be.

Upvotes: 1

picios
picios

Reputation: 250

ok, I assume the reason, why you're asking, is that you'd like to keep things clean but there's no way to set __toString() method to an existing object, so the best solution would be to split functionality

first create a 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}";
    }

}

and then the Person 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();
    }
}

and a little test:

$person = new Person('Foo', 'Bar');
echo $person;
echo '<hr />';
PersonRender::$chosenToStringMethod = PersonRender::PRINT_SURNAME_FIRST_UPPERCASE;
echo $person;

EDIT 1 to keep the code clean, the Person entity class should of course have private props firstname and surename and methods set and get

Upvotes: 3

Related Questions