PHP, closures and extracting parameters - php

I'm not sure how to best title this so I thought I would post an example of what I'm trying to achieve. I've super-simplified this for now.
So first is the very basic class that will do the action.
abstract class AuditedSave extends AuditedSaveImplementation
{
public static function run($callback = null)
{
return new AuditedSaveImplementation($callback);
}
}
class AuditedSaveImplementation
{
public function __construct(Closure $closure)
{
echo ' - I ran before'; // point 1, $test = 0
$closure();
echo ' - i ran after!'; // point 2, $test = 1
}
}
Then the code that calls it.
$test = 0;
AuditedSave::run(function() use ($test)
{
$test = 1;
});
So between point 1 and 2 as commented, the closure runs and would set the value of $test to 1. However, I want to store the value of whatever is passed as the first parameter (in this case, $test) as a copy of what it was at the time of function calling - which will always run, the closure then modifies it (this is the part that can be variable), and then afterwards a comparison gets made and actions happen based on differences - which will always run.
However, in order to do this, I need to be able to access the $test variable within the __construct() method of AuditedSaveImplementation without knowing what it's called.
Is this possible at all?

You can use ReflectionFunction class getStaticVariables method to get params passed to closure via use. Like this:
class AuditedSaveImplementation
{
public function __construct(Closure $closure)
{
echo ' - I ran before'; // point 1, $test = 0
$closureReflection = new ReflectionFunction($closure);
$variables = $closureReflection->getStaticVariables();
var_dump($variables);
$closure();
echo ' - i ran after!'; // point 2, $test = 1
}
}
But as mentioned in the comments you should rethink how you do this.
It's not a good approach. Try to create a special class as was suggested in comments.

Related

Dynamically changing attribute in PHP

I have some abstract knowledge of OOP but this is the first time I am trying to code some OOP in PHP. I want to create a class that will have some attributes from construction but some attributes that dynamically change.
I am a little confused about all the terms (objects, classes, methods,...) so I do not know exactly what to search for. I made a simplified example below.
This is where I declared my class, that will accept 2 parameters on construction and calculate the third one, which is the higher number (please ignore that I don't check the type).
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
$this->p_max = max(array($this->p1, $this->p2));
}
}
Then I initialize the object and check the p_max:
$test = new test(1,2);
echo $test->p_max; // Prints 2
But if I change p1 and p2, the p_max won't change:
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max; // Prints 2 (I want 4)
How should I define the p_max inside my class to update every time I change p1 or p2? Is there a way without turning p_max into a method?
You can achieve this, using the magic __get method, which will be called, if a property of a class is accessed, but not defined. This is pretty hacky in my opinion, but works just as you want it to.
<?php
class test {
public function __construct($p1, $p2) {
$this->p1 = $p1;
$this->p2 = $p2;
}
public function __get($name) {
if ('p_max' === $name) {
return max(array($this->p1, $this->p2));
}
}
}
$test = new test(1,2);
echo $test->p_max; // Prints 2
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max; // Prints 4
Doing it this way, the max value will be calculated every time, you access this property.
Edit: Because the __get method will only be called for a property, which is not defined in the class itself, this wont work, if you assign the variable a value in the constructor or create it as property.
Edit2: I´d like to point out - again - that it´s pretty hacky to do it this way. For a way cleaner way, go with AbraCadaver´s answer. That´s how I personally would do it, too.
You don't really need to use a magic method, just a method that returns the calculated value:
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
}
public function p_max() {
return max($this->p1, $this->p2);
}
}
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max(); // call method
You can also accept optional arguments to p_max() to set new values and return the calculated value:
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
}
public function p_max($p1=null, $p2=null) {
$this->p1 = $p1 ?? $this->p1;
$this->p2 = $p2 ?? $this->p2;
return max($this->p1, $this->p2);
}
}
echo $test->p_max(3, 4); // call method
Also notice that max accepts multiple arguments so you don't have to specify an array.

static variable in php closure defined within a private class method

