I am trying to unit-test some of my code and it would be easier to just call my setters dynamically based on some variables. Unfortunately my approach does not work as expected and I couldn't find more information regarding on how to do that.
I have one variable which always is a string. It is used as property name and together with the "set" keyword it should result in "setSomething" or "setSomethingElse".
I already tried
$obj->set{$property}($value);
// or
$obj->set$property($value);
But those do not seem to work.
Maybe someone of you pro's know the right approach ;)!
You need to make the entire method name a variable, or enclose the whole name in {} e.g.
class test {
public $Something;
public $SomethingElse;
function setSomething($value) {
$this->Something = $value;
}
function setSomethingElse($value) {
$this->SomethingElse = $value;
}
}
$property = "Something";
$t = new test;
$setter = "set$property";
$t->$setter(4);
echo $t->Something;
$property = "SomethingElse";
$t->{"set$property"}(8);
echo $t->SomethingElse;
Output
4
8
Demo on 3v4l.org
Related
I'm coming from a .Net background and I'm trying to wrap my brain around a programming pattern that I'm used to but in PHP.
I've got a class with an associative array property. I'd like to "elevate" some, but not all, of the associative array keys to class-level properties. In C# I'd normally do something like this:
//C# Code
class MyClass{
private Dictionary<string, string> attributes = new Dictionary<string,string>();
//Get/Set the ID from the private store
public string ID{
get { return (attributes.ContainsKey("ID") ? attributes["ID"] : "n/a"); }
set { attributes.Add("ID", value); }
}
}
This allows my object to control default values for missing properties. In PHP I couldn't find any way to do this directly. My first workaround was to just use functions:
//PHP Code
class MyClass{
private $attributes = array();
//Get the ID
public function getID(){
return (array_key_exists('ID', $this->attributes) ? $this->attributes['ID'] : 'n/a');
}
//Set the ID
public function setID($value){
$this->attributes['ID'] = $value;
}
}
This works although the calling syntax is slightly different and I've got two methods per property. Also, the code that is consuming these objects is currently inspecting object variables so functions wouldn't be found.
Then I started going down the magic method paths of __set and __get on the object itself and just switch case on the $name that's passed in and setting/getting my local variables. Unfortunately these methods don't get invoked if you modify the underlying array directly.
So my question is, is it possible in PHP to have a class-level property/variable that doesn't get calculated until it gets used?
PHP doesn't have properties as C# programmers would understand the concept, so you'll have to use methods as the getter and setter, but the principle is exactly the same.
class MyClass {
private $attributes = array ();
public function getSomeAttribute () {
if (!array_key_exists ('SomeAttribute', $this -> attributes)) {
// Do whatever you need to calculate the value of SomeAttribute here
$this -> attributes ['SomeAttribute'] = 42;
}
return $this -> attributes ['SomeAttribute'];
}
// or if you just want a predictable result returned when the value isn't set yet
public function getSomeAttribute () {
return array_key_exists ('SomeAttribute', $this -> attributes)?
$this -> attributes ['SomeAttribute']:
'n/a';
}
public function setSomeAttribute ($value) {
$this -> attributes ['SomeAttribute'] = $value;
}
}
You essentially got the basic ideas right with your implementation, but it does mean a lot of "boilerplate" code. In theory you can avoid a lot of that with __get and __set, but I'd strongly advise against those as they can lead to epic amounts of confusion and nasty logical tangles like the "what happens if the value is set within the class instead of from outside?" issue that you've run into.
In PHP You can do something like this:
<?php
class MyClassWithoutParams{
function test(){
return $this->greeting . " " . $this->subject;
}
}
$class = new MyClassWithoutParams();
$class->greeting = "Hello";
$class->subject = "world";
echo $class->greeting . " " . $class->subject . "<br />"; //Hello World
echo $class->test(). "<br />"; //Hello World
?>
You don't need to define any property, getter or setter to use properties. Of course it is better to do so, because it makes the code easier to read and allows autocomplete-functions of eclipse (or whatever IDE) to understand what you are looking for - but if you are just looking for the laziest way to implement an entity, that would work as well.
A more common approach would be to use an associative array instead of a class - but on arrays you cant define methods.
<?php
$array = new array();
$array["greeting"] = "Hello";
$array["subject"] = "World";
echo $array["greeting"] . " " . $array["subject"]; //Output: Hello World
?>
I can't quite understand why the output of this code is '1'.
My guess is that php is not behaving like most other OO languages that I'm used to, in that the arrays that php uses must not be objects. Changing the array that is returned by the class does not change the array within the class. How would I get the class to return an array which I can edit (and has the same address as the one within the class)?
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function getArr()
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = $t->getArr();
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
EDIT: I expected the output to be 0
You have to return the array by reference. That way, php returns a reference to the array, in stead of a copy.
<?php
class Test
{
public $arr;
public function __construct()
{
$this->arr = array();
}
public function addToArr($i)
{
$this->arr[] = $i;
}
public function & getArr() //Returning by reference here
{
return $this->arr;
}
}
$t = new Test();
$data = 5;
$t->addToArr($data);
$tobj_arr = &$t->getArr(); //Reference binding here
unset($tobj_arr[0]);
$tobj_arr_fresh = $t->getArr();
echo count($tobj_arr_fresh);
?>
This returns 0.
From the returning references subpage:
Unlike parameter passing, here you have to use & in both places - to
indicate that you want to return by reference, not a copy, and to
indicate that reference binding, rather than usual assignment, should
be done
Note that although this gets the job done, question is if it is a good practice. By changing class members outside of the class itself, it can become very difficult to track the application.
Because array are passed by "copy on write" by default, getArr() should return by reference:
public function &getArr()
{
return $this->arr;
}
[snip]
$tobj_arr = &$t->getArr();
For arrays that are object, use ArrayObject. Extending ArrayObject is probably better in your case.
When you unset($tobj_arr[0]); you are passing the return value of the function call, and not the actual property of the object.
When you call the function again, you get a fresh copy of the object's property which has yet to be modified since you added 5 to it.
Since the property itself is public, try changing:
unset($tobj_arr[0]);
To: unset($t->arr[0]);
And see if that gives you the result you are looking for.
You are getting "1" because you are asking PHP how many elements are in the array by using count. Remove count and use print_r($tobj_arr_fresh)
I have the following function:
<?php
class Test{
function myFunction(){
return array('first','second','third');
}
}
?>
And I can print out the elements of the array:
$var=new Test();
$varr=$var->myFunction();
print($varr[1]);
Is there a way to condense this statement so I don't have to assign $var->myFunction() to a second variable (in this case $varr)?
PHP does not support this very well (as of PHP 5.3) as Tim Cooper already highlighted. So you need to think twice if you really need to have this compacted.
You can do things quite dynamically e.g. by return an ArrayObject instead of an array:
class Test
{
function myFunction()
{
return new ArrayObject(array('first','second','third'), 3);
}
}
$var = new Test();
print($var->myFunction()->{1});
Which will decorate the array data with some additional methods and ways of accessing. Another way would be for functions w/o parameter to fool the PHP parser and offer a property instead of a function dynamically:
class Test
{
function myFunction()
{
return array('first','second','third');
}
public function __get($name)
{
return $this->$name();
}
}
$var = new Test();
print($var->myFunction[1]);
But I don't know if this is really useful in an application.
So check your motivation why you want to compact the code and then decide on your own.
In PHP 5.4 you'll be able to do:
$varr=$var->myFunction()[1];
Until then, using list might help out:
list(,$varr) = $var->myFunction();
Another solution is to modify your method to accept an optional index of the item to return:
function myFunction($index = null){
$arr = array('first','second','third');
return $index == null ? $arr : $arr[$index];
}
$varr = $var->myFunction(1);
I know you can assign a function's return value to a variable and use it, like this:
function standardModel()
{
return "Higgs Boson";
}
$nextBigThing = standardModel();
echo $nextBigThing;
So someone please tell me why the following doesn't work? Or is it just not implemented yet? Am I missing something?
class standardModel
{
private function nextBigThing()
{
return "Higgs Boson";
}
public $nextBigThing = $this->nextBigThing();
}
$standardModel = new standardModel;
echo $standardModel->nextBigThing; // get var, not the function directly
I know I could do this:
class standardModel
{
// Public instead of private
public function nextBigThing()
{
return "Higgs Boson";
}
}
$standardModel = new standardModel;
echo $standardModel->nextBigThing(); // Call to the function itself
But in my project's case, all of the information stored in the class are predefined public vars, except one of them, which needs to compute the value at runtime.
I want it consistent so I nor any other developer using this project has to remember that one value has to be function call rather then a var call.
But don't worry about my project, I'm mainly just wondering why the inconsistency within PHP's interpreter?
Obviously, the examples are made up to simplify things. Please don't question "why" I need to put said function in the class. I don't need a lesson on proper OOP and this is just a proof of concept. Thanks!
public $nextBigThing = $this->nextBigThing();
You can only initialize class members with constant values. I.e. you can't use functions or any sort of expression at this point. Furthermore, the class isn't even fully loaded at this point, so even if it was allowed you probably couldn't call its own functions on itself while it's still being constructed.
Do this:
class standardModel {
public $nextBigThing = null;
public function __construct() {
$this->nextBigThing = $this->nextBigThing();
}
private function nextBigThing() {
return "Higgs Boson";
}
}
You can't assign default values to properties like that unless that value is of a constant data type (such as string, int...etc). Anything that essentially processes code (such as a function, even $_SESSION values) can't be assigned as a default value to a property. What you can do though is assign the property whatever value you want inside of a constructor.
class test {
private $test_priv_prop;
public function __construct(){
$this->test_priv_prop = $this->test_method();
}
public function test_method(){
return "some value";
}
}
class standardModel
{
// Public instead of private
public function nextBigThing()
{
return "Higgs Boson";
}
}
$standardModel = new standardModel(); // corection
echo $standardModel->nextBigThing();
I'm not sure if this is a trivial questions but in a PHP class:
MyClass:
class MyClass {
public $var1;
public $var2;
constructor() { ... }
public method1 () {
// Dynamically create an instance variable
$this->var3 = "test"; // Public....?
}
}
Main:
$test = new MyClass();
$test->method1();
echo $test->var3; // Would return "test"
Does this work?? How would I get this to work? Ps. I wrote this quickly so please disregard any errors I made with setting up the class or calling methods!
EDIT
What about making these instance variables that I create private??
EDIT 2
Thanks all for responding - Everyone is right - I should have just tested it out myself, but I had an exam the next morning and had this thought while studying that I wanted to check to see if it worked. People keep suggesting that its bad OOP - maybe but it does allow for some elegant code. Let me explain it a bit and see if you still think so. Here's what I came up with:
//PHP User Model:
class User {
constructor() { ... }
public static find($uid) {
$db->connect(); // Connect to the database
$sql = "SELECT STATEMENT ...WHERE id=$uid LIMIT 1;";
$result = $db->query($sql); // Returns an associative array
$user = new User();
foreach ($result as $key=>$value)
$user->$$key = $value; //Creates a public variable of the key and sets it to value
$db->disconnect();
}
}
//PHP Controller:
function findUser($id) {
$User = User::find($id);
echo $User->name;
echo $User->phone;
//etc...
}
I could have just put it in an associative array but I can never correctly name that array something meaningful (ie. $user->data['name'] ... ugly.) Either way you have to know what is in the database so I do not really understand what the argument is that its confusing, especially since you can just var dump objects for debugging.
Why dont you just write the code and see for yourself?
<?php
class Foo
{
public function __construct()
{
$this->bar = 'baz';
}
}
$foo = new Foo;
echo $foo->bar; // outputs 'baz'
and
var_dump($foo);
gives
object(Foo)#1 (1) {
["bar"] => string(3) "baz"
}
but
$r = new ReflectionObject($foo);
$p = $r->getProperty('bar');
var_dump($p->isPublic());
will throw an Exception about 'bar' being unknown, while
$r = new ReflectionObject($foo);
$p = $r->getProperties();
var_dump($p[0]->isPublic());
will return true.
Now, should you do this type of assignment? Answer is no. This is not good OOP design. Remember, OOP is about encapsulation. So, if bar is describing some public property of the class, make it explicit and declare it in your class as public $bar. If it is supposed to be private declare it as private $bar. Better yet, dont use public properties at all and make them protected and provide access to them only through getters and setters. That will make the interface much more clearer and cleaner as it conveys what interaction is supposed to be possible with an object instance.
Assigning properties on the fly here and there across your code, will make maintaining your code a nightmare. Just imagine somewhere along the lifecylce of Foo someone does this:
$foo = new Foo;
$foo->monkey = 'ugh';
echo $foo->monkey; // outputs 'ugh'
Now, from looking at the class definition above, there is absolutely no way, a developer can see there is now a monkey patched into Foo. This will make debugging a pain, especially if code like this is frequent and distributed across multiple files.
Yes that will indeed work. Auto-created instance variables are given public visibility.
yes that works as you'd hope/expect.
I you wanted to make private variables on the fly you could use php magic functions to emulate this, e.g
MyClass
<?php
class MyClass {
public $var1;
public $var2;
private $data = array();
public function __get($key) {
// for clarity you could throw an exception if isset($this->data[$key])
// returns false as it is entirely possible for null to be a valid return value
return isset($this->data[$key]) ? return $this->data[$key] : null;
}
public function __set($key, $value) {
$this->data[$key] = $value;
}
}
?>
Main
<?php
$test = new MyClass();
$test->myVar = 'myVar is technically private, i suppose';
echo $this->myVar; // 'myVar is technically private
?>
Although these dynamically created variables are technically private, they are infact publicly accessible... i cannot image the purpose for wanting to dynamically create private instance variables. I would question your design.
Did you try it?
It is possible but you might get strict errors. If you dynamically need to create these variables, you are probably doing something wrong.
You should either change this into a function:
function var($no) { .. }
or use __get (http://ca.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members)