PHP Method chaining with dynamic names - php

I was wondering if it's possible to create a method chaining using the values (or keys) of an array as the dynamic names of the methods.
For example, I have an array:
$methods = ['first', 'second', 'third']
Is it possible to create the following call ?
first()->second()->third();

This is untested. Something along the lines of:
$object = null; // set this to an initial object to call the methods on
foreach ($methods as $value)
{
$object = $object->$value();
}
Note that each method you call should return an object that has a method to be called next. If it's an object of the same class - then it can just return itself with each chainable method.

You can also use eval function.
Example:
$object = new SomeClass(); // first, second, third
$methods = ['first', 'second', 'third'];
$callStr = 'return $object->';
foreach($methods as $method){
$callStr.= $method . '()->';
}
$callStr = substr($callStr, 0, -2);
$callStr.= ';'; // return $object->first()->second()->third();
$result = eval($callStr); // return result of call - $object->first()->second()->third();

Related

How to dynamically declare return type of function in PHP 7

I created method to dynamically go through object methods and create array with anonymous functions that will behave as factories.
I have a problem how to dynamically declare return type of anonymous function. I couldn't find the right syntax and I'm not sure if it's even possible in PHP.
I would like to create something like this in simplified version:
$services = [];
$object_class = get_class($object);
$method_names = get_class_methods($object_class);
// go through all object methods
foreach ($method_names as $method_name) {
// get return type of this method
$method = new ReflectionMethod($object_class, $method_name);
$type = $method->getReturnType();
// use it as return type for this anonymous function (not working)
$services[$method_name] = function() use ($object, $method_name): $type {
return call_user_func([$object, $method_name]);
};
}
But I'm getting syntax error here.
I'm not sure if this is possible, even with all the string magic PHP can do. What you could do is something like:
$result = call_user_func...;
return gettype($result) === $type ? $result : null;
or throw some exceptions if they do not match.

Match function parameters from an associative array and function call in PHP

