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.
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.
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'];
What's the best way to store language data?
Keep it as variables in some kind of lang.php file...
$l_ipsum = 'smth';
$l_rand = 'string';
Or select them from a database? I'm in search of your advice.
Keep them in an array, so you don't pollute the global namespace.
$lang = array(
'ipsum' => 'smth',
'rand' => 'string',
);
Plus, you can create a helper function to get the string
function translate($string) {
global $lang;
return isset($lang[$string]) ? $lang[$string] : $string;
}
Of course, there are a thousand ways to do this (and I personally wouldn't use global variables, but it's all up to your skill level and personal preferences)...
Here is a list of a Zend_Translate adapters to give you idea of how it could look like
http://framework.zend.com/manual/en/zend.translate.adapter.html
Being new to PHP, and with a lot riding on this "function wrapper", I thought I'd get a few opinions and a little feedback. I'd like to get about five comments, if possible.
Now before you ask, I have many reasons for wanting to wrap other (WordPress) functions, the primary being hassle-free upgrading. It was also important for me to be able to set a custom name for each function definition, hence the $wrap array.
But I digress, does this look acceptable and relatively bulletproof?
function core_oo( $function )
{
$args = array_slice( func_get_args(), 1 );
$wrap = array
(
'comment' => 'the_comment',
'comments' => 'have_comments',
'post' => 'the_post',
'posts' => 'have_posts'
);
return call_user_func_array( $wrap[ $function ], $args );
}
... and the function will be called like...
core_oo( 'post', 'arg1', 'arg2' );
Many thanks!
EDIT:
Per chaos's sugeestion below, is this the right way to declare $wrap as static?
static $wrap = array
( ...
Well, your fundamental aim seems like madness, but with your fundamental aim taken as a given, yes, that function looks like a fine way to accomplish it.
You should declare $wrap as static to ensure that you're not pointlessly regenerating the array every time the function is called, though.
And this:
if(!isset($wrap[$function]))
trigger_error('No underlying function known for ' . $function, E_USER_ERROR);
would probably be smart too.
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.