Run method chain from a string value - php

I have a question about getting value from running a method from a string. While I am able to handle a single method call from a string I am curious how to call a chain of methods from a string.
For example. $project is an Object.
$method1 = "name";
$project->$method1; // It shows the valid results
$method2 = "get()->first()->name";
$project->get()->first()-name; // It shows the valid results
$project->$method2; // get a null result
Please help to find a way to make the $method2 work. And what happen if I have params inside those methods?
The reason here is I have made an array of customized methods. It can be run line by line, but I am thinking of a way to turn them into a loop, so it's more efficient. Put the methods in to a file then get values by looping to them.
Array = ["getvalue1()", "getvalue2()",...."getValuen()->anotherMethod()->value"]
Thanks,

If you want nested try something like this:
private function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
return $model->$method;
}, $model);
}
This will go through a chain of method calls as your described. If some of the chain (the last piece) is a property I think I once rigged up the following to handle it:
protected function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
try {
return $model->$method;
} catch (Exception $e) {
return $model->$method();
}
}, $model);
}
If you want to add params try replacing $model->method with:
call_user_func_array(
array($project, 'your_method'),
$params
);

Try this approach:
$method1 = 'name';
$project->{$method1}();

Run method from a string value
Use call_user_func() and call_user_func_array().
call_user_func_array() suits good if you are passing parameters
call_user_func_array(
array($project, 'your_method'),
$params
);
Chain function
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
Here is test
akshay#db-3325:/tmp$ cat test.php
<?php
class Testclass
{
private $str;
function __construct()
{
$this->str = new StdClass;
}
function addA()
{
$this->str->a='A';
return $this;
}
function addB()
{
$this->str->b='B';
return $this;
}
function get()
{
return $this->str;
}
}
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
$object = new Testclass();
// Output 1
print_r(chain_fun("addA()->addB()->get()", $object));
// Output 2
echo chain_fun("addA()->addB()->get()->a", $object);
?>
Output
akshay#db-3325:/tmp$ php test.php
stdClass Object
(
[a] => A
[b] => B
)
A

Related

How to unit test while loop in PHPUnit?

I have a Service class and a test for that, follow below:
Class
class MyCustomService
{
public function job()
{
while($this->getResponseFromThirdPartyApi()->data) {
// do some stuff...
}
return ...
}
protected function getResponseFromThirdPartyApi()
{
// Here do some curl and return stdClass
// data attribute is populated only on first curl request
}
}
Test mocking getResponseFromThirdPartyApi method
class MyCustomServiceTest
{
public function testJobImportingData()
{
$myCustomServiceMock = $this->getMockBuilder('MyCustomService')
->setMethods(array('getResponseFromThirdPartyApi'))
->getMock();
$myCustomServiceMock->expects($this->any())
->method('getResponseFromThirdPartyApi')
->willReturn($this->getResponseWithData());
$jobResult = $myCustomServiceMock->job();
// here some assertions on $jobResult
}
protected function getResponseWithData()
{
$response = new \stdClass;
$response->data = ['foo', 'bar'];
return $response;
}
}
How can I change getResponseWithData return after first call on MyCustomService while loop?
I've tried creating a custom flag on MyCustomServiceTest and checking on getResponseWithData, but fails once that mocked object doesn't call getResponseWithData method again on MyCustomServiceTest.
Any direction?
As Nico Haase suggested above, the path is to use callback.
After some research I achieved passing mock object reference to method mocked and checking flag, this results on:
class MyCustomServiceTest
{
public function testJobImportingData()
{
$myCustomServiceMock = $this->getMockBuilder('MyCustomService')
->setMethods(array('getResponseFromThirdPartyApi'))
->getMock();
$myCustomServiceMock->expects($this->any())
->method('getResponseFromThirdPartyApi')
->will($this->returnCallback(
function () use ($myCustomServiceMock) {
return $this->getResponseWithData($myCustomServiceMock)
}
));
$jobResult = $myCustomServiceMock->job();
// here some assertions on $jobResult
}
protected function getResponseWithData(&$myCustomServiceMock)
{
$response = new \stdClass;
if (isset($myCustomServiceMock->imported)) {
$response->data = false;
return $response;
}
$myCustomServiceMock->imported = true;
$response = new \stdClass;
$response->data = ['foo', 'bar'];
return $response;
}
}
Then while loop will be called only once and we be able to test without forever loop.