So I re-wrote this to hopefully be clearer about what I'm attempting. During some parsing of data someValue(s) associated with someCode(s) are received. The intent is to capture a particularValue of a someCode having the lowest precedence, irrespective of the order the someCode/someValue (s) received. The issue keeping this from working is that the closure is created every time the private function is called and the value of $precedenceOfCodeCaptured therefore is always reset to null. If I could keep the closure around then things might work as intended.
private function Foo($particularValue, $someValue, $someCode) {
switch ($someCode) {
case:
CODE1:
case:
CODE2:
$c = function () use ($someCode, $someValue) {
static $precedenceOfCodeCaptured = null;
$precedenceArray = array(
CODE2 => 1,
CODE1 => 2
);
if ((is_null($someValue))) {//first time the case statement matched because $someValue==null
$precedenceOfCodeCaptured = $precedenceArray[$someCode];
$particularValue = someValue;
} else if ($precedenceArray[$someCode] <= $precedenceOfCodeCaptured) {
$particularValue = someValue;
}
};
$c();
break;
...
}
}//end of private method
Every time Foo is called, you create a new function/closure, since you evaluate a function(), and that's what evaluating function() does!
If you'd like to have Foo() return the same function/closure each time, only evaluate it once. Perhaps use a singleton pattern, where you'll check if some higher-scoped variable that holds the function has been initialized, and initialize it if not.
For example:
$c = null;
private function Foo(){
if ($c == null) {
$c = function() use ( $whatever){
static $x = 0;
echo "x= $x";
$x++;
...
};
}
$c();
}//end of private method
(but please don't use globals in your actual code!)
Try this:
static $x = 0;
private function Foo() {
$c = function() use ( $whatever){
echo "x= $x";
self::$x++;
...
};
$c();
}
<?php
class example{
private function foo(){
static $_closure;
if( ! $_closure ){
$_closure = function(){
static $_state;
/* . . . */
};
}
$_closure();
}
}
Note that the closure is static (it is created on first use, and not again), as is the variable that holds state inside the closure.
However, as stated in other answers, this is "pointless" at best. $_closure should be another class method; $_state should be a class property (there is no need for static anything in the closure, in foo(), nor in the class). It is not "cleaner" or more readable or performant (in fact, the opposite is likely true) to use a closure like this.
It might make sense if you were doing something like:
returning the closure for use elsewhere
dynamically creating the function at runtime (i.e., based on foo() args)
something else dynamic and interesting
…but as it stands, this is simply the wrong approach. The tools to do this already exist.

Get two values returned from class

I am trying to understand object oriented PHP programming and wrote a small class to learn. I am having trouble understanding why its not working the way I intend. I have two variables inside the class method hello() $result and $test. I am trying to access the data that is stored in those two variables and print it to the screen. I know I can just call an echo inside the method but I am trying to get it to echo outside of it.
What I get printed to the screen is 88 it does not print out the second variable $test. I am trying to understand why thats happening. My lack of understanding probably shows in the code.
<?php
class simpleClass{
public function hello($result,$test) {
$result = 4+4;
$test = 10+5;
return $result;
return $test;
}
}
$a = new simpleClass;
echo $a->hello();
echo $a->hello($result, $test);
?>
you can return a list or array
public function hello($result,$test) {
$result = 4+4;
$test = 10+5;
return array($result, $test);
}
Use parameter referencing :
class simpleClass{
public function hello(&$result, &$test) {
$result = 4+4;
$test = 10+5;
}
}
$a = new simpleClass;
$result=''; $test='';
$a->hello($result, $test);
echo $result;
echo '<br>';
echo $test;
8
15
To clarify, when you add & to a function param, the value of that param - if you change or manipulate it inside the function - is handled back to your original variable passed. So you dont even have to return a result, and lets say pack it into an array or stdObject and unpack it afterwards. But you can still return something from the function, eg
$ok = $a->hello($result, $test);
as a flag to indicate if the calculation went right, for instance.
You cannot have multiple return statements in the same function because of the way return works. When a return statement is encountered the function stops executing there and then, passing back to the caller. The rest of the function never runs.
The complicated answer is to use a model.
class simpleResultTestModel {
public $result;
public $test;
public function __construct($result,$test) {
$this->result = $result;
$this->test = $test;
}
}
class simpleClass {
public function hello($result=4, $test=10) {
$result = $result+4;
$test = $test+5;
return new simpleResultTestModel($result, $test);
}
}
This way, you know simpleClass->hello() will always return an instance of simpleResultTestModel.
Also, I updated your hello method definition. You have two parameters, but don't actually apply them; I took the liberty of setting default values and then used them in the computation.
Usage:
$a = new simpleClass();
$first = $a->hello();
echo $first->result;
echo $first->test;
$second = $a->hello($first->result,$first->test);
echo $second->result;
echo $second->test;
I would try to stay away from passing by reference (especially within a class definition) unless you have a legitimate reason for doing so. It is bad practice when creating instances of classes (i.e. "sticky values" if you will).

PHP object method doesn't behave as I expect

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)

Same named function with multiple arguments in PHP

