Weird problem with dynamic method invocation - php

this time, I'm facing a really weird problem. I've the following code:
$xml = simplexml_load_file($this->interception_file);
foreach($xml->children() as $class) {
$path = str_replace('__CLASS_DIR__',CLASS_DIR,$class['path']);
if(!is_file($path)) {
throw new Exception('Bad configuration: file '.$path.' not found');
}
$className = pathinfo($path,PATHINFO_FILENAME);
foreach($class as $method) {
$method_name = $method['name'];
$obj = new $className();
var_dump(in_array($method_name,get_class_methods($className)));exit;
echo $obj->$method_name();### not a method ???
}
}
As you can see, I get the class name and method name from an XML file.
I can create an instance of the class without any problem. The var_dump at the end returns true, that means $method_name (which has 2 optional parameters) is a method of $className.
BUT, and I am pretty sure the syntax is correct, when I try: $obj->$method_name() I get:
Fatal error: Method name must be a string
If you have any ideas, pleaaaaase tell me :)
Thanks in advance,
Rolf

The issue you are having is probably that $method_name is not a string, but it contains a method to convert it to a string (__toString()).
As in_array by default don't do strict type comparisons you will find that $method_name is probably coveted to a string and then compared with the method names, which would explain why the var_dump outputs true.
You should be able to confirm this by checking the type of $method_name
echo gettype($method_name);
If it isn't a string the solution is to case the variable to a string and then use that to call the function.
$obj->{(string)$method_name}();

It's better to use the call_user_func function instead of $obj->$method_name() to call the method.
echo call_user_func(array($className, $method_name));

Related

PHPUnit test function with value passed by reference and a returned value

Hi all I need to test a piece of code that call a function of another class that I can't edit now.
I need only to test It but the problem is that this function has a values passed by reference and a value returned, so I don't know how to mock It.
This is the function of column class:
public function functionWithValuePassedByReference(&$matches = null)
{
$regex = 'my regex';
return ($matches === null) ? preg_match($regex, $this->field) : preg_match($regex, $this->field, $matches);
}
This is the point where is called and where I need to mock:
$matches = [];
if ($column->functionWithValuePassedByReference($matches)) {
if (strtolower($matches['parameters']) == 'distinct') {
//my code
}
}
So I have tried
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->willReturn(true);
If I do this return me error that index parameters doesn't exist obviously so I have tried this:
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturn(true);
But same error, how can I mock that function?
Thanks
You can use ->willReturnCallback() to modify the argument and also return a value. So your mock would become like this:
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturnCallback(function(&$matches) {
$matches = 'foo';
return True;
});
In order for this to work, you will need to turn off cloning the mock's arguments when you build the mock. So your mock object would be built like so
$this->columnMock = $this->getMockBuilder('Column')
->setMethods(['functionWithValuePassedByReference'])
->disableArgumentCloning()
->getMock();
This really is code smell, btw. I realize that you stated that you can't change the code that you are mocking. But for other people looking at this question, doing this is causing side effects in your code and can be a source of very frustrating to fix bugs.

Reference to PHP method without using a string

Suppose I have the following PHP code:
class Foo {
function getBar() {
return 1;
}
}
function check( Foo $foo ) {
if ( $foo->getBar() == 1 ) {
// here could be more code ...
return 'Oh no, there was an error in class' .
get_class( $foo ) . ', method ' .
'getBar';
}
}
The last string in check bothers me because if Foo::bar gets renamed by a refactoring tool, the error message will be wrong. Is there any way to get around this without using a string somewhere?
You can use __METHOD__ to get the name of the current method.
But to get reference to other method that would allow you some kind of automatic refactoring - no, it's not possible in php.
Can be done by using method_exists()
class Foo {
function getBar() {
return 1;
}
}
function check( Foo $foo , $method = 'getBar') {
if (!method_exists($foo, $method) ) {
// here could be more code ...
return 'Oh no, there was an error in class' .
get_class( $foo ) . ', method ' .
$method;
}
}
It is not possible in PHP per se, but you can implement such a feature. One possible implementation would work as follows: somewhere the file path, class name, method name and some kind of a description of where and what should match what. Your new feature whenever triggered would check the given files, check whether some values changed, fix whatever needs to be fixed and log a report about the task. It would not be simple to implement something like this, but, important to note is that there is a solution.

Is there a way to check if a function exists within a class?

