Given a system written in php 5.6 with a function declared like this:
function doSometing($param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '') {...}
I want to list all the calls using fifth parameter $param5.
Consider that uses may be with variable formats:
Irrelevant spaces, breaklines, tabs, etc.
Arguments passed as pure strings or variable names or any other php trick
Complex example:
doSometing( 'loreIpsum',$Loreipsum, 'loreipsum',
"loreipsum", 'Target') ;
Maybe an IDE feature or plugin? Maybe Regexp expression (I'm newbie with it)?
Depending on the tools you have at hand there are two options that I can suggest:
1.) If you are using PHPStorm, you can do the following:
Go to the definition of your doSomething function and temporarily remove the fifth parameter from the function signature (or comment it out)
Then run the following PHPStorm inspection on your code base: "PHP => Code Smells => Parameter numbers mismatch declaration" (see
PHPStorm will show you all occurences where this problem occurred and with that the function calls where too many parameters (here 5) are passed to the function
2.) You can use a regular expression to search for specific function calls, for instance via your IDE RegEx search. But be aware that it will be rather tricky to design such a regex because your function can be called with variables, method calls on an object, strings, integers, etc.
But if you are sure that you are only passing in variables, numbers and strings it should be doable...
Related
It was noted in another question that wrapping the result of a PHP function call in parentheses can somehow convert the result into a fully-fledged expression, such that the following works:
<?php
error_reporting(E_ALL | E_STRICT);
function get_array() {
return array();
}
function foo() {
// return reset(get_array());
// ^ error: "Only variables should be passed by reference"
return reset((get_array()));
// ^ OK
}
foo();
I'm trying to find anything in the documentation to explicitly and unambiguously explain what is happening here. Unlike in C++, I don't know enough about the PHP grammar and its treatment of statements/expressions to derive it myself.
Is there anything hidden in the documentation regarding this behaviour? If not, can somebody else explain it without resorting to supposition?
Update
I first found this EBNF purporting to represent the PHP grammar, and tried to decode my scripts myself, but eventually gave up.
Then, using phc to generate a .dot file of the two foo() variants, I produced AST images for both scripts using the following commands:
$ yum install phc graphviz
$ phc --dump-ast-dot test1.php > test1.dot
$ dot -Tpng test1.dot > test1.png
$ phc --dump-ast-dot test2.php > test2.dot
$ dot -Tpng test2.dot > test2.png
In both cases the result was exactly the same:
This behavior could be classified as bug, so you should definitely not rely on it.
The (simplified) conditions for the message not to be thrown on a function call are as follows (see the definition of the opcode ZEND_SEND_VAR_NO_REF):
the argument is not a function call (or if it is, it returns by reference), and
the argument is either a reference or it has reference count 1 (if it has reference count 1, it's turned into a reference).
Let's analyze these in more detail.
First point is true (not a function call)
Due to the additional parentheses, PHP no longer detects that the argument is a function call.
When parsing a non empty function argument list there are three possibilities for PHP:
An expr_without_variable
A variable
(A & followed by a variable, for the removed call-time pass by reference feature)
When writing just get_array() PHP sees this as a variable.
(get_array()) on the other hand does not qualify as a variable. It is an expr_without_variable.
This ultimately affects the way the code compiles, namely the extended value of the opcode SEND_VAR_NO_REF will no longer include the flag ZEND_ARG_SEND_FUNCTION, which is the way the function call is detected in the opcode implementation.
Second point is true (the reference count is 1)
At several points, the Zend Engine allows non-references with reference count 1 where references are expected. These details should not be exposed to the user, but unfortunately they are here.
In your example you're returning an array that's not referenced from anywhere else. If it were, you would still get the message, i.e. this second point would not be true.
So the following very similar example does not work:
<?php
$a = array();
function get_array() {
return $GLOBALS['a'];
}
return reset((get_array()));
A) To understand what's happening here, one needs to understand PHP's handling of values/variables and references (PDF, 1.2MB). As stated throughout the documentation: "references are not pointers"; and you can only return variables by reference from a function - nothing else.
In my opinion, that means, any function in PHP will return a reference. But some functions (built in PHP) require values/variables as arguments. Now, if you are nesting function-calls, the inner one returns a reference, while the outer one expects a value. This leads to the 'famous' E_STRICT-error "Only variables should be passed by reference".
$fileName = 'example.txt';
$fileExtension = array_pop(explode('.', $fileName));
// will result in Error 2048: Only variables should be passed by reference in…
B) I found a line in the PHP-syntax description linked in the question.
expr_without_variable = "(" expr ")"
In combination with this sentence from the documentation: "In PHP, almost anything you write is an expression. The simplest yet most accurate way to define an expression is 'anything that has a value'.", this leads me to the conclusion that even (5) is an expression in PHP, which evaluates to an integer with the value 5.
(As $a = 5 is not only an assignment but also an expression, which evalutes to 5.)
Conclusion
If you pass a reference to the expression (...), this expression will return a value, which then may be passed as argument to the outer function. If that (my line of thought) is true, the following two lines should work equivalently:
// what I've used over years: (spaces only added for readability)
$fileExtension = array_pop( ( explode('.', $fileName) ) );
// vs
$fileExtension = array_pop( $tmp = explode('.', $fileName) );
See also PHP 5.0.5: Fatal error: Only variables can be passed by reference; 13.09.2005
I am trying to make my own template engine, (Don't ask why!)
And will try making:
{{#Form::input("name")}}
In my rendering system i'll make this eval code:
preg_match_all('/\{\{\#(.*?)\}\}/is',$data,$output);
$out="";
foreach ( $output[1] as $variables):
$find = '{{#' . $variables . '}}';
$data = str_replace($find, eval($variables), $data);
endforeach;
But i am getting this error
Parse error: syntax error, unexpected $end in /home/psafari/public_html/slt/classes/template.class.php(43) : eval()'d code on line 1
It should run "Form::input('something')" as specificed in the template engine. What am i doing wrong?
Despite the already uber-relevant comments against building a new templating engine, I'd suggest breaking the string found. Taking your example of {{#Form::input("name")}}, you'd have {{#CLASS_NAME::FUNCTION_NAME(params)}}. So using regex you could break the found string (as you already do), create an instance of or reference the class detected, then call the method parsed using a dynamic call (and obviously fail/error if either is not found).
As for the parameters, you could avoid qualifiers (quotes in this case) and break on every comma, or use the qualifiers and parse it differently via regex again. Your call. For each parameter parsed and found, they'd be added to the function call. In order to avoid eval, and given the fact PHP does not support a random number of arguments (such as Python does with args and kwargs), having an array as the single parameter for the functions you'll use on the template files.
You could also have sort of an 'interface' function that accepts a certain number of parameters that then receives the function (or class + function names), counts the number of parameters it finds and finally relays the call to the actual underlying function based on the correct number of parameters found. Though the more you add the more obscure the processing becomes - assuming users of your templating engine will be able to define new template methods, it's a very bad idea to use this second route (as you'd have to support up to N parameters on these interface functions).
I like creating my PHP functions using key=>value pairs (arrays) as arguments instead of individual parameters.
For example, I prefer:
function useless_func($params) {
if (!isset($params['text'])) { $params['text'] = "default text"; }
if (!isset($params['text2'])) { $params['text2'] = "default text2"; }
if (!isset($params['text3'])) { $params['text3'] = "default text3"; }
echo $params['text'].$params['text2'].$params['text3'];
return;
}
And I don't like:
function useless_func($text = "default text", $text2 = "default text2", $text3 = "default text3") {
echo $text.$text2.$text3;
return;
}
I had first seen things done this way extensively in the Wordpress codebase.
The reason I prefer arrays:
Function arguments can be provided in any order
Easier to read code / more self documenting (in my opinion)
Less prone to errors, because when calling a function I must investigate the proper array keys
I was discussing this with a co-worker and he says that it's useless and just leads to extra code and it's much harder to set the default values. Basically, he disagrees with me completely on all three points.
I am looking for some general advise and guidance from experts who might be able to provide insight: What's the better or more proper way to do this?
Don't do that!
Passing all in an array is a bad idea most of the time.
It prevents people from using your function without knowing what it needs to operate.
It lets you create functions needing lots of parameters when probably you should create a function with more precise argument needs and a narrower goal
It seems like the contrary of injecting in a function what it needs.
Function arguments can be provided in any order
I have no such preference. I don't understand that need.
Easier to read code / more self documenting (in my opinion)
Most IDEs will present you with the different arguments a function needs. If one sees a function declaration like foo(Someclass $class, array $params, $id) it is very clear what the function needs. I disagree that a single param argument is easier to read or self documenting.
Less prone to errors, because when calling a function I must investigate the proper array keys
Allowing people to pass in an array without knowing that values will be defaulted is not close to "not error-prone". Making it mandatory for people to read your function before using it is a sure way for it never to be used. Stating that it needs three arguments along with their defaults is less error prone because people calling your function will know which values the parameters will be defaulted to, and trust that it will present the result they expect.
If the problem you are trying to solve is a too great number of arguments, the right decision is to refactor your functions into smaller ones, not hide function dependencies behind an array.
Well, it's kinda usefully. But for some arguments which is passing always it's better to use classic passing like function some($a1, $a2). I'm doing like this in my code:
function getSome(SomeClass $object, array $options = array())
{
// $object is required to be an instance of SomeClass, and there's no need to get element by key, then check if it's an object and it's an instance of SomeClass
// Set defaults for all passed options
$options = array_merge(array(
'property1' => 'default1',
'property2' => 'default2',
... => ...
), $options);
}
So, as you can see I like that code style too, but for core-arguments I prefer classic style, because that way PHP controls more things which should I, if I used the you code style.
I'm assuming you're asking whether it's A Good Thing to write all functions so that they accept only one argument, and for that argument to be an array?
If you're the only person who's ever going to work on your code then you can do what you like. However, by passing all argument values through an array, anyone else is going to have to work harder to understand what the function does and why / how they could use it, especially if they're using an IDE with auto-complete for function names etc. They don't call it a "function signature" for nothing.
I'd recommend that array parameters are reserved either for items where you don't know how many there will be (e.g. a series of data items), or for groups of related options / settings (which may be what's going on in the Wordpress example that you mention?).
If you do continue with a blanket approach to array arguments then you should at least be aware of its impact on readability and take some steps to counter that issue.
Your co-worker is right. Not only is it more code for the same functionality, it is harder to read and probably has lowered performance (Since you need to call isset for each param and you need to access an array to set values).
This borders on Cargo Cult programming. You say this is more readable and self-documenting. I would ask how? To know how to use your function/method I have to read into the code itself. There's no way I can know how to use it from the signature itself. If you use any half-decent IDE or editor that supports method signature hinting this will be a real PITA. Plus you won't be able to use PHP's type-hinting syntax.
If you find you are coding a load of parameters, especially optional parameters then it suggests there might be something wrong with your design. Consider how else you might go about it. If some or all of the parameters are related then maybe they belong to their own class.
Using array_merge() works okay, but using the + operator can be used too; it works the other way, it only adds default values where one hasn't been given yet.
function useless_func(array $params = array())
{
$params += array(
'text' => 'default text',
'text2' => 'default text2',
'text3' => 'default text3',
);
}
See also: Function Passing array to defined key
A few things you don't get with using arrays as function arguments is:
type checking (only applicable to objects and arrays, but it can be useful and in some cases expected).
smart(er) text editors have a code insight feature that will show the arguments a function understands; using arrays takes away that feature, though you could add the possible keys in the function docblock.
due to #2 it actually becomes more error prone, because you might mistype the array key.
Your co-worker is crazy. It's perfectly acceptable to pass in an array as a function argument. It's prevalent in many open source applications including Symfony and Doctrine. I've always followed the 2 argument rule, if a function needs more than two arguments, OR you think it will use more than two arguments in the future, use an array. IMO this allows for the most flexibility and reduces any calling code defects which may arise if an argument is passed incorrectly.
Sure it takes a little bit more work to extrapolate the values from the array, and you do have to account for required elements, but it does make adding features much easier, and is far better than passing 13 arguments to the function every time it needs to be called.
Here is a snippet of code displaying the required vs optional params just to give you an idea:
// Class will tokenize a string based on params
public static function tokenize(array $params)
{
// Validate required elements
if (!array_key_exists('value', $params)) {
throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
}
// Localize optional elements
$value = $params['value'];
$separator = (array_key_exists('separator', $params)) ? $params['separator'] : '-';
$urlEncode = (array_key_exists('urlEncode', $params)) ? $params['urlEncode'] : false;
$allowedChars = (array_key_exists('allowedChars', $params)) ? $params['allowedChars'] : array();
$charsToRemove = (array_key_exists('charsToRemove', $params)) ? $params['charsToRemove'] : array();
....
I have used arrays to substitute a long list of parameters in many occasions and it has worked well. I agree with those in this post that have mentioned about code editors not being able to provide hints for the arguments. Problem is that if I have 10 arguments, and the first 9 are blank/null it just becomes unwieldy when calling that function.
I would also be interested in hearing an how to re-design a function that requires a lot of arguments. For example, when we have a function that builds SQL statements based on certain arguments being set:
function ($a1, $a2, ... $a10){
if($a1 == "Y"){$clause_1 = " something = ".$a1." AND ";}
...
if($a10 == "Y"){$clause_10 = " something_else = ".$a10." AND ";}
$sql = "
SELECT * FROM some_table
WHERE
".$clause_1."
....
".$clause_10."
some_column = 'N'
";
return $sql;
}
I would like to see PHP entertain adding a native helper function that could be used within a the function being called that would assist in passing an array of parameters by undertaking the necessary type checking. PHP recognized this to a certain extent by creating the func_get_args() function which allows arguments to be passed in any order. BUT this will only pass a COPY of the values, so if you want to pass objects to the function this will be a problem. If such a function existed, then the code editors would be able to pick this up and provide details on possible arguments.
#Mike, you could also "extract()" your $params argument into local variables, like this:
// Class will tokenize a string based on params
public static function tokenize(array $params)
{
extract($params);
// Validate required elements
if (!isset($value)) {
throw new Exception(sprintf('Invalid $value: %s', serialize($params)));
}
// Localize optional elements
$value = isset($value) ? $value : '';
$separator = isset($separator) ? $separator] : '-';
$urlEncode = isset($urlEncode) ? $urlEncode : false;
$allowedChars = isset($allowedChars) ? $allowedChars : array();
$charsToRemove = isset($charsToRemove) ? $charsToRemove : array();
....
Same implementation, but shorter.
This question already has answers here:
PHP call_user_func vs. just calling function
(8 answers)
Closed 6 months ago.
I dont understand the function, call_user_func() in the sense that i get how it works but I'm not sure why its required or in what context to use it in php. As far as I'm concerned why not just call the function instead of calling a function with a function? Thnx!
call_user_func gives PHP the ability to treat methods and functions as quasi first-class citizens. In functional languages like javascript there is no need for these special tools per-se because a function is an object that happens to be callable.
PHP is getting closer to having this sort of notion though, especially with the closure support that came along with PHP5.3. Take a look at the comment I put under #deceze's answer. There are some other tools (namely variable functions, reflection and now closures) that offer the same basic functionality.
The most notable thing about call_user_func though is how it allows you to treat global functions, static classes and objects with a uniform interface. It's probably the closest thing they have to a single interface to invoke functions no matter how they're implemented. Internally the PHP core group of developers is working to homogenize some sort of a 'callable' or 'invokable' interface for the language, I'm sure we'll see a clean offering in PHP5.4 or the next major release.
I've had a couple situations where this was very necessary. For example, when I was creating a project that allowed a user to construct parts of a programming language, the system would wrap the "tag" definition in a function before running it for security reasons. However, in doing this, I can't simply call those functions, because I don't know when I need to call them, or even what the names would be. Enter call_user_func()...
So, I'm sure you're confused right now. Let me explain. What my system did, was take some XML, and convert it into PHP/HTML/JS with PHP. This allowed for rapid creation of GUIs (which was the goal). Take this XML for example:
<window id="login-win" title="Access Restricted" width="310" height="186" layout="accordion" layoutConfig="animate:true">
<panel id="login-panel" title="User Login">
<form id="login-form" title="Credentials">
<textbox id="login-uname" label="Username"/>
<password id="login-pass" label="Password"/>
<submit text="Login"/>
</form>
</panel>
<panel id="login-register" title="Register">
Nothing, you can't register!
</panel>
</window>
Each XML tag would be read, then fed into it's corresponding PHP function. That function would determine what to do for that 1 tag. In order to pull this off, I had files each named after the tag it handled. So, for example, the tag's file was "submit.php". This file's contents would get wrapped in a generated function, something like:
function tag_submit($variables, $parent, $children){
// deal with the data, then echo what is needed
}
This function's name would be stored in an array, with it's associated tag name, with the rest of the generated functions. This makes it so the function is generated once, and only created when needed, saving memory, since I would do a if(func_exists()) call to determine if I needed it or not.
However, since this is all dynamic, and the user may want to add in a new tag, for say, a < date > tag, I needed to use call_user_func() to get things to work. I can't hard-code a function call if I don't know what the name is.
Hope that all made sense. Basically, yes, it is a rarely used function, but it is still very very useful.
Named functions, anonymous functions, static methods, instance methods and objects with an "__invoke" method are collectively known as 'callables'. If I have a callable it should be possible to call it by placing parentheses afterwards "()" (I'll assume it takes no arguments). This works when our callable is stored in a variable, for example:
$f(); // Call $f
However, what if I store a callable in an object property?
$obj = new stdClass;
$obj->my_func = function() {};
$obj->my_func(); // Error: stdClass does not have a "my_func" method
The problem is that PHP's parser is getting confused because the code is ambiguous, it could mean calling a callable property or calling a method. PHP chooses to always treat this kind of code as a method call, so we cannot call callable properties this way. This is why we need 'call_user_func':
call_user_func($obj->my_func); // Calls $obj->my_func
There are other times when PHP doesn't understand the normal parentheses syntax; for example, PHP currently (5.5) doesn't know how to call the return value of another callable:
get_a_callable()(); // Parse error, unexpected "("
call_user_func(get_a_callable()); // Calls the return value of get_a_callable
It's also not currently possible to call a function definion directly, which is useful to work around PHP's lack of "let" statements, for example:
function($x) { echo $x . $x; }('hello'); // Parse error, unexpected "("
call_user_func(function($x) { echo $x . $x; }, 'hello'); // Calls the function
There are also times where it's useful to have function calls /reified/ as the 'call_user_func' function. For example, when using higher-order functions like array_map:
$funcs = [function() { return 'hello'; },
function() { return 'world'; }];
array_map('call_user_func', $funcs); // ['hello', 'world']
It's also useful when we don't know how many parameters will be needed, ie. we can't substitute our own "function($f, $x, $y, ...) { return $f($x, $y, ...); }".
One particularly nice definition is partial application, which only takes a few lines thanks to call_user_func and call_user_func_array:
function partial() {
$args1 = func_get_args(); // $f, $a, $b, $c, ...
return function() use ($args1) {
// Returns $f($a, $b, $c, ..., $d, $e, $f, ...)
$args2 = func_get_args(); // $d, $e, $f, ...
return call_user_func_array('call_user_func',
array_merge($args1, $args2));
};
}
I'm using someone elses class and that person has defined a function with five arguments.
in Sentry.php:
function checkLogin($user = '',$pass = '',$group = 10,$goodRedirect = '',$badRedirect = '')
If all five fields are filled in this leads to a login procedure.
Now on the page where he explains how to use this there is a snippet which, according to php.net, doesn't make sense.
in a page that loads the sentry:
require_once('../system/Sentry.php');
$theSentry = new Sentry();
if(!$theSentry->checkLogin(2)){ header("Location: login.php"); die(); }
which by default should behave as such that it checks if the $group argument is <= 10 (default). In this situation it should be two. If the user checked has a group-variable of <=2 this should enable the person to view the page.
However, this doesn't work and for a very obvious reason: the php manual states:
Note that when using default
arguments, any defaults should be on
the right side of any non-default
arguments; otherwise, things will not
work as expected.
So the code, according to phpbuilder.com should have no optional ($variable = default_something) field to fill in with a call to the function and it definitely shouldn't be defined as the third of five arguments.
How can I use the function like this?:
checkLogin(2)
Default arguments are PHP's way of dealing with the lack of overloaded functions. In Java you can write this:
public void login(String username)
public void login(String username, String password)
In PHP you have to solve it like this:
function login($username, $password = '')
This way $username is mandatory and $password is optional. While this may be convenient, it isn't always. In your example there are a log of arguments and they are all optional. A clean solution for this would be to make 1 function that does the work and add 'convenience' methods to make the interface cleaner.
So add:
function checkLoginGroup($group = 10) {
$this->checkLogin('', '', $group);
}
It simply calls the function that already exists, but it allows for a cleaner interface, just call:
$theSentry->checkLoginGroup(2);
It's even neater to make the provided method private (or protected, depending on your needs) and create public 'convience' methods so you can hide the implementation details.
However, if you can not or don't want to change the original class, you might be able to create a subclass.
Changing the order of the arguments is a possibility... But what for the time you'll need another argument to be "more optionnal" or "less optionnal" ? Will you change that order again ?
And, with it, every call that's made to the function ?
To pass only the third argument, considering your function's declaration, the only method I see is to give the three first arguments... Passing the default value (which you'll have to know, of course -- just look at it's declaration, for that) for the first two :
checkLogin('', '', 20);
In my opinion, using this instead of that one :
checkLogin(null, null, 20);
Has the advantage of being explicit : someone looking at the function's declaration will immediatly notice you're using the default values for the first two parameters.
Using NULL, the personn reading your code would have to check inside the function's code to see if NULL is handled in a special way (it could, afterall !) ; it wouldn't be as easy for someone to understand that this is your way of passing default values... As they are not the default values ^^
Other solutions would imply refactoring the function,
either to pass only one array as parameter (with its keys not being required by the code)
ot using Variable-length argument lists
Either way, you'd lose the ability your IDE has to show you the parameters the function is waiting for ; and that's bad :-(
And you'd also lose the phpdoc...
In addition to Mythicas excellent answer, I just want to point out another way of dealing with a lot of optional arguments: Arrays.
function checkLogin($args) {
$defaults = array('user' => null, 'pass' => null, 'group' => null, ...);
$args = array_merge($defaults, $args);
...
}
checkLogin(array('user' => 'x', 'group' => 9));
This essentially bypasses PHPs optional/required argument syntax entirely and instead custom-handles these things internally. It has the advantage of avoiding the problem you describe and making the function call extremely readable. The problem is that it makes the function more complex and you can't take advantage of PHP checking the existence of arguments for you.
The easiest way is just to change the order of arguments so that $group is first.
checkLogin(NULL,NULL,2);
but honestly bad coding style.