GOAL: Declare/set variables in the scope of the function that called the running function.
DETAILS:
Hey Overflowans,
I'm looking to add one last little piece of sugar to a utility that I use in a lot of my php functions. It allows me to define a flexible contract on the input of my functions. Over a few iterations, I've gotten it pared down to a usage that looks like this:
function doSomething($param_arr){
FPX::contract(array(
'required' => array("length", "width", "height", "weight"),
'optional' => array("circumference")
));
$length = $parr['length'];
$width = $parr['width'];
$height = $parr['height'];
$weight = $parr['weight'];
$circumference = $parr['circumference'];
....
}
FPX::contract() automatically grabs the $param_arr and parses it to make sure that it's compatible with the defined contract.
What I would like to do now is eliminate the need to declare each of the variables afterwards. Is there a way that I can, within the lowest function, declare variables in the scope of the function that called it? So FPX::contract() needs to be able to set variables that are in the scope of doSomething() so that I don't have to declare each of these variables. (I don't want to declare globals).
Ideally it would just look like:
function doSomething($param_arr){
FPX::contract(array(
'required' => array("length", "width", "height", "weight"),
'optional' => array("circumference")
));
....
}
And then doSomething() would be able to access each of the variables listed in the contract as $length, $width, etc.
I'm aware of the function($var0, $var1, $var2=null) syntax, but it's not very easy to use this syntax with a large number of optional variables.
Thanks,
Ken
Maybe you can do something with extract()?
That is, just have your contract() return an array or a reference to an array, then extract() it.
You can do something like
extract(FPX::contract(array(
'required' => array("length", "width", "height", "weight"),
'optional' => array("circumference")
)));
Its slightly different from the idea you had about it, but the result should be, what you expected.
extract(array('foo'=>'bar'));
echo $foo;
http://php.net/extract
Do they have to be a collection of variables and not an associative array?
$parsedParams = FPX::contract(array(
'required' => array("length", "width", "height", "weight"),
'optional' => array("circumference")
),
$param_arr
);
echo $parsedParams['length'];
echo $parsedParams['circumference'];
Related
I always get a PHP notice in my error log because of a little language function I am using. Somehow I can't get it right...
My languages array looks like this (EDITED):
$dict = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
],
];
And this is my language function (EDITED):
function lang($phrase){
global $l, $dict;
return $dict[$phrase][$l];
}
$l is defined before the $dict array (as "de" or "en").
In my document I am doing:
<?php echo lang('title');?>
And the PHP notice refers to the "return $dict[$phrase][$l];" part.
I don't really understand why. I tried different things and nothing works.
EDIT:
Oh, damn. After posting I finally realized I where using a phrase which is not in my language array anymore. Somehow I was blind. Nevertheless the post was good to clean up my function. Thanks!
As noted my comments on the original post, after fixing the missing bracket (assuming typo) in your array declaration, the function works correctly, if called correctly as echo lang('title'). If you get a notice on Undefined index, it means the $phrase you're looking for doesn't exist in the $dict array. (If you only get the notice in your error logs, you may want to turn on on-screen error reporting when you're developing to catch these in real time!)
Basic debugging aside, let me help you tidy up this code a bit. First, $l or language can be passed in as a function argument. Second, the $dict doesn't need to be a variable, assuming you're not modifying it on the fly. You can declare it as a constant and avoid globals. Third, your array mapping is unnecessary overhead, when you can simply call the relevant indexes in the array. A minimal clean-up would then look something like this:
const DICT = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
]
];
function lang($phrase, $lang) {
// If you're using PHP 7, the null-coalescing operator is handy here:
return DICT[$phrase][$lang] ?? "Phrase {$phrase} Unknown!";
}
You'd use it like this:
// Sample usage: Output title in English and description in German:
echo lang('title', 'en') . ' ' . lang('description', 'de');
// Or, if the language variable is pre-defined, then e.g.:
$l = 'en';
echo lang('title', $l) . ' ' . lang('description', $l);
Here, the use of a constant for your dictionary is a basic but very efficient way to handle a global need for pre-defined data. Constants are parsed at compile-time, while variable declarations are parsed at runtime. They are available in all contexts. And you definitely don't want to have the whole $dict inside your function just to avoid a global declaration!
A more elegant way to handle this would be to create a class, which will allow you to avoid the language function parameter. (Class constants and properties are like "local globals", available to all methods in the class.) Here's a sample application of your case as a class, where the dictionary is a class constant, and the language is a class property:
class Dict {
const DICT = [
'title' => [
'en' => 'Welcome',
'de' => 'Willkommen'
],
'description' => [
'en' => 'Hello',
'de' => 'Hallo'
]
];
public static $lang = 'en'; // for a default language
public static function phrase($phrase)
{
return self::DICT[$phrase][self::$lang] ?? "Phrase {$phrase} Unknown!";
}
}
Dict::$lang = 'de'; // Define language
echo Dict::phrase('title') . ' ' . Dict::phrase('description');
// : Willkommen Hallo
If you expect to have a large dictionary that needs to be accessed in other contexts, or simply wish to centralize your language reference, then you may want to define it as a regular constant outside the class, as in the first example. I've used a class constant here mostly for educational purposes. Hope this helps you forward and towards cleaner code.
The notice means that you are trying to access an index of your array that doesn't exist. In your case, it seems that you mixed up the order of language and phrase: You try to access $dict[en][title] instead of $dict[title][en].
More generally, always check if an index exists before you return it:
if (isset($dict[$l])) {
return ...
} else {
throw new Exception... // or whatever
}
As you move further on with PHP, you'll find that using globals will cause more harm than it's worth.
In the past few years I've used this formula to read parameters in my methods inside my php classes:
$params = func_get_args();
if(is_array($params[0])){
foreach($params[0] as $key => $value){
${$key} = $value;
}
}
And it works fine, as if I pass something like this:
$class->foo(array('bar' => 'hello', 'planet' => 'world'));
I will have in my foo method the variables bar and planet with their relative values.
But what I'm asking is: Is there any better way to do it? Something that maybe I can encapsulate in another method for example?
UPDATE
So, taking in consideration rizier123 comment, and after a chat with a friend of mine, I nailed down what I think is the better way pass parameters to function. As I know that I will always pass just one parameter to the function, which is always going to be an array, there's no need to call the func_get_args() function, but I better to expect an array all the time and by default I set an empty array, like in the following example:
class MyClass{
public function MyMethod(array $options = array()){
extract($options);
}
}
$my = new MyClass();
$my->MyMethod(array('name' => 'john', 'surname' => 'doe'));
// Now MyMethod has two internal vars called $name and $surname
Yes you can use extract() to convert your arrays to variables, like this:
extract($params[0]);
There is a new feature from PHP 5.6, it's called Variadic functions
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
function foo(...$arguments) {
foreach ($arguments as $arg) {
var_dump($arg);
}
}
foo('1', 2, true, new DateTime('now'));
You can do this with PHP built-in function extract().
Use it this way:
$var_array = array("color" => "blue",
"size" => "medium",
"shape" => "sphere");
extract($var_array);
When you run echo $color, $size, $shape; it outputs:
blue, medium, sphere
in PHP, I'm considering doing something like this:
function foo(){
echo 'bar';
}
$fn = 'foo';
$fn();
It works, but is it considered bad practice?
I have an multidimensional array of elements that each have a corresponding function. I would like to store that function name, and call the corresponding functions for each element when traversing the array.
something like:
function render_el1(){ echo 'et';}
function render_el2(){ echo 'to';}
$elements = array(
'el_1' => array(
'name' => 'Element One'
, 'func' => 'render_el1'
)
, 'el_2' => array(
'name' => 'Element Two'
, 'func' => 'render_el2'
)
);
foreach($elements as $element => $options){
$fn = $options['func'];
echo '<h1>'.$options['name'].'</h1>';
if (function_exists($fn)) {
$fn();
}
}
Any comments to this approach is highly welcome, and I'd also like to know what this method is called in programming terms.
Not sure it is bad practice, but it makes your code hard to understand : to understand your short (5 lines) example, I've had to think :-(
Using call_user_func() and other functions of the same kind could have at least one advantage : looking at the code, one would immediatly understand you are calling a function in a way that's not the one we're generally used to.
You want to register functions into an array in your second example and then call them for what looks like a render process. This is similar to using function pointers in C (or paint event callbacks etc). It is an okay approach if you don't want to/can't use polymorphism (the feature that makes OOP worthwhile).
Your approach is simpler at that stage, but will probably get more bloated if you are adding more sophisticated code.
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 happened to be making some changes to a WordPress blog and noticed that they use parse_str (http://php.net/parse_str) for parsing and setting their optional parameters to a function.
I'm wondering if there is an advantage to this over sending an array?
Examples:
With array:
$blahOptions = array(
'option_1' => true,
);
BlahArray($blahOptions);
function BlahArray($options = array()) {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
// this would probably be in a function to be used everywhere
foreach ($defaults as $defaultOption => $defaultValue) {
if (!isset($options[$defaultOption])) $options[$defaultOption] = $defaultValue;
}
}
With parse_str:
$blahOptions = 'option_1=1';
BlahString($blahOptions);
function BlahString($options = '') {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
parse_str($options, $defaults);
$options = $defaults;
}
No. That seems like a ridiculous way to pass functional parameter arguments. I could understand it if you needed to recreate $_GET or $_POST variables or something along those lines, but for parameters to a function? That's code smell right there.
They should be using an array, and then utilizing extract() instead. I've worked with Wordpress before, and my advice is to keep the code at arm's length. It is not an example of model programming.
No. There are more disadvantages than advantages.
When you’re using a single string, you just can pass string values. With an array you can use every PHP data type and every element’s value type is independently of each other.
With parse_str, you can potentially drop in the query string from the URL, and the function will work. If you use an array, and you want to use the query string, you'll have to enumerate everything into an array before calling the function.
I'm not totally convinced it's the best way to go, but I see how it can add a bit of flexibility.