I'm passing some post data to execute a function based on post data, to determine if this should execute I've tried to use the following:
$SP = new StoredProcedure();
if(function_exists($SP->$_POST['function']))
{
$SP->$_POST['function']();
}
else
{
echo 'function does not exist.';
}
Unfortunately this passes the following error:
Notice: Undefined property: StoredProcedure::$getFormList in
C:\DWASFiles\Sites\junglegym\VirtualDirectory0\site\wwwroot\wp-content\plugins\qcore\qcore_waitress.php
on line 353 function does not exist.
I'm certain this function does exist, and when I execute it without the function_exists()
Is there a way to check if a function exists when it's inside a class?
method_exists checks for method of a class for a given object:
Docs Link:
http://www.php.net/method_exists
if(method_exists($SP, $_POST['function'])) {
{
$SP->$_POST['function']();
}
else
{
echo 'function does not exist.';
}
function_exists() and method_exists() are for these checks. First is for regular functions and second for OOP functions.
You should use method_exists
Try with:
if(method_exists($SP, $_POST['function'])) {
check this all
Find out if a method exists in a static class
Checking if function exists
and also PHP manual at
php.net/method_exists
php.net/manual/en/function.function-exists.php
www.php.net/class_exists
Hope these might help you.

PHP Get corresponding data, with default and error handling

I have a normal HTML menu that passes a GET statement to the url.
<li>Home</li>
Just like this, al though this is ofcourse only 1 item of the entire menu.
In a seperated file I have an function that checks if an GET or POST statement exist,
and If it exist and is not NULL then it will give the value back to where it was called.
public function getFormVariable($value){
switch (strtoupper($_SERVER['REQUEST_METHOD'])) {
case 'GET':
if (isset($_GET[$value]) && $_GET[$value] != NULL) {
return $_GET[$value];
}
else{
return false;
}
break;
case 'POST':
if (isset($POST[$value]) && $POST[$value] != NULL) {
return $POST[$value];
}
else{
return false;
}
break;
default:
return false;
}
}
And with the following code it takes the get value and finds the corrosponding class
(every class is in a seperated file, and every class is 1 link in my menu)
In this class there is just some regular functions/data that gives the content of that page.
$class = loadClass($ConfigPage->getFormVariable('Page'));
$ConfigPage->SetProperty('content', $class);
function loadClass($Page){
$class_name = 'Content' . $Page;
if(!class_exists($class_name)){
return 'Error: Content has not been found.';
}
$class = new $class_name();
return $class;
}
Explaining: The menu gives a GET value of 'Contact' which is then check by GetFormVariable() and then the corresponding class is found which gives back the content that class holds.
Now my question:
When the function LoadClass() cant find the name of the class it was given through the GET statement, it should return a error string. But this is not happening. I get a beautiful big orange error from PHP saying the following:
Fatal error: Call to a member function render() on a non-object in
E:\Program files\wamp\www\BDW\Class\Html_Page.Class.php on line 147
Line 147 is where to object is called
echo $this->content->render();
The Render function is as it says a normal return function inside the content classes.
Why do i get this error, and how do i fix it?
Second question. If there is no GET statement in the url. It gives the exact same error. which is pretty logical. But how do i make it show ContentHome when there is no GET statement in the url, and an ERROR when the value of the GET statement is incorrect.
Thank you for reading,
If there is anything unclear please tell me. English is not my native language, and after all. I am here to learn.
EDIT:
My knowledge of PHP is not great enough, so i decided when a class can not be found. it brings you back to home without any error. which i wanted in the first place.
Here is my new code which is still not working, why?
$class = loadClass($ConfigPage->getFormVariable('Page'));
$ConfigPage->SetProperty('content', $class);
function loadClass($Page){
$class_name = 'Content' . $Page;
$Default_Class = 'ContentHome';
if(!class_exists($class_name)){
//echo 'Error: Content has not been found.';
$class = new $Default_Class();
return $class;
}
else{
$class = new $class_name();
return $class;
}
$ConfigPage->Render();
}
It's happening because of this line :
if(!class_exists($class_name)){
return 'Error: Content has not been found.';
}
In the case the class doesn't exist, you're returning a string from the function, and afterwards trying to call a method render() on it. You can fix it either by changing this behaviour (don't return an error string, but use an Exception or trigger_error() ) or checking that the function loadClass() does return a valid object through is_object() for example.
Regarding your second question, you should expand your tests in loadClass() to handle the empty GET variable case, and substitute the empty string with a "Home" default value.
Update :
Example usage for is_object in your case :
$class = loadClass($ConfigPage->getFormVariable('Page'));
if(! is_object($class)) {
// if $class is not an object, then something went wrong above
throw new \Exception('Invalid data returned from loadClass()');
}
$ConfigPage->SetProperty('content', $class);

Callback in function (PHP) is not working

When I execute following code I am getting this error. Why is that? What is the proper use of callbacks?
CODE (simplified)
class NODE {
//...some other stuff
function create($tags, $callback=false) {
$temp = new NODE();
//...code and stuff
if($callback) $callback($temp); //fixed (from !$callback)
return $this;
}
}
$document = new NODE();
$document->create("<p>", function($parent) {
$parent->create("<i>");
});
ERROR
Fatal error: Function name must be a string in P:\htdocs\projects\nif\nif.php on line 36
$document->new NODE();
This is not valid syntax. The accepted format would be:
$document = new NODE();
In addition to this, if you use the unary operator (!) on a false, you get true. If you use it on a Callable, you get false. As such, if (!$callback) $callback() will throw the first error of your script.
As a side note, you are reinventing the wheel. I would strongly recommend you take a look at the DOMDocument family of classes, which are doing exactly what you are currently trying to implement, albeit with fewer callbacks.
if(!$callback) $callback($temp);
If $callback is false, for sure you won't be able to call it as a callback.
if(!$callback) $callback($temp);
should probably be
if($callback) $callback($temp);
And the instanciation:
$document = new NODE();
My 2c here, type hinting may be good to use here as well.
Ex: function create($tags, callable $callback = function())
To do such a thing in php you should use function pointers and tell php which function to execute.
Look at this code.
// This function uses a callback function.
function doIt($callback)
{
$data = acquireData();
$callback($data);
}
// This is a sample callback function for doIt().
function myCallback($data)
{
echo 'Data is: ', $data, "\n";
}
// Call doIt() and pass our sample callback function's name.
doIt('myCallback');
So as you seen you can only pass the name to the function and you should predefine the function..
Similar question: How do I implement a callback in PHP?

Categories