How to check returned value to which function it belogns

Say I have to similar function :
public function auth(){
return $someResponse;
}
public function collect(){
return $someOtherResponse
}
Question : When one of the response get passed to another class, is there any way to check which function returned the response ?
In a purely object-oriented way, wanting to attach information to a value is akin to wrapping it into a container possessing context information, such as:
class ValueWithContext {
private $value;
private $context;
public function __construct($value, $context) {
$this->value = $value;
$this->context = $context;
}
public value() {
return $this->value;
}
public context() {
return $this->context;
}
}
You can use it like this:
function auth()
{
return new ValueWithContext($someresponse, "auth");
}
function collect()
{
return new ValueWithContext($someotherrpesonse, "collect");
}
This forces you to be explicit about the context attached to the value, which has the benefit of protecting you from accidental renamings of the functions themselves.
As per my comment, using arrays in the return will give you a viable solution to this.
It will allow a way to see what has been done;
function auth()
{
return (array("auth" => $someresponse));
}
function collect()
{
return (array("collect" => $someotherrpesonse));
}
class myClass
{
function doSomething($type)
{
if (function_exists($type))
{
$result = $type();
if (isset($result['auth']))
{
// Auth Used
$auth_result = $result['auth'];
}
else if (isset($result['collect']))
{
// Collect used
$collect_result = $result['collect'];
}
}
}
}
It can also give you a way to fail by having a return array("fail" => "fail reason")
As comments say also, you can just check based on function name;
class myClass
{
function doSomething($type)
{
switch ($type)
{
case "auth" :
{
$result = auth();
break;
}
case "collect" :
{
$result = collect();
break;
}
default :
{
// Some error occurred?
}
}
}
}
Either way works and is perfectly valid!
Letting the two user defined functions auth() & collect() call a common function which makes a call to debug_backtrace() function should do the trick.
function setBackTrace(){
$backTraceData = debug_backtrace();
$traceObject = array_reduce($backTraceData, function ($str, $val2) {
if (trim($str) === "") {
return $val2['function'];
}
return $str . " -> " . $val2['function'];
});
return $traceObject;
}
function getfunctionDo1(){
return setBackTrace();
}
function getfunctionDo2(){
return setBackTrace();
}
class DoSomething {
static function callfunctionTodo($type){
return (($type === 1) ? getfunctionDo1() : getfunctionDo2());
}
}
echo DoSomething::callfunctionTodo(1);
echo "<br/>";
echo DoSomething::callfunctionTodo(2);
/*Output
setBackTrace -> getfunctionDo1 -> callfunctionTodo
setBackTrace -> getfunctionDo2 -> callfunctionTodo
*/
The above function would output the which function returned the response

How do you get value from private properties in PHP [duplicate]

It doesn't seem to work:
$ref = new ReflectionObject($obj);
if($ref->hasProperty('privateProperty')){
print_r($ref->getProperty('privateProperty'));
}
It gets into the IF loop, and then throws an error:
Property privateProperty does not exist
:|
$ref = new ReflectionProperty($obj, 'privateProperty') doesn't work either...
The documentation page lists a few constants, including IS_PRIVATE. How can I ever use that if I can't access a private property lol?
class A
{
private $b = 'c';
}
$obj = new A();
$r = new ReflectionObject($obj);
$p = $r->getProperty('b');
$p->setAccessible(true); // <--- you set the property to public before you read the value
var_dump($p->getValue($obj));
Please, note that accepted answer will not work if you need to get the value of a private property which comes from a parent class.
For this you can rely on getParentClass method of Reflection API.
Also, this is already solved in this micro-library.
More details in this blog post.
getProperty throws an exception, not an error. The significance is, you can handle it, and save yourself an if:
$ref = new ReflectionObject($obj);
$propName = "myProperty";
try {
$prop = $ref->getProperty($propName);
} catch (ReflectionException $ex) {
echo "property $propName does not exist";
//or echo the exception message: echo $ex->getMessage();
}
To get all private properties, use $ref->getProperties(ReflectionProperty::IS_PRIVATE);
In case you need it without reflection:
public function propertyReader(): Closure
{
return function &($object, $property) {
$value = &Closure::bind(function &() use ($property) {
return $this->$property;
}, $object, $object)->__invoke();
return $value;
};
}
and then just use it (in the same class) like this:
$object = new SomeObject();
$reader = $this->propertyReader();
$result = &$reader($object, 'some_property');
Without reflection, one can also do
class SomeHelperClass {
// Version 1
public static function getProperty1 (object $object, string $property) {
return Closure::bind(
function () use ($property) {
return $this->$property;
},
$object,
$object
)();
}
// Version 2
public static function getProperty2 (object $object, string $property) {
return (
function () use ($property) {
return $this->$property;
}
)->bindTo(
$object,
$object
)->__invoke();
}
}
and then something like
SomeHelperClass::getProperty1($object, $propertyName)
SomeHelperClass::getProperty2($object, $propertyName)
should work.
This is a simplified version of Nikola Stojiljković's answer

