PHP multiple __toString methods, switched in the runtime - php

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:
the firstname comes first, followed by space and surname Thomas Müller
the surname comes first and uppercased, followed by space and the firstname MÜLLER Thomas
the surname comes first, followed by comma, then space and the firstname Müller, Thomas
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:
Person class is getting heavy
switch-case statements can hide some mistakes
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().

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

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.

Related

Importing Parent function data in child php oop

I am trying to learn simple method of php oop by calling parent function data in child.
However somewhere I am making mistake.
class name{
var $firstname;
var $lastname;
var $name;
public function name($firstname, $lastname){
$this->firstname=$firstname;
$this->lastname=$lastname;
$this->name=$this->firstname." ".$this->lastname;
return $this->name;
}
}
class sentence extends name{
var $name;
var $letter;
function sentence(){
$this->letter="My name is ";
echo $this->letter.$this->name;
}
}
$name=new name(ABC, Xyz);
$letter=new sentence();
I have created one calls to get name input and another child class to write the sentence. However not able to call the name in child.
a quick fix since thats probably what you want.
<?php
class name {
private $firstname;
private $lastname;
private $name;
private $sentence;
public function __construct($firstname, $lastname){
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->name = $firstname . " " . $lastname;
$this->sentence = "My name is : " . $this->name;
}
public function getName(){
return $this->name;
}
public function getSentence(){
return $this->sentence;
}
}
$instance = new name("test","lastname");
echo $instance->getSentence();
?>
class name
{
public $firstname;
public $lastname;
public $name;
public function name($firstname, $lastname)
{
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->name = $this->firstname.' '.$this->lastname;
return $this->name;
}
}
class sentence extends name
{
public $name;
public $letter;
public function greeting()
{
$this->letter = 'My name is ';
echo $this->letter.$this->name;
}
}
//$name=new name(ABC, Xyz);
$letter = new sentence('ABC', 'Xyz');
$letter->greeting();
Thanks to #ManhNguyen
Are you trying to read the state of variables in a parent class?
That is not how OOP works.
You have to either group together all the variables into one class or pass it trough as an argument to the function you want to have access to the data.
Second of all you don't seem to not be using a __construct function and yet pass variables into the new name() function that is also a NO-NO.

function __get() not giving any effect in PHP