I have an associative array in the following form:
$params = array(
'paramName_4'=>'param_4',
'paramName_2'=>'param_2',
// ...,
'paramName_6'=>'param_6',
);
and I also have a function myFunction defined as:
public function myFunction($paramName_1, $paramName_2, $paramName_3, ....);
Does a a "parsing" function exist in PHP so that I can call function myFunction by matching the parameters (even if they are not sorted wrt the myFunction's parameter sequence)? In other words, can I do
my_magic(__NAMESPACE__.'\\myFunction', $params);
Does this "magic" function does exist? If not, how can I implement it?
You can implement it using reflection. Here's how:
// The input is the array of arguments and the function name
$arguments = array(....);
$functionName = __NAMESPACE__.'\\myFunction';
$reflector = new \ReflectionFunction($functioName);
$params = $reflector->getParameters();
$values = array();
foreach ($params as $param) {
$name = $param->getName();
$isArgumentGiven = array_key_exists($name, $arguments);
if (!$isArgumentGiven && !$param->isDefaultValueAvailable() {
die ("Parameter $name is mandatory but was not provided");
}
$values[$param->getPosition()] =
$isArgumentGiven ? $arguments[$name] : $param->getDefaultValue();
}
// You can now call the function:
call_user_func($functionName, $values);
Yes, you can use Reflection as per #Jon's example, but if the problem is just that the params aren't in the right order, why not just use ksort() or uksort() to put them in the right order.
Then you can use call_user_func_array(). Problem solved.
$params = array(....);
uksort($params, function($a,$b) {
//sort the params into the known order....
$sortOrder = array('param1','param2','param3','param4');
return (array_search($a, $sortOrder) > array_search($b, $sortOrder)) ? -1 : 1;
});
//now that $params is in the right order we can do this....
$retVal = call_user_func_array($func, $params);
I've hard-coded the param order here, because it's the most efficient way. If you are calling a function where you don't know the correct param order in advance, then yes, you'll need to use reflection. But I would think that's fairly unlikely (passing an unknown params list into an unknown function sounds like a goldmine for hackers)
Here is a library which does the argument resolving for a given function/method: ArgumentsResolver.

Sorting argument array for dynamically called method

I'm using reflection to dynamically call methods.
$object = new $class;
$reflector = new ReflectionMethod($class, $method);
$reflector->invokeArgs($object, $arguments);
The $arguments array looks like:
Array
(
[fooparam] => false
[id] => 238133
)
The method called could be:
class MyClass
{
public function myMethod ($id, $fooParam)
{
// Whatever
}
}
The problem is that everything comes from frontend designers, depending on data-* attributes, href... so $arguments array has arbitrary sorting.
How can I sort this array to match method parameters?
O maybe, is there a better solution? Named parameters?
Use ReflectionMethod::getParameters() to get a list of arguments and filter map them to their corresponding position, e.g.:
$sorted_args = array_map(function($param) use($arguments) {
$name = $param->getName();
if (!isset($arguments[$name]) && !$param->isOptional())
throw new BadMethodCallException("Argument '{$name}' is mandatory");
return isset($arguments[$name]) ? $arguments[$name] : $param->getDefaultValue();
}, $reflector->getParameters());
You could also use a simple foreach loop, it's up to you.
Then invoke the method with $sorted_args instead:
$reflector->invokeArgs($object, $sorted_args);

Dynamically call Class with variable number of parameters in the constructor

I know that it is possible to call a function with a variable number of parameters with call_user_func_array() found here -> http://php.net/manual/en/function.call-user-func-array.php . What I want to do is nearly identical, but instead of a function, I want to call a PHP class with a variable number of parameters in it's constructor.
It would work something like the below, but I won't know the number of parameters, so I won't know how to instantiate the class.
<?php
//The class name will be pulled dynamically from another source
$myClass = '\Some\Dynamically\Generated\Class';
//The parameters will also be pulled from another source, for simplicity I
//have used two parameters. There could be 0, 1, 2, N, ... parameters
$myParameters = array ('dynamicparam1', 'dynamicparam2');
//The instantiated class needs to be called with 0, 1, 2, N, ... parameters
//not just two parameters.
$myClassInstance = new $myClass($myParameters[0], $myParameters[1]);
You can do the following using ReflectionClass
$myClass = '\Some\Dynamically\Generated\a';
$myParameters = array ('dynamicparam1', 'dynamicparam2');
$reflection = new \ReflectionClass($myClass);
$myClassInstance = $reflection->newInstanceArgs($myParameters);
PHP manual: http://www.php.net/manual/en/reflectionclass.newinstanceargs.php
Edit:
In php 5.6 you can achieve this with Argument unpacking.
$myClass = '\Some\Dynamically\Generated\a';
$myParameters = ['dynamicparam1', 'dynamicparam2'];
$myClassInstance = new $myClass(...$myParameters);
I implement this approach a lot when function args are > 2, rather then end up with an Christmas list of arguments which must be in a specific order, I simply pass in an associative array. By passing in an associative array, I can check for necessary and optional args and handle missing values as needed. Something like:
class MyClass
{
protected $requiredArg1;
protected $optionalArg1;
public function __construct(array $options = array())
{
// Check for a necessary arg
if (!isset($options['requiredArg1'])) {
throw new Exception('Missing requiredArg1');
}
// Now I can just localize
$requiredArg1 = $options['requiredArg1'];
$optionalArg1 = (isset($options['optionalArg1'])) ? $options['optionalArg1'] : null;
// Now that you have localized args, do what you want
$this->requiredArg1 = $requiredArg1;
$this->optionalArg1 = $optionalArg1;
}
}
// Example call
$class = 'MyClass';
$array = array('requiredArg1' => 'Foo!', 'optionalArg1' => 'Bar!');
$instance = new $class($array);
var_dump($instance->getRequiredArg1());
var_dump($instance->getOptionalArg1());
I highly recommend using an associative array, however it is possible to use a 0-index array. You will have to be extremely careful when constructing the array and account for indices that have meaning, otherwise you will pass in an array with offset args and wreck havoc with your function.
You can do that using func_get_args().
class my_class {
function __construct( $first = NULL ) {
$params = func_get_args();
if( is_array( $first ) )
$params = $first;
// the $params array will contain the
// arguments passed to the child function
foreach( $params as $p )
echo "Param: $p\n";
}
}
function my_function() {
$instance = new my_class( func_get_args() );
}
echo "you can still create my_class instances like normal:";
$instance = new my_class( "one", "two", "three" );
echo "\n\n\n";
echo "but also through my_function:";
my_function( "one", "two", "three" );
Basically, you simply pass the result of func_get_args to the constructor of your class, and let it decide whether it is being called with an array of arguments from that function, or whether it is being called normally.
This code outputs
you can still create my_class instances like normal:
Param: one
Param: two
Param: three
but also through my_function:
Param: one
Param: two
Param: three
Hope that helps.
I've found here
Is there a call_user_func() equivalent to create a new class instance?
the example:
function createInstance($className, array $arguments = array())
{
if(class_exists($className)) {
return call_user_func_array(array(
new ReflectionClass($className), 'newInstance'),
$arguments);
}
return false;
}
But can somebody tell me if there is an example for classes with protected constructors?

Emulate ruby's inject() behavior in PHP

From this question here, I was writing an enum wrapper to have some methods that can be used with lambdas to somewhat emulate ruby's usage of blocks in enums.
class enum {
public $arr;
function __construct($array) {
$this->arr = $array;
}
function each($lambda) {
array_walk($this->arr, $lambda);
}
function find_all($lambda) {
return array_filter($this->arr, $lambda);
}
function inject($lambda, $initial=null) {
if ($initial == null) {
$first = array_shift($this->arr);
$result = array_reduce($this->arr, $lambda, $first);
array_unshift($this->arr, $first);
return $result;
} else {
return array_reduce($this->arr, $lambda, $initial);
}
}
}
$list = new enum(array(-1, 3, 4, 5, -7));
$list->each(function($a) { print $a . "\n";});
// in PHP you can also assign a closure to a variable
$pos = function($a) { return ($a < 0) ? false : true;};
$positives = $list->find_all($pos);
Now, how could I implement inject() as elegantly as possible?
EDIT: method implemented as seen above. Usage examples:
// inject() examples
$list = new enum(range(5, 10));
$sum = $list->inject(function($sum, $n) { return $sum+$n; });
$product = $list->inject(function($acc, $n) { return $acc*$n; }, 1);
$list = new enum(array('cat', 'sheep', 'bear'));
$longest = $list->inject(function($memo, $word) {
return (strlen($memo) > strlen($word)) ? $memo : $word; }
);
I'm not familiar with Ruby, but from the description, it seems similar to array_reduce.
mixed array_reduce ( array $input , callback $function [, mixed $initial = NULL ] )
array_reduce() applies iteratively the function function to the elements of the array input, so as to reduce the array to a single value.
In addition to "reduce", this operation is also sometimes called "fold"; in Mathematica:
Fold[f, init, {a, b, c, d}] == f[f[f[f[init, a], b], c], d]
The second form uses the first element of the collection as a the initial value (and skips that element while iterating).
This second form can be implemented this way:
//$arr is the initial array
$first = array_shift($arr);
$result = array_reduce($arr, $callback, $first);
Response to Mladen
The array functions in PHP cannot be used that way because they can only work with arrays, not arbitrary objects.
There are a few options here:
You could convert the object into an array prior to passing it to array_reduce. In practice, this doesn't have much value because the conversion consists of creating an array with the object properties as elements. This behavior can only be changed internally (writing a native extension).
You could have all your objects implement an interface with a method toArray that would have to be called priorly to passing it to array_reduce. Not a great idea, either.
You could implement a version of array_reduce that works with any Traversable object. This would be easy to do, but you couldn't put a Traversable type hint in the function declaration since arrays are not objects. With such a hint, every array would have to be encapsulated in an ArrayIterator object prior to the function call.

Categories