PHP static method recursive

Is it possible to do a recursion on a static method?
class Helpers {
public static function objectToArray($obj) {
if (is_object($obj)) {
$obj = get_object_vars($obj);
}
if (is_array($obj)) {
return array_map(__FUNCTION__, $obj);
}
else {
return $obj;
}
}
}
I'm getting this error when executed:
Severity: Warning
Message: array_map() expects parameter 1 to be a valid callback, function 'objectToArray' not found or invalid function name.
Thanks!
You can recursively call a static method. The
following code, for example, will work fine:
<?php
class MyClass{
public static function test($count)
{
if ($count < 10) {
$count++;
self::test($count);
echo $count;
}
}
}
MyClass::test(0);
Try this
return array_map(['Helpers', 'objectToArray'], $obj);
array_map permit callable type.
You could try it with magic constants
return array_map([__CLASS__, __METHOD__], $obj);
Or using self
return array_map([self, __METHOD__], $obj);
You should use:
return array_map('self::objectToArray', $obj);
// or
return array_map(array(self, 'objectToArray'), $obj);
Provide an alternative solution for you (encode the object to a json string, then decode it to an array):
class Helpers {
public static function objectToArray($obj) {
return json_decode(json_encode($obj), true);
}
}

Can I get the value of a private property with Reflection?

It doesn't seem to work:
$ref = new ReflectionObject($obj);
if($ref->hasProperty('privateProperty')){
print_r($ref->getProperty('privateProperty'));
}
It gets into the IF loop, and then throws an error:
Property privateProperty does not exist
:|
$ref = new ReflectionProperty($obj, 'privateProperty') doesn't work either...
The documentation page lists a few constants, including IS_PRIVATE. How can I ever use that if I can't access a private property lol?
class A
{
private $b = 'c';
}
$obj = new A();
$r = new ReflectionObject($obj);
$p = $r->getProperty('b');
$p->setAccessible(true); // <--- you set the property to public before you read the value
var_dump($p->getValue($obj));
Please, note that accepted answer will not work if you need to get the value of a private property which comes from a parent class.
For this you can rely on getParentClass method of Reflection API.
Also, this is already solved in this micro-library.
More details in this blog post.
getProperty throws an exception, not an error. The significance is, you can handle it, and save yourself an if:
$ref = new ReflectionObject($obj);
$propName = "myProperty";
try {
$prop = $ref->getProperty($propName);
} catch (ReflectionException $ex) {
echo "property $propName does not exist";
//or echo the exception message: echo $ex->getMessage();
}
To get all private properties, use $ref->getProperties(ReflectionProperty::IS_PRIVATE);
In case you need it without reflection:
public function propertyReader(): Closure
{
return function &($object, $property) {
$value = &Closure::bind(function &() use ($property) {
return $this->$property;
}, $object, $object)->__invoke();
return $value;
};
}
and then just use it (in the same class) like this:
$object = new SomeObject();
$reader = $this->propertyReader();
$result = &$reader($object, 'some_property');
Without reflection, one can also do
class SomeHelperClass {
// Version 1
public static function getProperty1 (object $object, string $property) {
return Closure::bind(
function () use ($property) {
return $this->$property;
},
$object,
$object
)();
}
// Version 2
public static function getProperty2 (object $object, string $property) {
return (
function () use ($property) {
return $this->$property;
}
)->bindTo(
$object,
$object
)->__invoke();
}
}
and then something like
SomeHelperClass::getProperty1($object, $propertyName)
SomeHelperClass::getProperty2($object, $propertyName)
should work.
This is a simplified version of Nikola Stojiljković's answer

Categories