So I'm following a tutorial about OOP in PHP and got stuck in understanding how __get() function works. Here's the code:
<?php
class Animal{
protected $name;
protected $favorite_food;
protected $sound;
protected $id;
public static $number_of_animals = 0;
const PI = "3.14159";
//function to return the name
//encapsulation
function getName(){
//when you want to refer attribute in a class
return $this->name;
}
//initialize things
function __construct(){
//generate random 100-10
$this->id = rand(1,10);
echo $this->id ." has been assigned<br/>";
//akses static attribute in a class
Animal::$number_of_animals++;
}
//destruct the object
function __destruct(){
echo $this->name ." is being destroyed :(";
}
//getter : to get protected attribute of a function
function __get($name){
echo "Asked for " . $name . "<br/>";
return $this->$name;
}
//setter : set the attribute to
function __set($name, $value){
switch($name){
case "name" :
$this->name = $value;
break;
case "favorite_food" :
$this->favorite_food = $value;
break;
case "sound" :
$this->sound = $value;
break;
default :
echo $name ."Name not found";
}
echo "Set " .$name. " to " .$value. "<br/>";
}
function run(){
echo $this->name. " runs<br/>";
}
}
class Dog extends Animal{
function run(){
echo $this->name. " runs like crazy<br/>";
}
}
$animal_one = new Animal();
$animal_one->name = " SPOT";
$animal_one->favorite_food = " MEAT";
$animal_one->sound = " RUFF";
echo $animal_one->name ." says". $animal_one->sound. " give me some " .$animal_one->favorite_food. " my id is " .$animal_one->id. " total animal is " .Animal::$number_of_animals. "<br/><br/>";
?>
The output will be like this :
5 has been assigned
Set name to SPOT
Set favorite_food to MEAT
Set sound to RUFF
Asked for name
Asked for sound
Asked for favorite_food
Asked for id
SPOT says RUFF give me some MEAT my id is 5 total animal is 1
SPOT is being destroyed :(
When I try to change the argument and value in __get() function to another attribute like $sound or $favorite_food, it doesn't give any change to the output. The output will still the same. I don't get it why we should set it only to $name.
The name of the parameter inside any function is scoped to that function alone, and doesn't have any reference anywhere else.
You're probably getting confused in that your local function parameter $name has the same name as one of it's class properties $this->name
Notice in your __get method, $name is a stand-in variable for what could be any protected/private property, which is dynamically evaluated at run-time:
$this->$name
as opposed to a hard-coded property
$this->name
Consider this example:
class MyClass {
protected $one = 'first';
protected $name = 'fred';
public function __get(String $property){
return $this->$property;
}
public function getOne(){
return $this->one;
}
public function foo(String $variable_could_be_named_anything){
return $variable_could_be_named_anything;
}
}
$object = new MyClass;
echo $object->one; // first (using __get)
echo $object->getOne(); // first
$object->two = 'second'; // because this property isn't declared protected, accessed normally
echo $object->two; // second
$name = 'jon';
echo $object->name; // fred
echo $object->foo($name); // jon
echo $object->three; // PHP Notice: Undefined property: MyClass::$three
$object->one = 'something'; // Fatal error: Cannot access protected property

How to initialize variable in constructor in PHP

I am writing this simple code and do not know what the issue with constructor is:
class Animal {
public $_type;
public $_breed;
public function __construct ($t, $b) {
echo "i have initialized<br/>";
$this ->_type = $t;
$this ->_breed = $b;
echo "type is " .$_type. "<br/>";
echo "breed is " .$_breed. "<br/>";
}
public function __destruct () {
echo "i am dying";
}
}
$dog = new Animal("Dog", "Pug");
Why do you have a space after $this? Remove the space.
Also, add $this when calling a variable.
class Animal {
public $_type;
public $_breed;
public function __construct ($t, $b) {
echo "i have initialized<br/>";
$this->_type = $t; // <-- remove space
$this->_breed = $b; // <-- remove space
echo "type is " .$this->_type. "<br/>"; // <-- add $this
echo "breed is " .$this->_breed. "<br/>"; // <-- add $this
}
public function __destruct () {
echo "i am dying";
}
}
You are initializing them fine, but retrieving them wrong...
Since $_type and $_breed are scoped at the object level, you need to tell PHP what scope you're referencing them in.
Therefore, instead of
echo $_breed;
You need
echo $this->_breed;
On a side note, it's very strange practice to prefix variable names with _ these days, even moreso if they are public variables. This will likely confuse other developers working with your code.
Try this (note the echo lines):
class Animal {
public $_type;
public $_breed;
public function __construct ($t, $b) {
echo "i have initialized<br/>";
$this->_type = $t;
$this->_breed = $b;
//You have to use '$this' keyword to access
//class attibutes:
echo "type is " . $this->_type . "<br/>";
echo "breed is " . $this->_breed . "<br/>";
}
public function __destruct () {
echo "i am dying";
}
}
$_type and $_breed is variable of class so you need to use using this keyword
echo "type is " .$this->_type. "<br/>";
echo "breed is " .$this->_breed. "<br/>";
Although this not violate the syntax of the php, I suggest that this two lines
echo "type is " .$dog->_type. "<br/>";
echo "breed is " .$dog->_breed. "<br/>";
must not be put in __construct() instead use this outside the class,
Like this,
class Animal {
public $_type;
public $_breed;
public function __construct ($t, $b) {
$this ->_type = $t;
$this ->_breed = $b;
}
public function __destruct () {
echo "i am dying";
}
}
$dog = new Animal("Dog", "Pug");
echo "i have initialized<br/>";
echo "type is " .$dog->_type. "<br/>";
echo "breed is " .$dog->_breed. "<br/>";
With constructor property promotion in PHP 8, you can now declare and set properties as parameters of the constructor.
See here.
class someClass
{
// Can delete these declarations and put them in the constructor instead //
/*
protected int $id;
protected string $name;
protected int $type;
protected string $frontend_name;
protected int $account_id;
protected someOtherClass $object;
*/
public function __construct(
protected int $id,
protected string $name,
protected int $type,
protected string $frontend_name,
protected int $account_id,
protected someOtherClass $object,) // you can also have a trailing comma now!
{
$this->init();
}
protected function init() {
// do other stuff
}
}

Why child object can not access inherited function from its parent in PHP?

I have made a very basic type of my class and as mentioned in question Why child object can not access inherited function from its parent? I have added one new field and new constructor. show_param() is defined in parent class but I can not use it in child class?!
<?php
class Test {
private $age;
private $fname;
private $lname;
public function __construct($age,$fname,$lname)
{
$this->age = $age;
$this->fname = $fname;
$this->lname = $lname;
echo "A new constructor in " . __CLASS__ . ".<br />";
}
public function __destruct()
{
echo 'The class "', __CLASS__, '" was destroyed.<br />';
}
public function set_age($age)
{
$this->age = $age;
}
public function get_age()
{
return $this->age;
}
public function __toString()
{
return $this->fname." ".$this->lname;
}
public function show_param()
{
echo $this->age."<br />";
echo $this->fname."<br />";
echo $this->lname."<br />";
}
}
//Child definition
class T1 extends Test
{
private $level;
public function __construct($age,$fname,$lname,$level)
{
$this->level = $level;
$this->age = $age;
$this->fname = $fname;
$this->lname = $lname;
echo "A child constructor in " . __CLASS__ . ".<br />";
}
public function get_level()
{
return $this->level;
}
public function set_level($level)
{
$this->level= $level;
}
}
$a = new T1(23,"Bernard","Grey","Under");
echo $a->show_param();
?>
Result:
A child constructor in T1.
A child constructor in T1.
The class "Test" was destroyed.
The class "Test" was destroyed.
Expected:
A child constructor in T1.
A child constructor in T1.
Bernard
Grey
23
The class "Test" was destroyed.
The class "Test" was destroyed.
Use the parent`s constructor in class T1:
class T1 extends Test
{
private $level;
public function __construct($age,$fname,$lname,$level)
{
parent::__construct($age,$fname,$lname);
$this->level = $level;
echo "A child constructor in " . __CLASS__ . ".<br />";
}
public function get_level()
{
return $this->level;
}
public function set_level($level)
{
$this->level= $level;
}
}

Is it possible to pass arguments to the class and the parent class constructor?

Class a {
public function __construct($a){
$this->age = $a;
}
}
Class b extends a {
public function printInfo(){
echo 'age: ' . $this->age . "\n";
}
}
$var = new b('age');
$var->printInfo();
I understand how this code works, however is it possible to pass arguments to the constructor of the class and parent class?
My attempt below is causing an error
Class a {
public function __construct($a){
$this->age = $a;
}
}
Class b extends a {
public function __construct($name){
$this->name = $name;
}
public function printInfo(){
echo 'name: ' . $this->name . "\n";
echo 'age: ' . $this->age . "\n";
}
}
$var = new b('name', 'age');
$var->printInfo();
?>
Yes, you simply need to use the parent::__construct() method.
Like so:
class a{
/**
* The age of the user
*
* #var integer
*/
protected $age;
function __construct($a){
$this->age = $a;
}
}
class b extends a{
/**
* The name of the user
*
* #var string
*/
protected $name;
function __construct($name,$age){
// Set the name
$this->name = $name;
// Set the age
parent::__construct($age);
}
public function printInfo(){
echo 'name: ' . $this->name . "\n";
echo 'age: ' . $this->age . "\n";
}
}
$var = new b('name','age');
$var->printInfo();
Just make sure the variables are set to public or protected!
You can pass value to the parent constructor but the way you are doing is wrong,
$var = new b('name', 'age');
it is as if the child class accepts two parameters in its constructor but in real it has only one parameter.
You can pass parameter to parent constructor something like this
parent::__construct($var);
So change you class b to this
Class b extends a {
public function __construct($name, $age){
$this->name = $name;
parent::__construct($age);
}
public function printInfo(){
echo 'name: ' . $this->name . "\n";
echo 'age: ' . $this->age . "\n";
}
}
Yes you can pass the argument to the class as well as parent class
Class a {
public function __construct($age){
$this->age = $a;
}
}
Class b extends a {
public function __construct($name,$age){
parent::__construct($age);
$this->name = $name;
}
}
$var = new b('name', 'age');
?>
Just call parent::__construct in the child. for example
class Form extends Tag
{
function __construct()
{
parent::__construct();
// Called second.
}
}
Here is how is should go:
<?php
class a {
private $age;
public function __construct($age){
$this->age = $age;
}
public function getAge()
{
return $this->age;
}
}
class b extends a {
private $name;
public function __construct($age, $name){
parent::__construct($age);
$this->name = $name;
}
public function printInfo(){
echo 'name: ' . $this->name . "\n";
echo 'age: ' . $this->getAge() . "\n";
}
}
$b = new b(20, "Bob");
$b->printInfo();
?>

Categories