The best praticle to handle parameters (or arguments) in a functions it is about to know exactly what the function needs in a ordered list (see func_get_args()) so to get the corresponding variables (locale scope) and values.
What about if you know what you need as input inside a function but you don't know what are you are going to get? (How many arguments? What order? What type?)
I suppose that there could be different ways to do that and I've found myself the following code lines:
function myvars() {
$vars = array( "firstname" => "string", "lastname" => "string", "age" => "int", "alive" => "bool" );
$args = func_get_args();
foreach ($args as $arg) {
foreach($vars as $var => $type) {
if (preg_match('/^' . $type . '/i', gettype($arg))) {
$$var = $arg;
unset($vars[$var]);
break;
}
}
}
var_dump($firstname);
var_dump($lastname);
var_dump($age);
var_dump($alive);
}
myvars(31, "Mark", true, "Green");
I would appriciate other snippets to work out this issue.
On two occasions I have been asked, "Pray, Mr. Babbage, if you put
into the machine wrong figures, will the right answers come out?" ...
I am not able rightly to apprehend the kind of confusion of ideas that
could provoke such a question.
—Charles Babbage, Passages from the Life of a Philosopher
Function signatures and declarations are precisely to establish an interface or contract between two different parts of your code. Why would you ever declare a function without any specification and why would you ever call that function without any idea of what you're doing and why would you still expect to get a sensible result?
Have your function declare its parameters by name, or at least accept an array or object with named keys. You can't get code structure without structuring your code. Your example code is non-deterministic, since you're getting two strings and cannot distinguish them from one another. There's no guarantee you can make that this function always produces the correct output.
Related
I call an object that returns an array given certain chained methods:
Songs::duration('>', 2)->artist('Unknown')->genre('Metal')->stars(5)->getAllAsArray();
The problem lies that every time I want to get this array, for example, in another script, I have to chain everything again. Now imagine that in over 10 scripts.
Is there a way to recall the chained methods for later use?
Since you can't cache the result, you could cache the structure of the call chain in an array.
$chain = [
'duration' => ['>', 2],
'artist' => 'Unknown',
'genre' => 'Metal',
'stars' => 5,
'getAllAsArray' => null
];
You could use that with a function that emulates the chained call using the cached array:
function callChain($object, $chain) {
foreach ($chain as $method => $params) {
$params = is_array($params) ? $params : (array) $params;
$object = call_user_func_array([$object, $method], $params);
}
return $object;
}
$result = callChain('Songs', $chain);
If you can not cache your results as suggested, as I commented, here are a couple ideas. If your application allows for mixing of functions (as in you are permitted by standards of your company's development rules) and classes, you can use a function wrapper:
// The function can be as complex as you want
// You can make '>', 2 args too if they are going to be different all the time
function getArtists($array)
{
return \Songs::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(getArtists(array('Unkown','Metal',5)));
If you are only allowed to use classes and __callStatic() is not forbidden in your development and is also available in the version of PHP you are using, you might try that:
// If you have access to the Songs class
public __callStatic($name,$args=false)
{
// This should explode your method name
// so you have two important elements of your chain
// Unknown_Metal() should produce "Unknown" and "Metal" as key 0 and 1
$settings = explode("_",$name);
// Args should be in an array, so if you have 1 value, should be in key 0
$stars = (isset($args[0]))? $args[0] : 5;
// return the contents
return self::duration('>', 2)->artist($settings[0])->genre($settings[1])->stars($stars)->getAllAsArray();
}
This should return the same as your chain:
print_r(\Songs::Unknown_Metal(5));
It should be noted that overloading is hard to follow because there is no concrete method called Unknown_Metal so it's harder to debug. Also note I have not tested this particular set-up out locally, but I have notated what should happen where.
If those are not allowed, I would then make a method to shorten that chain:
public function getArtists($array)
{
// Note, '>', 2 can be args too, I just didn't add them
return self::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(\Songs::getArtists(array('Unkown','Metal',5)));
I wrote a lib doing exactly what you're looking for, implementing the principle suggested by Don't Panic in a high quality way: https://packagist.org/packages/jclaveau/php-deferred-callchain
In your case you would code
$search = DeferredCallChain::new_(Songs::class) // or shorter: later(Songs::class)
->duration('>',2) // static syntax "::" cannot handle chaining sadly
->artist('Unknown')
->genre('Metal')
->stars(5)
->getAllAsArray();
print_r( $search($myFirstDBSongs) );
print_r( $search($mySecondDBSongs) );
Hoping it will match your needs!
I have a model that runs a query with a bunch of conditions in the SQL. As a result, the model needs to accept a lot of parameters, i.e:
this->model_name->method($param1, $param2, ... )
On the model side, I typically set this up as
function method($param1 = NULL, $param2 = NULL, ... )
Each of those parameters is optional, and use cases will vary around the app. So my question is: at what point (if ever) does it make sense to start passing these parameters to the method via an array, a la:
$params = [
'param1' => 'whatever',
'param2' => 'whatever',
...
]
this->model_name->method($params)
With the end goal being, I suppose, cleaner code, and less instances of method(null, null, null, null, $param) unless that's an okay thing to do.
Most answers have been supportive of the array method (which, generally speaking, I would also agree with), but I'll play devil's advocate and list some negatives:
Documentation is less clear
Most methods of documenting functions/methods will list the parameters of that function individually. For example, a function with a basic DocBlock will look like this:
/**
* A function that accepts an array of params
* #param array $param_array An array of key=>value arguments
*/
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
Note how the DocBlock doesn't directly support individual parts of the $param_array, just the array as a whole. In contrast, listing all the arguments individually looks like this:
/**
* A function that 'normal' params
* #param string $key1 First argument
* #param string $key2 Second argument
*/
function accept_normal($key1 = 'first_val', $key2 = 'second_val') {
echo $key1;
echo $key2;
}
This is also a problem if you expect your functions to be fairly self-documenting, as in the first example you're not required to actually list your expected arguments in the function itself.
Default values may not work as expected
'As expected' is probably a bit of a loaded phrase (and this is probably one of the more obvious problems), but take the following:
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
Some may expect the var_dump to include the default value of key1 and the new value of key2, but the whole array is replaced, meaning you will need to remember to set default values for each key manually in each function, like so:
function accept_array($param_array = array()) {
if (!isset($param_array['key1'])) { $param_array['key1'] = 'first_val'; }
if (!isset($param_array['key2'])) { $param_array['key2'] = 'second_val'; }
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
No automatic filtering
Listing the arguments the 'normal' way also gives you a built-in set of filters. Take for example this quick and dirty user search:
/**
* We want to allow searching for users by user_id or email only!
* #param array $param_array
*/
function find_user($param_array = array('user_id' => 0, 'email' => '')) {
foreach ($param_array as $field => $value) {
$this->db->or_where($field, $value);
}
$this->db->get('users');
}
find_user(array('first_name' => 'Joe', 'last_name' => 'Bloggs'));
Without manually adding some 'accepted keys' type validation on the $param_array, a call to the find_user() function can essentially use whatever fields it likes. The simpler version would obviously look like this:
/**
* We want to allow searching for users by user_id or email only!
* #param int $user_id
* #param string $email
*/
function find_user($user_id = 0, $email = '') {
$this->db->or_where('user_id', $user_id);
$this->db->or_where('email', $email);
$this->db->get('users');
}
// No way for me to submit any other fields, they'll just fail when they get to the query
find_user('Joe', 'Bloggs'));
I accept some of these are a bit entry-level and there's probably many more that I missed (feel free to comment with more and I'll copy them into the reply with credit), but hopefully there's enough there to make people think twice about automatically using the 'array method' without thinking about manual validation and documentation etc.
Passing an array of parameters provides a better option for self-documenting your code.
When I use many parameters, I often find myself using a style like:
// do_something_model($enable_option1,$enable_option2,$enable_option3)
do_something_model(FALSE, TRUE, FALSE)
where I carry a comment line with the parameter names to remind myself of how I am
using the model.
In such a case, using an array with meaningfully named keys provides a useful mnemonic.
More recently, I am also using more wrapper functions. For example, I may have my
basic model method do get all my data from a table and this method will have a few
options.
I then define a new method that does a specific task and then invoke the basic method within it using the correct options.
Footnote
I find that if my methods have "too many options", it is better to rethink the purpose of the method and to break it up into two or more specialized methods that are easier to use.
I would recommend the array version as well. Symfony2 also uses this pattern a lot, for instance in rendring templates, creating form classes and creating http responses in general. You just have to make sure you cleanly document all possible parameters.
You could go either route, but an array would definitely keep your methods cleaner. It makes perfect sense to pass the parameters as an array.
I know it is possible to use optional arguments as follows:
function doSomething($do, $something = "something") {
}
doSomething("do");
doSomething("do", "nothing");
But suppose you have the following situation:
function doSomething($do, $something = "something", $or = "or", $nothing = "nothing") {
}
doSomething("do", $or=>"and", $nothing=>"something");
So in the above line it would default $something to "something", even though I am setting values for everything else. I know this is possible in .net - I use it all the time. But I need to do this in PHP if possible.
Can anyone tell me if this is possible? I am altering the Omnistar Affiliate program which I have integrated into Interspire Shopping Cart - so I want to keep a function working as normal for any places where I dont change the call to the function, but in one place (which I am extending) I want to specify additional parameters. I dont want to create another function unless I absolutely have to.
No, in PHP that is not possible as of writing. Use array arguments:
function doSomething($arguments = array()) {
// set defaults
$arguments = array_merge(array(
"argument" => "default value",
), $arguments);
var_dump($arguments);
}
Example usage:
doSomething(); // with all defaults, or:
doSomething(array("argument" => "other value"));
When changing an existing method:
//function doSomething($bar, $baz) {
function doSomething($bar, $baz, $arguments = array()) {
// $bar and $baz remain in place, old code works
}
Have a look at func_get_args: http://au2.php.net/manual/en/function.func-get-args.php
Named arguments are not currently available in PHP (5.3).
To get around this, you commonly see a function receiving an argument array() and then using extract() to use the supplied arguments in local variables or array_merge() to default them.
Your original example would look something like:
$args = array('do' => 'do', 'or' => 'not', 'nothing' => 'something');
doSomething($args);
PHP has no named parameters. You'll have to decide on one workaround.
Most commonly an array parameter is used. But another clever method is using URL parameters, if you only need literal values:
function with_options($any) {
parse_str($any); // or extract() for array params
}
with_options("param=123&and=and&or=or");
Combine this approach with default parameters as it suits your particular use case.
I am a total NOOB in programming (but this is only my second question on stackoverflow :-) ).
By a foreach function I get 5 different string values for $Loncoord, $Latcoord, $gui;
this I can see with the print_r in the code written below:
"-5.68166666667","+24.6513888889","IMG_3308",
But I now want to create 5 different markers in the $map->addMarkerByCoords (function is it ?).
print_r ("$Loncoord");
print_r ("$Latcoord");
print_r ("$gui");
$map->addMarkerByCoords("$Loncoord","$Latcoord","$gui",'OldChicago');
Is this possible?
Do I need to put them in a array and call these in the (function ?) or do I need to use a foreach function?
I tried both for a week now but I can't get it working.
Can you help me?
The answers you produced gave me a turn in the right direction.
Thank you for the quick responses and the explaining part.
But for the addMarkerByCoord (function! (stupid me)) I found this in the googlemaps API:
function addMarkerByCoords($lon,$lat,$title = '',$html = '',$tooltip = '') {
$_marker['lon'] = $lon;
$_marker['lat'] = $lat;
$_marker['html'] = (is_array($html) || strlen($html) > 0) ? $html : $title;
$_marker['title'] = $title;
$_marker['tooltip'] = $tooltip;
$this->_markers[] = $_marker;
$this->adjustCenterCoords($_marker['lon'],$_marker['lat']);
// return index of marker
return count($this->_markers) - 1;
}
It depends on the implementation of map::addMarkerByCoords()
The method (not a function) name, and its signature, suggests that you are only able to add one coord at a time. But to be sure you'ld need to know the methods true signature. So the question is: does the method allow arrays as arguments?
Usually, a method that allows you to add multiple items at once, has the plural name of the intended action in it's name:
map::addMarkersByCoords() // note the s after Marker
If the 'map' class is your own implementation, you are free to implement it the way you like of course, but in that case keep the descriptive names of the methods in mind. So, add one marker:
map::addMarkerByCoords()
Add multiple markers at once:
map::addMarkersByCoords()
Typically you would implement the plural method as something like this:
public function addMarkersByCoords( array $markers )
{
foreach( $markers as $marker )
{
$this->addMarkerByCoord( $marker[ 'long' ], $marker[ 'lat' ], $marker[ 'img ' ], $marker[ 'name' ] );
}
}
Basically, the plural method accepts one array, and adds each individual marker by calling the singular method.
If you wanna get even more OOP, you could implement the plural and singular method to accept (an array of) Marker objects. But that is not particalarly relevant for this discussion.
Also, the suggested expantion of the Map's interface with a plural method doesn't nessecarily mean you can't add multiple markers outside the object with calling the singular method in a foreach loop. It's up to your preference really.
If you want to call the addMarkerByCoords for 5 times with 5 different values for each parameter then you can build an array for every parameter and then iterate with the foreach function:
$Loncoord=array(1,2,3,4,5);
$Latcoord=array(1,2,3,4,5);
$gui=array(1,2,3,4,5);
$city=array('OldChicago','bla','bla','bla','bla');
foreach($Loncoord as $k=>$v)
$map->addMarkerByCoords($Loncoord[$k],$Latcoord[$k],$gui[$k],$city[$k]);
Try losing some of the quotes...
$map->addMarkerByCoords($Loncoord,$Latcoord,$gui,'OldChicago');
To answer the question properly though, we would need to know what addMarkerByCoords was expecting you to pass to it.
Seeking suggestions from PHP architects!
I'm not terribly familiar with PHP but have taken over maintenance of a large analytics package written in the language. The architecture is designed to read reported data into large key/value arrays, which are passed through various parsing modules to extract those report parameters known to each of those modules. Known parameters are removed from the master array, and any leftovers which were not recognized by any of the modules, are dumped into a kind of catch-all report showing the "unknown" data points.
There are a few different methods being used to call these parser modules, and I would like to know which if any are considered to be "proper" PHP structure. Some are using pass-by-reference, others pass-by-value, some are functions, some are objects. All of them modify the input parameter in some way.
A super-simplified example follows:
#!/usr/bin/php
<?php
$values = Array("a"=>1, "b"=>2, "c"=>3, "d"=>4 );
class ParserA {
private $a = null;
public function __construct(&$myvalues) {
$this->a = $myvalues["a"];
unset($myvalues["a"]);
}
public function toString() { return $this->a; }
}
// pass-by-value
function parse_b($myvalues) {
$b = $myvalues["b"];
unset($myvalues["b"]);
return Array($b, $myvalues);
}
// pass-by-reference
function parse_c(&$myvalues) {
echo "c=".$myvalues["c"]."\n";
unset($myvalues["c"]);
}
// Show beginning state
print_r($values);
// will echo "1" and remove "a" from $values
$a = new ParserA($values);
echo "a=".$a->toString()."\n";
print_r($values);
// w ill echo "2" and remove "b" from $values
list($b, $values) = parse_b($values);
echo "b=".$b."\n";
print_r($values);
// will echo "3" and remove "c" from $values
parse_c($values);
print_r($values);
?>
The output will be:
Array
(
[a] => 1
[b] => 2
[c] => 3
[d] => 4
)
a=1
Array
(
[b] => 2
[c] => 3
[d] => 4
)
b=2
Array
(
[c] => 3
[d] => 4
)
c=3
Array
(
[d] => 4
)
I'm really uncomfortable having so many different call methods in use, some of which have hidden effects on the call function parameters using "&pointer"-style functions, some requiring the main body to write their output, and some writing their output independently.
I would prefer to choose a single methodology and stick with it. In order to do so, I would also like to know which is most efficient; my reading of the PHP documentation indicates that since it uses copy-on-write, there shouldn't be much performance difference between using pointers to vs passing the object directly and re-reading a return value. I would also prefer to use the object-oriented structure, but am uncomfortable with the hidden changes being made to the input parameter on the constructor.
Of the three calling methods, ParserA(), parse_b(), and parse_c(), which if any is the most appropriate style?
I'm not really an expert in PHP but from my experience passing by value is better. This way code won't have side effects and that mean it will be easier to understand and maintain and do all sorts of crazy things on it, like using it as callback for map function. So I'm all for parse_b way of doing things.
FYI: In PHP, objects are always passed by reference, no matter what. Also if you have an array with objects and scalar values in it, the scalar values are passed by value, but the objects by reference.
As a general rule in PHP, do not use references unless you really have to.
references in PHP are also not what most people expect them to be:
"References in PHP are a means to access the same variable content by different names. They are not like C pointers; instead, they are symbol table aliases.""
see also: php.net: What References Are
So in short:
The proper way of handling this PHP is using creating an object that passes the variables around by value or manipulating the array with array_map (array_map allows you to apply a callback function to the elements an array.)
I would vote against the methods proposed in general, but of them, I think parse_b has the best idea.
I think it would be better design to wrap the "data" array in a class that could let you "pop" a key out of it easily. So the parser ends up looking like:
class ParserA {
private $a = null;
public function __construct(My_Data_Class $data) {
$this->a = $data->popValue("a");
}
public function toString() { return $this->a; }
}
And a sample implementation
class My_Data_Class {
protected $_data;
public function __construct(array $data) {
$this->_data = $data;
}
public function popValue($key) {
if (isset($this->_data[$key])) {
$value = $this->_data[$key];
unset($this->_data[$key]);
return $value;
}
}
}