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
Related
I'm trying to print a property of the simple class below. But instead i get the error above. I haven't found an answer on similar questions on here. The error triggers on this line:
echo "$object1 name = " . $object1->name . "<br>";
Using XAMPP on Windows Help?
<?php
$object1 = new User("Pickle", "YouGotIt");
print_r($object1);
$object1->name = "Alice";
echo "$object1 name = " . $object1->name . "<br>"; /* this triggers the error */
class User
{
public $name, $password;
function __construct($n, $p) { // class constructor
$name = $n;
$password = $p;
}
}
?>
There are two things wrong in your code,
You're using local variables in your class constructor, not instance properties. Your constructor method should be like this:
function __construct($n, $p) {
$this->name = $n;
$this->password = $p;
}
Now comes to your error, Object of class could not be converted to string. This is because of this $object in echo statement,
echo "$object1 name = " ...
^^^^^^^^
You need to escape this $object1 with backslash, like this:
echo "\$object1 name = " . $object1->name . "<br>";
In my case the problem was the way I was initializing the class variable.
This was my code :
public function __construct(User $userObj) {
$this->$userObj = $userObj;
}
and I solved it by changing it to the following :
public function __construct(User $userObj) {
$this->userObj = $userObj;
}
The line in the first snippet caused the problem : $this->$userObj = $userObj
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
}
}
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.
Is it possible to run php method on property change? Like shown in the example below:
Class:
class MyClass
{
public MyProperty;
function __onchange($this -> MyProperty)
{
echo "MyProperty changed to " . $this -> MyProperty;
}
}
Object:
$MyObject = new MyClass;
$MyObject -> MyProperty = 1;
Result:
"MyProperty changed to 1"
Like #lucas said, if you can set your property as private within the class, you can then use the __set() to detect a change.
class MyClass
{
private $MyProperty;
function __set($name, $value)
{
if(property_exists('MyClass', $name)){
echo "Property". $name . " modified";
}
}
}
$r = new MyClass;
$r->MyProperty = 1; //Property MyProperty changed.
You can achieve this best by using a setter method.
class MyClass
{
private MyProperty;
public function setMyProperty($value)
{
$this->MyProperty = $value;
echo "MyProperty changed to " . $this -> MyProperty;
}
}
Now you simply call the setter instead of setting the value yourself.
$MyObject = new MyClass;
$MyObject -> setMyProperty(1);
This post is quite old, but I have to deal with the same issue and a typical class with multiple fields. Why using private properties ?
Here is what I implemented :
class MyClass {
public $foo;
public $bar;
function __set($name, $value) {
if(!property_exists('MyClass', $name)){ // security
return;
}
if ('foo' == $name) { // test on the property needed to avoid firing
// "change event" on every private properties
echo "Property " . $name . " modified";
}
$this->$name = $value;
}
}
$r = new MyClass();
$r->bar = 12;
$r->foo = 1; //Property foo changed.
var_dump($r);
/*
object(MyClass)[1]
public 'foo' => int 1
public 'bar' => int 12
*/
Using the magic method __set:
class Foo {
protected $bar;
public function __set($name, $value) {
echo "$name changed to $value";
$this->$name = $value;
}
}
$f = new Foo;
$f->bar = 'baz';
It may be a better and more conventional idea to use a good old-fashioned setter instead:
class Foo {
protected $bar;
public function setBar($value) {
echo "bar changed to $value";
$this->bar = $value;
}
}
$f = new Foo;
$f->setBar('baz');
I have an object that is returns as below.
print_r($all->getInfo());
//returns the following on browser.
User Object ( [_name:User:private] => Kanye, West [_email:User:private] => kanye#hotmail.com)
I would like to read the name and email and set it to two seperate variables like below.
$email = $all->getInfo()._email;
$name= $all->getInfo()._name;
Any help would be greatly appreciated.
You can do somtething like this to get access to private proprites.
class sampleClass {
private $property1 = 'name';
private $prooerty2 = 'email';
public function getProperty1(){
return $this->property1;
}
public function getProperty2(){
return $this->property2;
}
public function setProperty1($name){
}
$this->property1 = $name;
}
public function setProperty2($email){
$this->property2 = $email;
}
}
To get properties:
$classy = new sampleClass();
echo $classy->getProperty1;
echo $classy->getProperty2;
To set properties:
$classy->setProperty1('name1');
$classy->setProperty2('email2');
You can also use magic methods for __get and __set, more info.
If you want to access the values, you can either set them to public (which may not be the best solution depending on what you're trying to do), or create the __get() magic method
class foo
{
private $value = "foo";
private $value2 = "foo2";
public function __get($name)
{
switch ($name)
{
case "value";
return $this->$name;
default:
return null;
}
}
}
class bar
{
private $value = "bar";
}
$f = new foo;
$b = new bar;
echo "Result:" . $f->value . "<br/>";
echo "Result:" . $f->value2 . "<br/>";
echo "Result:" . $b->value . "<br/>";
this yeilds
Result:foo
Result:
E_ERROR : type 1 -- Cannot access private property bar::$value -- at line 31
Of course, you can also create a function such as get_name() but then you'd have to do that for every private property you want to access.
If you want to control what is and what is not accessible from the __get() method, you can add some logic. This way you know that if you are trying to access a property, it's in the __get() method and not have to try to figure out what the method name is.