I started off OOP with Java, and now I'm getting pretty heavy into PHP. Is it possible to create multiples of a function with different arguments like in Java? Or will the interpreted / untyped nature of the language prevent this and cause conflicts?
Everyone else has answers with good code explanations. Here is an explanation in more high level terms: Java supports Method overloading which is what you are referring to when you talk about function with the same name but different arguments. Since PHP is a dynamically typed language, this is not possible. Instead PHP supports Default arguments which you can use to get much the same effect.
If you are dealing with classes you can overload methods with __call() (see Overloading) e.g.:
class Foo {
public function doSomethingWith2Parameters($a, $b) {
}
public function doSomethingWith3Parameters($a, $b, $c) {
}
public function __call($method, $arguments) {
if($method == 'doSomething') {
if(count($arguments) == 2) {
return call_user_func_array(array($this,'doSomethingWith2Parameters'), $arguments);
}
else if(count($arguments) == 3) {
return call_user_func_array(array($this,'doSomethingWith3Parameters'), $arguments);
}
}
}
}
Then you can do:
$foo = new Foo();
$foo->doSomething(1,2); // calls $foo->doSomethingWith2Parameters(1,2)
$foo->doSomething(1,2,3); // calls $foo->doSomethingWith3Parameters(1,2,3)
This might not be the best example but __call can be very handy sometimes. Basically you can use it to catch method calls on objects where this method does not exist.
But it is not the same or as easy as in Java.
Short answer: No. There can only be one function with a given name.
Longer answer: You can do this by creating a convoluted include system that includes the function with the right number of arguments. Or, better yet, you can take advantage of PHP allowing default values for parameters and also a variable amount of parameters.
To take advantage of default values just assign a value to a parameter when defining the function:
function do_something($param1, $param2, $param3 = 'defaultvaule') {}
It's common practice to put parameters with default values at the end of the function declaration since they may be omitted when the function is called and makes the syntax for using them clearer:
do_something('value1', 'value2'); // $param3 is 'defaultvaule' by default
You can also send a variable amount of parameters by using func_num_args() and func_get_arg() to get the arguments:
<?php
function dynamic_args() {
echo "Number of arguments: " . func_num_args() . "<br />";
for($i = 0 ; $i < func_num_args(); $i++) {
echo "Argument $i = " . func_get_arg($i) . "<br />";
}
}
dynamic_args("a", "b", "c", "d", "e");
?>
Following isn't possible with php
function funcX($a){
echo $a;
}
function funcX($a,$b){
echo $a.$b;
}
Instead do this way
function funcX($a,$b=null){
if ($b === null) {
echo $a; // even though echoing 'null' will display nothing, I HATE to rely on that
} else {
echo $a.$b;
}
}
funcX(1) will display 1, func(1,3) will display 13
Like everyone else said, it's not supported by default. Felix's example using __call() is probably the best way.
Otherwise, if you are using classes that inherit from each other you can always overload the method names in your child classes. This also allows you to call the parent method.
Take these classes for example...
class Account {
public function load($key,$type) {
print("Loading $type Account: $key\n");
}
}
class TwitterAccount extends Account {
public $type = 'Twitter';
public function load($key) {
parent::load($key,$this->type);
}
}
Then you can call them like so...
$account = new Account();
$account->load(123,'Facebook');
$twitterAccount = new TwitterAccount();
$twitterAccount->load(123);
And your result would be...
Loading Facebook Account: 123
Loading Twitter Account: 123
No this isn't possible, because PHP cannot infer from the arguments which function you want (you don't specify which types you expect). You can, however, give default values to arguments in php.
That way the caller can give different amounts of arguments. This will call the same function though.
Example is:
function test($a = true)
This gives a default of true if 0 arguments are given, and takes the calling value if 1 argument is given.
I know it's a bit old issue, but since php56 you can:
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
ref: http://php.net/manual/en/functions.arguments.php
Overloading is not possible in PHP but you can get around it to some extend with default parameter values as explained in other responses.
The limit to this workaround is when one wants to overload a function/method according to the parameter types. This is not possible in PHP, one need to test the parameter types yourself, or write several functions. The functions min and max are a good example of this : if there is one parameter of array type it returns the min/max of the array, otherwise it returns the min/max of the parameters.
I had the idea of something like:
function process( $param1 , $type='array' ) {
switch($type) {
case 'array':
// do something with it
break;
case 'associative_array':
// do something with it
break;
case 'int_array':
// do something with it
break;
case 'string':
// do something with it
break;
// etc etc...
}
}
I have got 2 methods, getArrayWithoutKey which will output all the entries of an array without supplying any key value. The second method getArrayWithKey will output a particular entry from the same array using a key value. Which is why I have used method overloading there.
class abcClass
{
private $Arr=array('abc'=>'ABC Variable', 'def'=>'Def Variable');
public function setArr($key, $value)
{
$this->Arr[$key]=$value;
}
private function getArrWithKey($key)
{
return $this->Arr[$key];
}
private function getArrWithoutKey()
{
return $this->Arr;
}
//Method Overloading in PHP
public function __call($method, $arguments)
{
if($method=='getArr')
{
if(count($arguments)==0)
{
return $this->getArrWithoutKey();
}
elseif(count($arguments)==1)
{
return $this->getArrWithKey(implode(',' , $arguments));
}
}
}
}
/* Setting and getting values of array-> Arr[] */
$obj->setArr('name', 'Sau');
$obj->setArr('address', 'San Francisco');
$obj->setArr('phone', 7777777777);
echo $obj->getArr('name')."<br>";
print_r( $obj->getArr());
echo "<br>";

Categories