call_user_func how to solve this error - php

How to solve this problem with call_user_func
When I call the function I have this error generated by php. Below the error.
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'getChildsInMenuCount' not found or invalid function name i
the line inside my files with the function
while ($Qcategories->fetch() ) {
$categories_count++;
$rows++;
if ((!isset($_GET['cID']) && !isset($_GET['pID']) || (isset($_GET['cID']) && ((int)$_GET['cID'] === $Qcategories->valueInt('id')))) && !isset($cInfo) && (substr($action, 0, 3) != 'new')) {
$category_childs = ['childs_count' => AdministratorMenu::getChildsInMenuCount($Qcategories->valueInt('id'))];
$cInfo_array = array_merge($Qcategories->toArray(), $category_childs);
$cInfo = new objectInfo($cInfo_array);
}
The result of var_dump(__FUNCTION__); is string(20)"getChildsInMenuCount"
class AdministratorMenu {
// Count how many subcategories exist in a category
public static function getChildsInMenuCount($id) {
$OSCOM_Db = Registry::get('Db');
$categories_count = 0;
$Qcategories = $OSCOM_Db->prepare('select id
from :table_administrator_menu
where parent_id = :parent_id
');
$Qcategories->bindInt(':parent_id', $id );
$Qcategories->execute();
while ($Qcategories->fetch() !== false) {
$categories_count++;
$categories_count += call_user_func(__FUNCTION__, $Qcategories->valueInt('id'));
}
return $categories_count;
}
}

Since it's a class method, you need to use __METHOD__, not __FUNCTION__. This will include the class prefix.
$categories_count += call_user_func(__METHOD__, $Qcategories->valueInt('id'));

change call_user_func parameters like below:
call_user_func(__CLASS__ . '::' . __FUNCTION__, $Qcategories->valueInt('id'));
You must also specify the class in first parameter of call_user_func

Related

PHP autoloader pointing to a dynamic method name

For testing/debugging/evaluation purposes I want to write wrappers for selected entries of the PHP autoloader chain. As autoloader methods are restricted to a single argument with a given value (the class name) the only way to tell the wrapper what to do seems to be the method name. I thus iterate over spl_autoload_functions(), replace the selected items with dynamically created calls of methods like Wrapper::handler_xxxx and save the original information in that class. Wrapper has a method public static function __callStatic() which should evaluate the incoming requests, do its testing/debugging/evaluating work and otherwise redirect to the (saved) original handler. However, this does not work but leads to errors like this one:
PHP Fatal error: Uncaught LogicException: Passed array does not specify an existing method (class 'Wrapper' does not have a method 'autoload_mdGVzdG1ldGhvZAo') in [somewhere]
Seems that the autoloader needs real methods and does not work with PHP-magic. Whether this is a bug or a feature (if the latter: why?), is there any way to work around it?
Edit: some code example upon request, I am not sure if this helps understanding the problem; this is by far not finished, but just an attempt to get the basic idea up and running. Calling the magic method manually (by the name given above) works fine - it is just that the autoloader-logic is not willing to call it.
class Wrapper {
const AUTOLOAD_PREFIX = 'autoload';
public static function __callStatic(String $name, Array $arguments) {
if (strpos($name, self::AUTOLOAD_PREFIX) === 0) {
try {
# valid signature found
if (preg_match(sprintf('/^%s(_c([^_]+))?_m([^_]+)$/', self::AUTOLOAD_PREFIX), $name, $matches)) {
# call via class/method
$method = self::decodeMethodName($matches[3]);
if ($matches[2]) {
$class = self::decodeMethodName($matches[2]);
$class::$method($arguments);
}
else {
# call global function
$method($arguments);
}
}
else {
# invalid name
throw new \Exception(sprintf('Invalid static call of %s::%s', __CLASS__, $name));
}
}
catch (\Throwable $e) {
echo "I got you.";
die;
}
}
else {
throw new \Exception(sprintf('Invalid static call of %s::%s', __CLASS__, $name));
}
return;
}
private static function encodeMethodName(String $method) : String {
# protect and encode method names
return str_replace(array('+', '/', '='), array(0x81, 0x82, ''), base64_encode($method));
}
private static function decodeMethodName(String $method) : String {
# reconstruct method names
return base64_decode(str_replace(array(0x80, 0x81), array('+', '/'), $method));
}
public function protectAutoloader(?String $originalClass, String $originalMethod) : Void {
$stack = array();
$autoLoaders = spl_autoload_functions();
while (!$done && count($autoLoaders) > 0) {
$item = array_pop($autoLoaders);
if (
is_string($item) &&
is_null($originalClass) &&
($originalMethod === $item)
) {
# global function
$replacement = array(
__CLASS__,
sprintf('%s_m%s', self::AUTOLOAD_PREFIX, self::encodeMethodName($item)),
);
$done = true;
}
elseif (
is_array($item) &&
($item[0] === $originalClass) &&
($item[1] === $originalMethod)
) {
# static method
$replacement = array(
__CLASS__,
sprintf('%s_c%s_m%s', self::AUTOLOAD_PREFIX, self::encodeMethodName($item[0]), self::encodeMethodName($item[1])),
);
$done = true;
}
else {
# don't touch anything else (closures)
$replacement = $item;
}
# remove item and push to the stack
spl_autoload_unregister($item);
array_push($stack, $replacement);
}
# restore autoloader chain
while (count($stack) > 0) {
$item = array_pop($stack);
spl_autoload_register($item, true);
}
return;
}
I'd activate this for a specific autoloader:
$wrapper->protectAutoloader(NULL, 'testmethod');
Now testmethod() (if existing in the chain) will be replaced by Wrapper::autoload_mdGVzdG1ldGhvZAo() and if e.g. a syntactically broken file were autoloaded the message "I got you" would be printed.

PHP: how to resolve a dynamic property of an object that is multiple levels deep

I am have written a helper function to "cleanup" callback variables for input into MySQL. This is the function that I wrote:
public function string($object, $objectPath) {
if (!empty($object->$objectPath) || $object->$objectPath !== '') {
$value = $object->$objectPath;
} else {
return 'NULL';
}
if (!empty($value) || $value != '') {
return "'".str_replace("'","''",$value)."'";
} else {
return 'NULL';
}
}
Now, $object is always an object returned by the call, and $objectPath is always a string to points to a given value. Here's where the problem comes in. This works:
$value = $this->db->string($object, 'foo');
However, this does not work:
$value = $this->db->string($object, 'foo->bar->foo1->bar1');
Whenever $objectPath is more than "one layer" deep, I get the following error from (Amazon's) client library:
Fatal error: Call to undefined method MarketplaceWebServiceOrders_Model_Order::getFoo->Bar() in /path/to/Model.php on line 63
The code block that the error refers to is this:
public function __get($propertyName)
{
$getter = "get$propertyName";
return $this->$getter(); // this is line 63
}
$object is not XML, so I can't use SimpleXMLElement and XPath.
What is the problem with my code? Is it that am I concatenating an object and a string? If so, how can I make that possible? How can I get this function to do what I intended it to do?
By the way, I'm using PHP 5.4.27.
PHP doesn't automatically resolve a string containing multiple path levels to children of an object like you are attempting to do.
This will not work even if $obj contains the child hierarchy you are expecting:
$obj = ...;
$path = 'level1->level2->level3';
echo $obj->$path; // WRONG!
You would need to split up the path and "walk" through the object trying to resolve the final property.
Here is an example based on yours:
<?php
$obj = new stdClass();
$obj->name = 'Fred';
$obj->job = new stdClass();
$obj->job->position = 'Janitor';
$obj->job->years = 4;
print_r($obj);
echo 'Years in current job: '.string($obj, 'job->years').PHP_EOL;
function string($obj, $path_str)
{
$val = null;
$path = preg_split('/->/', $path_str);
$node = $obj;
while (($prop = array_shift($path)) !== null) {
if (!is_object($obj) || !property_exists($node, $prop)) {
$val = null;
break;
}
$val = $node->$prop;
// TODO: Insert any logic here for cleaning up $val
$node = $node->$prop;
}
return $val;
}
Here it is working: http://3v4l.org/9L4gc
With #itsmejodie's help, I finally got a working solution:
public function string($node, $objectPath) {
$value = NULL;
$path = explode('->', $objectPath);
while (($prop = array_shift($path)) !== NULL) {
if (!$node->$prop) {
break;
}
$value = $node->$prop;
$node = $node->$prop;
}
if (is_string($value)) {
return "'".str_replace("'","''",$value)."'";
} else {
return 'NULL';
}
}
The key for me was to see that, as #itsmejodie put it, "PHP doesn't automatically resolve a string containing multiple path levels to children of an object." In a string like, 'foo->bar->foo1->bar2', PHP won't convert the ->'s into the T_OBJECT_OPERATOR, thus appending a string to an object, e.g., $object->foo->bar->foo1->bar2, just won't work.

How can I pass a function as an optional variable to call_user_func in PHP

So I'd like to use the call_user_func to pass data to an optional parameter of a function.
Here's the example of a code, the optional parameter $data represents a functional called data that was declared in another file. I just want it to be called by using call_user_func that will set the parameter with the function's name and call it within the createtable function, but doesn't seem to work.
I got the example from the PHP Manual, but createTable contains many parameters. How can I make call_user_func only assign the string data to the optional parameter $data set to NULL as default?
function createTable($database, $table, $patch,$data = NULL)
{
echo "INFO: Adding '$table'in database '$database'.\n";
$sql = "SHOW TABLES FROM $database WHERE Tables_in_$database='$table';";
$result = mysql_query($sql);
$result_count = mysql_num_rows($result);
if ( $result_count != 1 ) {
echo "ERROR: Can not find table '$table' in database '$database'.\n";
$result = mysql_query($patch);
if ( false === $result ) {
echo "ERROR: Adding Table '$table' in database '$database' ... Failed\n";
return false;
}
else {
echo "INFO: Adding Table '$table'in database '$database' ... Success\n";
// using the optional parameter here
$data();
return true;
}
} else {
if ( $result_count == 1 ) {
echo "ERROR: Table '$table'already in database '$database'.\n";
return false;
}
}
}
// Now I'm passing value to the optional parameter $ data that is NULL as default.
call_user_func('createTable', "data");
Even with call_user_func you have to pass all the parameters.
Anyway, call_user_func is intended for use when the name of the function isn't necessarily known up front. For instance, you might have several functions and a variable, and the variable contains the name of the function to call.
Personally I think it's on par with eval and variable variables: A horrible idea. After all, if you have $foo = "function_name"; then you can call $foo() and it will call function_name.
Anyway, back to the point, just call it as a normal function and give it the parameters it needs. Pass null if you have to.
you must pass value like this
call_user_func('createTable', $database, $table, $patch,$data);
or this for call from class
call_user_func(array(&$objectName->{$anotherObject},$functionName), $arg1, $arg2, $arg2);
or you can use this can get arg as array
call_user_func_array("createTable", array("one", "two"));
or this for call from class can get arg as array
call_user_func_array(array($foo, "bar"), array("three", "four"));
or This can help you too it not need to pass all args
function __named($method, array $args = array())
{
$reflection = new ReflectionFunction( $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs( $pass);
}
I hope It Work
sample:
__named('createTable', array('data' => 'value'));
and it is for use in class
public function __named($method, array $args = array())
{
$reflection = new ReflectionMethod($this, $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs($this,$pass);
}
if you Don't set any value __named Put Null instead of Unset Value
It seems you just want to pass the last param, and not worry about the 1st three. I don't think call_user_func is the right tool here at all.
Why not just make a function that calls your function?
function call_createTable($data){
$database = '...';
$table = '...';
$patch = '...';
return createTable($database, $table, $patch, $data);
}
Then just simply call it like this: call_createTable("data");.

jquery dynamic url

I have a form and the url for submitting the form will generated dynamicly
var Var1 = new Array();
var Var2 = new Array();
if(Var1.length === 0)
$(this).attr('action', 'http://localhost/Method' ).submit();
if(Var1.length != 0 && Var2.length === 0)
$(this).attr('action', 'http://localhost/Method/Var1').submit();
if(Var1.length != 0 && Var2.length != 0)
$(this).attr('action', 'http://localhost/Method/Var1/Var2').submit();
and all that URLs fires one method in the server and it is
public function Method(){}
public function Method(Var1){}
public function Method(Var1 , Var2){}
is there anyway to make all the last 3 methods as one method? something like this:
public function Method(Var1, Var2){
if( condition for Var1 ){// doSomthing}
if( condition for Var2 ){// doSomthing}
}
If you need this function for PHP, you can use func_get_arg and func_num_args:
public function Method() {
$numArguments = func_num_args();
if ($numArguments >= 1) {
$argument1 = func_get_arg(0);
// Do something with argument 1
}
if ($numArguments >= 2) {
$argument2 = func_get_arg(1);
// Do something with argument 2
}
}
If all the three url calls a single method in your server you can use the default argument mechanism.
public function Method($Var1=null, $Var2=null){
if(is_null($Var1)){// doSomthing}
if(is_null($Var2)){// doSomthing}
}
Obviously to map these urls you need to use some sorts of router logic. And the router must dispatch the proper method of the object.
For exmaple if your url is something like /index.php/Object/method/param1/param2, index.php should create the proper object first.
$parts = explode('/', $_SERVER['REQEUST_URI']);
array_shift($parts); // emtpy part
array_shift($parts); // index.php
$cls = array_shift($parts) // Object
$obj = new $cls;
And then dispatch the method.
$method = array_shift($parts);
call_user_func_array(array($obj, $method), $parts);
public function Method(){
$root = 'http://localhost/Method/',
$Var1 = func_get_args(0) || '',
$Var2 = func_get_args(1) || '';
if($Var1 && $Var2) $root .= $Var1 + '/' + $Var2;
else if($Var1 && !$Var2) $root .= '/' + $Var1;
}

"Fatal error: Cannot redeclare" when reusing xml parsing code

I have a single xml parsing function that I'm trying to call multiple times as I only need to strip a little data out and continue on.
Here is the function:
//Parse Product ID from Product Sides
function getProductSpecs($xml,$type) {
// Setup arrary
global $productspecs;
global $count;
$count = 0;
global $type_check;
$type_check = $type;
// Parse the XML
// Create the parser
if (! ($xmlparser = xml_parser_create()) )
{
die ("Cannot create name list parser");
}
// Start tag function
function first($parser, $name, $attribs) {
global $trigger;
if ($name == "PRODUCTSIDEID") {
$trigger = 1;
} elseif ($name == "PRODUCTID") {
$trigger = 1;
}
}
// data handler function
function xml($parser, $data) {
global $trigger;
global $productspecs;
global $count;
global $type_check;
if ($trigger == 1){
if ($type_check == "sideid") {
$productspecs[$count]=$data;
$count = $count + 1;
} elseif ($type_check == "productid") {
$productspecs[$count]=$data;
$count = $count + 1;
}
$trigger = 0;
}
}
// Call the handler functions
xml_set_element_handler($xmlparser, "first", "");
// Call the data handler
xml_set_character_data_handler($xmlparser, "xml");
// Parse the XML data
xml_parse($xmlparser,$xml);
// Clear parser
xml_parser_free($xmlparser);
//Return the array
return $productspecs;
}
My problem arises when this is called:
xml_set_element_handler($xmlparser, "first", "");
I get the redeclare error on:
function first($parser, $name, $attribs) {
The function only appears the one time and I'm assuming the problem occurs on the call but is there a way around this so I don't have to duplicate so much code. I'm going to have to iterate through this multiple times.
Thanks.
Defining functions inside of functions can lead to this. Each time you run getProductSpecs() it's going to try to declare first() and xml() again, and in PHP, all user functions are declared in a global scope. The best solution is to move your first() function and your xml() function outside of the main getProductSpecs() function.
Another option is to use function_exists() around your function declarations, like this:
if (! function_exists('first')) {
// Start tag function
function first($parser, $name, $attribs) {
global $trigger;
if ($name == "PRODUCTSIDEID") {
$trigger = 1;
} elseif ($name == "PRODUCTID") {
$trigger = 1;
}
}
}

Categories