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
i'm writing a php class that is like an orm.
I have a method, that can be called statically or instanciated, and it must work in both cases.
Can you see what's wrong.
Basically is an object called Model.
When created it creates a table based on the inherited class.
For example:
Podcast extends Model ....
There are some functions like this that needs to be called statically and dynamically.
for example:
$podcastList = Podcast::findAll($db);
I get all podcasts objects from DB without need to have a podcast object instanciated.
But i can also do:
$podcast = new Podcast($db)
$podcastList = $podcast->findAll(); //no db here.... passed before
$db is a class i wrote to make operation on Database. IT simply does with OOP, what mysql_* do with functions. I'm not using PDO, i may use in future, but now i use mysql_* :P
that are the incriminated functions
public static function findAll($db=NULL, $self=NULL) {
if($self == NULL) {
$self = new static($db);
} else {
$self = $this;
}
$self->tableName = "";
$self->db = NULL;
$is_static = !(isset($this) && get_class($this) == __CLASS__);
if($is_static) {
//die(__CLASS__ . "::" . __FUNCTION__ . " CALLED STATICALLY");
if(!$self->db) {
die(__CLASS__ . "::" . __FUNCTION__ . " CALLED STATICALLY AND DB IS NULL");
//It stops here!
}
$self->tableName = $self->genTableName();
} else {
$self->db = $this->db;
$self->tableName = $this->tableName;
}
$query = "SELECT * FROM {$self->tableName}";
$r = $self->db->exec($query);
if(!$r) {
die(__CLASS__ . ":Error " . __FUNCTION__ . " record: " . $self->db->getError());
}
if($self->db->countRows($r) == 0) {
return NULL;
}
$objects = array();
while($row = $self->db->fetch($r, DBF::FETCH_ASSOC)) {
$objectClass = __CLASS__;
$object = new $objectClass($this->db);
//TODO Do it dinamically indipendently of column name
$f = get_class_vars($objectClass);
foreach ($f as $field => $value) {
$chuncks = explode("_", $field);
if($chuncks[0] == "f") {
$object->{$field} = $row[$chuncks[2]];
}
}
$objects[] = $object;
}
return $objects;
}
public function __call($name, $arguments) {
if ($name === 'findAll'){
return static::findAll($arguments, $this);
}
}
Both are part of a class.
Thank you for the help !
There's a lot wrong with this code. More important than your many logic mistakes (why are you setting $self = $this, then $self->db = NULL, then $self->db = $this->db?) is that you are misunderstanding what it means to be able to call static functions dynamically in PHP. The object $this simply doesn't exist in a static method. The call $podcast->findAll() looks non-static, but it's still static.
To do what you want to do, here are some options:
leave the function static and call findAll($this->db, $tablename) as needed
put the function into the db class and call it with parameter tablename
EDIT:
The second in my list is how I would do it. This is because you already have to have a db object in your original example, and there is nothing in particular that makes the function's purpose only suited to Podcast objects and not to, say, any other object representing database rows.
//calling examples:
$podcastlist = $db->findAll('Podcast');
$podcast = new Podcast($db);
$podcastlist = $podcast->findAll();
public class db {
....
function findAll($classname, $tablename=NULL) {
if(!isset($tablename)) {
//let's pretend you put default table names as class constants
$tablename = get_constant($classname.'::DEFAULT_TABLE');
}
$query = "SELECT * FROM {$tableName}";
$r = $this->exec($query);
if(!$r) {
throw new Exception("Error " . __FUNCTION__ . " record: " . $this->getError());
}
if($this->countRows($r) == 0) {
return NULL;
}
$objects = array();
while($row = $this->fetch($r, DBF::FETCH_ASSOC)) {
$object = new $classname($this);
//the following is an easier way to do your original foreach
foreach($row as $field=>$value) {
if(property_exists($classname, "f_".$field)) {
$object->{'f_'.$field} = $value;
}
}
$objects[] = $object;
}
//something you forgot:
return $objects;
}
}
public class Podcast extends Model {
....
public function findAll($tablename=NULL) {
return $this->db->findAll(class_name($this), $tablename);
}
}
Consider the following code:
class Project
{
public $ProjectID;
}
class Work
{
public $WorkID;
}
public function insert($pData, $tableName)
{
//generate insert here
$pData->{$tableName . 'ID'} = $result->getId();
}
$p = new Project();
$w = new Work();
insert($w, 'Work');
insert($p, 'Project');
echo $p . ' -- ' . $w;
Now how would I go about setting the variable in a dynamic way? I'm building a data layer. The $pData->{$tableName . 'ID'} doesn't seem to work...
So, you want to dynamically call setters?
$y = new stdClass();
$y->prop1 = "something";
$targetProperty = "prop1";
$y->$targetProperty = "something else";
echo $y->prop1;
//Echos "something else"
That what you're looking for?
This is what you're looking for:
public function set_to_seven($p_data, $name)
{
$name = $name . 'ID';
$p_data->$name = 7;
}
The property name can be a variable. Just like functions:
$p = 'print_r';
$p('StackOverflow');
For future reference: if you need this statically, you're looking for variable variables,
public function set_to_seven($p_data, $name)
{
$name = $name . 'ID';
$p_data::$$name = 7;
}
You can set public properties by accessing them just like any other definition in the class.
$p = new Project();
$p->ProjectID = 5;
echo $p->ProjectID; // prints 5
http://php.net/manual/en/language.oop5.visibility.php
This worked for me.
class Project {
public $ProjectID;
}
function setToSeven($pData, $name) {
$pData->{$name . "ID"} = 7;
}
$p = new Project();
setToSeven($p, 'Project');
echo $p->ProjectID;
You just need to echo the variable or set up a toString function on the class to echo the class. To String works like this
class Project {
public $ProjectID;
public function __toString(){
return (string)$this->ProjectID;
}
}
function setToSeven($pData, $name) {
$pData->{$name . "ID"} = 7;
}
$p = new Project();
setToSeven($p, 'Project');
echo $p;
Can we dynamically create and initialize an object in PHP?
This is the normal code:
class MyClass{
var $var1 = null;
var $var2 = null;
.
.
public function __construct($args){
foreach($args as $key => $value)
$this->$key = $value;
}
}
---------------------
$args = ($_SERVER['REQUEST_METHOD'] == "POST") ? $_POST : $_REQUEST;
$obj = new MyClass($args);
The above code works fine. Please note that the names of REQUEST parameters are accurately mapped with the members of class MyClass.
But can we do something like this:
$class = "MyClass";
$obj = new $class;
If we can do like this, then can we initialize $obj by using $args.
According to this post, $obj = $class should work. But it does not work for me. I tried get_class_vars($obj). It threw an exception.
Thanks
It's more a comment, but I leave it here more prominently:
$class = "MyClass";
$obj = new $class($args);
This does work. See newDocs.
You have to overload some other magic methods:
__get (a method that gets called when you call object member)
__set (a method that gets called when you want to set object member)
__isset
__unset
Please see this codepad to see your code rewritten to work with what you want:
<?php
class MyClass{
var $properties = array();
public function __construct($args){
$this->properties = $args;
}
public function __get($name) {
echo "Getting '$name'\n";
if (array_key_exists($name, $this->properties)) {
return $this->properties[$name];
}
return null;
}
}
$args = array("key1" => "value1", "key2" => "value2");
$class = "MyClass";
$obj = new $class($args);
echo "key1:". $obj->key1;
?>
You can use Reflection to instanciate an object with parameters.
<?php
class Foo {
protected $_foo;
protected $_bar;
public function __construct($foo, $bar)
{
$this->_foo = $foo;
$this->_bar = $bar;
}
public function run()
{
echo $this->_foo . ' ' . $this->_bar . PHP_EOL;
}
}
$objectClass = 'Foo';
$args = array('Hello', 'World');
$objectReflection = new ReflectionClass($objectClass);
$object = $objectReflection->newInstanceArgs($args);
$object->run();
See Reflection on php manual.
I've looked at similar questions like this, and none of the solutions offered within them seems to answer my question.
This is the code I have so far (just learning OOP):
<?php
class page {
var $ot;
function begin(){
$this->ot = '';
}
function finish(){
echo $this->ot;
}
class forms extends page {
function __construct($form_action, $form_type){
$this->ot .= '<form action=' . $form_action . ' method=' . $form_type . ' />';
}
function create_input($type, $name){
$this->ot .= '<input type="' . $type . '" name="' . $name . '" /><br />';
}
function create_submit($value){
$this->ot .= '<input type="submit" value="' . $value . '" />';
}
function __destruct(){
$this->ot .= '</form>';
}
}
class labels extends page {
function create_label($label){
$this->ot .= '<label>' . $label . ' </label>';
}
}
$page = new page();
$page->begin();
$newform = new forms('/class_lib.php', 'GET');
$newlabels = new labels();
$newlabels->create_label('Username:');
$newform->create_input('text', 'username');
$newlabels->create_label('Password:');
$newform->create_input('password', 'password');
$page->finish();
?>
For some reason, this code does not output anything to the browser. However, if I change the child classes forms and labels to echo their output instead of storing it in the parent variable the code seems to spring to life and works as intended.
Excuse my ignorance as I am new to OOP.
Thanks!
$ot is an object-property. This means, that every object of the class page or any subclass has its own "version" of $ot. Now you instanciate some objects and set some values, but at the end, when you call $page->finish(); $page->ot is empty anyway.
In OOP you have the classes (types) and instances of the classes (objects). The $ot property is what you call an instance variable, it belongs to the instances (objects) you create and is not a property of the class itself.
By making forms a subclass of page, you get what you call a "is a" relationship between the classes. That means that forms will inherit the structure of the page class. Modifying the property of a subclass object will not affect the superclass objects or any other object in this case.
When you first create a page object, that object has a $ot property. When you create a object of type forms, that object has its own $ot property.
To understand the concepts of OOP I would recommend you to read some tutorials. You could start by reading the Class, Instance and Inheritance parts of this wikipedia article:
http://en.wikipedia.org/wiki/Object-oriented_programming
The objects are not really connected.
You create three new objects, but they never reference each other.
Try this
// Create a new form
$newform = new forms('/class_lib.php', 'GET');
$newform->create_input('text', 'username');
$newform->create_input('password', 'password');
// Output the form, it can use the finish() method because it extends page
$newform->finish();
This will work and output the <input> elements, but your label class isn't plugged in to the $newForm to do anything, its just created and is completely separate.
EDIT - bored this evening....
You will need PHP5 to run this, its not perfect, but its a good start! I have defined the following an interface called renderable and classes called element, input, label and form
// An interface describes the methods that a class must use
interface renderable
{
// Any classes that implement the renderabe interface must define a method called render()
function render();
}
// This abstract class can never be created, so you can never do new element(), it implements renderable
abstract class element implements renderable
{
// Set up some variables for all elemnts
var $attribs = array();
var $name = "";
var $type = "";
// The construct for a element needs a type and a name
function __construct($type, $name)
{
$this->name = $name;
$this->type = $type;
}
// Set an attribute for the element
function setAttribute($name, $value)
{
$this->attribs[$name] = $value;
}
// Get the name of this element
function getName()
{
return $this->name;
}
// The render function outputs an element
function render()
{
// Output the start of the element eg <input
echo "<" . $this->type . " ";
// each attribute eg class='blue'
foreach($this->attribs as $name => $value)
echo " " . $name . "='" . $value ."' ";
// end the element
echo " />";
echo "<br />";
}
}
// The input element extends element but is not abstract
class input extends element
{
// Nothing is overridden here from the parent class element
}
// The label element extends element but is not abstract
class label extends element
{
// Define a new var called label, this is special for the label element
var $label = "";
// Override the contruct for element to only accept a name, this
// is because the label element type will always be label
function __construct($name)
{
$this->name = $name;
$this->type = "label";
}
// Set the label var
function setLabel($label)
{
$this->label = $label;
}
// Override the render function, this means that label has its own render function
// and does not use the function from the abstract class element
function render()
{
echo "<" . $this->type . " ";
foreach($this->attribs as $name => $value)
echo " " . $name . "='" . $value ."' ";
echo " >";
// Here the special label content is displayed
echo $this->label;
echo "</label>";
}
}
// A form extends element
class form extends element
{
// A form has some new vars
var $elements = array();
var $labels = array();
var $action;
var $method;
// Override the contruct and use name, action and method
// There are default values for action and method so they are not required
function __construct($name, $action = "/", $method = "GET")
{
$this->name = $name;
$this->type = "form";
$this->action = $action;
$this->method = $method;
}
// Add a new element to the form along with its label
function appendElement($element, $label)
{
// Add these to an array inside this class
$this->elements[$element->getName()] = $element;
$this->labels[$label->getName()] = $label;
}
// Override the render function
function render()
{
// Output the form's start along with the method and action
echo '<' . $this->type. ' ' . 'action="' . $this->action . '" method="' . $this->method . '" />';
// Iterate over the array of elments and render each one
foreach($this->elements as $name => $ele)
{
// Render the label for the current element
$this->labels[$name]->render();
// Render the element
$ele->render();
}
// End the form
echo "</form>";
}
}
// Create form with name, action and method
$form = new form("login", "/login.php", "POST");
// Create input username
$ele = new input("input", "username");
// Set type
$ele->setAttribute("type", "text");
// Set a class
$ele->setAttribute("class", "blue");
// Create a label for the username long with its content
$label = new label("username");
$label->setLabel("Username: ");
// Add the username element and its label
$form->appendElement($ele, $label);
// Repeat for password
$ele = new input("input", "password");
$ele->setAttribute("type", "password");
$label = new label("password");
$label->setLabel("Password: ");
$form->appendElement($ele, $label);
// Render the form
$form->render();
Since you're learning OOP, it's time to learn about Abstract Classes (PHP Manual)!
An abstract class is a sort of skeleton class that defines a series of generic functions. An abstract class can never be instantiated (i.e., you cannot call new AbstractClass), but can be extended by other classes. This allows us to define something generic and repeatable, say, and HTML Element, and then extend that to specific HTML elements as time goes on. Here is a sample implementation of that concept.
WARNING: I am not saying that this implementation is a great idea; learning purposes only!
First, some abstract classes to define how this stuff ought to work.
abstract class HTMLWriter
{
protected $html = '';
protected $tagName = null;
protected $selfClosing = false;
protected $elements = array();
protected $attributes = array();
protected $closed = false;
abstract public function __construct();
public function addElement(HTMLWriter $element)
{
if ($this->closed || $this->selfClosing) {
return;
}
$element->close(); // automatic!
$this->elements[] = $element->write();
}
public function addElements() {
foreach (func_get_args() as $arg) {
if ($arg instanceof HTMLWriter) {
$this->addElement($arg);
}
}
}
public function addAttribute($name, $value)
{
return $this->attributes[$name] = $value;
}
public function write()
{
if (!$this->closed) {
$this->close();
}
return $this->html;
}
public function close()
{
$this->closed = true;
$this->html = '<' . $this->tagName;
foreach ($this->attributes AS $attr => $val) {
$this->html .= ' ' . $attr . '="' . $val . '"';
}
if ($this->selfClosing) {
$this->html .= '/>';
return;
}
$this->html .= '>';
foreach($this->elements as $elem) {
$this->html .= $elem;
}
$this->html .= '</' . $this->tagName . '>';
}
}
abstract class HTMLWriterWithTextNodes extends HTMLWriter
{
//abstract public function __construct();
public function addText($text)
{
$this->elements[] = htmlentities($text);
}
public function addTextRaw($text)
{
$this->elements[] = $text;
}
}
And then the concrete implementations of those classes:
note: a concrete class is any non-abstract class, although this term loses its meaning when applied to classes that are not extensions of abstract classes.
class Form extends HTMLWriter
{
public function __construct($action, $method, $can_upload = false)
{
$this->tagName = 'form';
$this->addAttribute('action', $action);
$this->addAttribute('method', $method);
if ($can_upload) {
$this->addAttribte('enctype','multipart/form-data');
}
}
}
class Input extends HTMLWriter
{
public function __construct($type, $name, $id = null)
{
$this->tagName = 'input';
$this->selfClosing = true;
$this->addAttribute('type', $type);
$this->addAttribute('name', $name);
if (!is_null($id)) {
$this->addAttribute('id', $id);
}
}
// overrides
public function addElement()
{
return false;
}
}
class Label extends HTMLWriterWithTextNodes
{
public function __construct($labelText = null, $for = null)
{
$this->tagName = 'label';
if (!is_null($labelText)) {
$this->elements[] = $labelText;
}
if (!is_null($for)) {
$this->addAttribute('for', $for);
}
}
}
class GenericElement extends HTMLWriterWithTextNodes
{
public function __construct($tagName, $selfClosing = false)
{
if (empty($tagName)) {
$this->closed = true;
$this->html = '';
return;
}
$this->tagName = $tagName;
$this->selfClosing = (bool)$selfClosing;
}
}
Finally, let's instantiate and use our new classes
$form = new Form('/class_lib.php','get');
$username = new Input('text','username','username');
$password = new Input('password','password','password');
$submit = new Input('submit','login');
$submit->addAttribute('value','login');
$ulabel = new Label('Username: ', 'username');
$plabel = new Label('Password: ','password');
$br = new GenericElement('br',true);
$form->addElements(
$ulabel, $username, $br,
$plabel, $password, $br,
$submit
);
echo $form->write();
Output:
<form action="/class_lib.php" method="get"><label for="username">Username: </label><input type="text" name="username" id="username"/><br/><label for="password">Password: </label><input type="password" name="password" id="password"/><br/><input type="submit" name="login" value="login"/></form>
Hooray for abstract classes!