I have some PHP 5.3 code which builds an array to be passed to a view. This is the code I have.
# Select all this users links.
$data = $this->link_model->select_user_id($this->user->id);
if (count($data) > 0) {
# Process the data into the table format.
$table = array
(
'properties' => array
(
'delete_link_column' => 0,
),
'callbacks' => array
(
# Callback for the name link.
function($value) {
return sprintf('%s', $value, $value);
},
# Callback for the category link.
function($value) {
return sprintf('%s', $value, $value);
},
# Callback for the creation date.
function($value) {
return date('jS M Y', $value);
},
# Callback for the delete link.
function($value) {
return sprintf('delete', $value);
},
),
'columns' => array
(
'name', 'category', 'creation date',
),
'data' => array
(
),
'sorting' => array
(
'sort' => false,
),
);
However the problem is that I cannot use anonymous functions in PHP 5.2, which is the server I must upload this schoolwork. The view requires callback functions to be defined so it can call them.
What would be the neatest way to convert this PHP code to not using anonymous functions? Thanks.
You could call one of those function like so:
$func = $callbacks[0];
$func();
Which also works with create_function() and using strings for named functions like so:
function test() {
echo "test";
}
$func = 'test';
$func();
$func = create_function('' , 'echo "test 2"; ');
$func();
Also, if the calling is done using call_user_func you can use array($object, 'func_name') to call a public method on an object or a class with array('Class_Name', 'func_name'):
class Test {
public static function staticTest() { echo "Static Test"; }
public function methodTest() { echo "Test"; }
public function runTests() {
$test = array('Test', 'staticTest');
call_user_func($test);
$test = array($this, 'methodTest');
call_user_func($test);
}
}
$test = new Test();
$test->runTests();
Anonymous functions are great for ephemeral one-offs, like event listeners in patterns like Observer.
However, since you've already formalized an interface (callbacks for rendering names, categories, creation dates, and a delete link) you may as well go the extra step of defining a 'renderer' interface that requires those 4 methods to be implemented. Instead of passing callbacks you'd pass a single renderer subclass to the view, which could then be used to call the appropriate methods. The view could also validate it by checking the parent class. That would still allow you to swap renderers in the spirit of portable, reusable OOP without requiring anonymous functions.
Is there a situation where your callbacks would ever be coming from arbitrary code (e.g. plugins)? If not, there's really no benefit to making those callbacks anonymous. It might seem like you're saving a little namespace bloat, but you're also making it tougher to debug or document.
Related
I have the following class method for creating a Twig environment object.
public function getView($filename,
array $customFunctions = null,
array $customFunctionArgs = null,
$debug = false) {
$loader = new \Twig_Loader_Filesystem('/App/Views/Templates/Main');
$twig = new \Twig_Environment($loader);
if (isset($customFunctions)) {
foreach ($customFunctions as $customFunction) {
$customFunction['name'] = new \Twig_SimpleFunction($customFunction['name'],
function ($customFunctionArgs) {
return $customFunction['method']($customFunctionArgs);
});
$twig->addFunction($customFunction['name']);
}
}
// Check debugging option
if ($debug == true && !$twig->isDebug()) {
$twig->enableDebug();
$twig->addExtension(new \Twig_Extension_Debug());
} elseif (!$debug && $twig->isDebug()) {
$twig->disableDebug();
}
$template = $twig->load($filename);
return $template;
}
Problem is, I don't understand how to pass values in order to make this work dynamically and keep all the objects in context and scope. For instance, here is how I'm trying to use it but can't pass the variables as a reference I guess?
$customFunctions = ['name' => 'customFunctionName',
'method' => $Class->method($arg)];
$customFunctionArgs = [$arg];
$template = $View->getView('template.html.twig', $customFunctions, $customFunctionArgs, true);
My environment is PHP 5.6 & Twig 1.35.0. I suppose this is not a Twig specific question per se, but more of how to use class objects within other classes/methods.
Félix Gagnon-Grenier's answer helped me find a solution to this problem. However, I feel the need to post an answer with all the missing pieces to the puzzle for anyone that needs a solution for this.
I believe it will make more sense if I start at the end and explain to the beginning. When creating your array, there are several things to consider.
Any class objects that are needed for the function have to be declared inside a use() with the closure.
Any arguments for the custom function must be declared as a function parameter for the closure. This will allow you to declare them later.
I ended up adding a sub-array with the arguments I needed for each custom function, that way I don't need to iterate over them separately.
$customFunctions = [
[
'name' => 'customFunction',
'method' => function($arg1, $arg2) use($Class) {
return $Class->customFunction($arg1, $arg2);
},
'arguments' =>
[
'arg1', 'arg2'
]
]
];
$template = $View->getView(
'template.html.twig',
true,
$customFunctions
);
echo $View->renderView($template);
Based on this code (reflective of question above), I had to make some notable modifications.
if (isset($customFunctions)) {
foreach ($customFunctions as $index => $customFunction) {
if (isset($customFunctions['arguments'])) {
$arguments = $customFunctions['arguments'];
} else {
$arguments = [];
}
$twigFunction = new \Twig_SimpleFunction(
$customFunction['name'],
function (...$arguments) use ($customFunction) {
return $customFunction['method'](...$arguments);
});
$twig->addFunction($twigFunction);
}
}
You can do this whatever way works for you, but there are important things to consider which I struggled with. Once again, your arguments MUST go into the function parameters. function (...$arguments) use ($customFunction). Your custom function will be passed in the use(). In order to actually pass the arguments in the closure, you must use ... to unpack them (as an array). This applies to PHP 5.6+. It allows the arguments to be dynamically expanded to the correct amount, otherwise you will get missing argument errors.
There are slight flaws in how you construct the custom functions data array and the loop that injects them into the template.
The custom functions should be a three dimensional array
$customFunctions = [
[ // notice the extra level, allowing you to access the name
'name' => 'customFunctionName',
'method' => function() { return 'wat'; }
// you need to pass a callable, not the result of a call
]
];
The scope is not inherited like you seem to think it is, you need to use() variables you intend to access. I personnally would not overwrite the 'name' value of the array, but that's uncanny paranoia of internal side effects, it seems to work in practice.
if (isset($customFunctions)) {
foreach ($customFunctions as $customFunction) {
$customFunction['name'] = new \Twig_SimpleFunction(
$customFunction['name'],
function () use ($customFunctionArgs, $customFunction) {
return $customFunction['method']($customFunctionArgs);
});
$twig->addFunction($customFunction['name']);
}
}
You might need to add looping over $args so that the correct args are sent to the correct function (send $args[0] to $customFunctions[0] etc.).
Note that this prevents you from sending a parameter into your custom function unless you add it in the loop:
function ($templateArg) use ($customFunctionArgs, $customFunction) {
return $customFunction['method']($customFunctionArgs, $templateArg);
}
Here is a gist with tests if you're interested.
From very long time i am working on php.
But one question may I have no idea about
like I have one function as bellow:
function hello($param1, $param2="2", $param3="3", $param4="4")
Now whenever I will use this function and if I need 4th params thats the $param4 then still I need to call all as blank like this one:
hello(1, '', '', "param4");
So is there any another way to just pass 1st and 4th param in call rather then long list of blanks ?
Or is there any other standard way for this ?
There was an RFC for this named skipparams but it was declined.
PHP has no syntactic sugar such as hello(1, , , "param4"); nor hello(1, default, default, "param4"); (per the RFC) for skipping optional parameters when calling a function.
If this is your own function then you can choose the common jQuery style of passing options into plug-ins like this:
function hello( $param1, $more_params = [] )
{
static $default_params = [
'param2' => '2',
'param3' => '3',
'param4' => '4'
];
$more_params = array_merge( $default_params, $more_params );
}
Now you can:
hello( 1, [ 'param4'=>'not 4, muahaha!' ] );
If your function requires some advanced stuff such as type hinting then instead of array_merge() you will need to manually loop $more_params and enforce the types.
One potential way you can do this, while a little bit hacky, may work well in some situations.
Instead of passing multiple variables, pass a single array variable, and inside the function check if the specific keys exist.
function hello($param1, $variables = ["param2" => "2", "param3" => "3", "param4" => "4"]) {
if(!array_key_exists("param2", $variables)) $variables['param2'] = "2";
if(!array_key_exists("param3", $variables)) $variables['param3'] = "3";
if(!array_key_exists("param4", $variables)) $variables['param4'] = "4";
echo "<pre>".print_r($variables, true)."</pre>";
}
This will allow you to set "param4" in the above variable, while still remaining default on all of the others.
Calling the function this way:
hello("test", ["param4" => "filling in variable 4"]);
Will result in the output being:
Array
(
[param4] => filling in variable 4
[param2] => 2
[param3] => 3
)
I don't generally recommend this if it can be avoided, but if you absolutely need this functionality, this may work for you.
The key here is that you have a specifically named index inside the array being passed, that you can check against inside the function itself.
The answer, as I see it, is yes and no.
No, because there's no way to do this in a standard fashion.
Yes, because you can hack around it. This is hacky, but it works ;)
Example:
function some_call($parm1, $parm2='', $parm3='', $parm4='') { ... }
and the sauce:
function some_call_4($parm1, $parm4) {
return some_call($parm1, '', '', $parm4);
}
So if you make that call ALOT and are tired of typing it out, you can just hack around it.
Sorry, that's all I've got for you.
It is an overhead, but you can use ReflectionFunction to create a class, instance of which that can be invoked with named parameters:
final class FunctionWithNamedParams
{
private $func;
public function __construct($func)
{
$this->func = $func;
}
public function __invoke($params = [])
{
return ($this->func)(...$this->resolveParams($params));
}
private function resolveParams($params)
{
$rf = new ReflectionFunction($this->func);
return array_reduce(
$rf->getParameters(),
function ($carry, $param) use ($params) {
if (isset($params[$param->getName()])) {
$carry[] = $params[$param->getName()];
} else if ($param->isDefaultValueAvailable()) {
$carry[] = $param->getDefaultValue();
} else {
throw new BadFunctionCallException;
}
return $carry;
},
[]
);
}
}
Then you can use it like this:
function hello($param1, $param2 = "2", $param3 = "3", $param4 = "4")
{
var_dump($param1, $param2, $param3, $param4);
}
$func = new FunctionWithNamedParams('hello');
$func(['param1' => '1', 'param4' => 'foo']);
Here is the demo.
I can't seem to find anything of this, and was wondering if it's possible to store a function or function reference as a value for an array element. For e.g.
array("someFunc" => &x(), "anotherFunc" => $this->anotherFunc())
Thanks!
You can "reference" any function. A function reference is not a reference in the sense of "address in memory" or something. It's merely the name of the function.
<?php
$functions = array(
'regular' => 'strlen',
'class_function' => array('ClassName', 'functionName'),
'object_method' => array($object, 'methodName'),
'closure' => function($foo) {
return $foo;
},
);
// while this works
$functions['regular']();
// this doesn't
$functions['class_function']();
// to make this work across the board, you'll need either
call_user_func($functions['object_method'], $arg1, $arg2, $arg3);
// or
call_user_func_array($functions['object_method'], array($arg1, $arg2, $arg3));
PHP supports the concept of variable functions, so you can do something like this:
function foo() { echo "bar"; }
$array = array('fun' => 'foo');
$array['fun']();
Yout can check more examples in manual.
Yes, you can:
$array = array(
'func' => function($var) { return $var * 2; },
);
var_dump($array['func'](2));
This does, of course, require PHP anonymous function support, which arrived with PHP version 5.3.0. This is going to leave you with quite unreadable code though.
check out PHP's call_user_func. consider the below example.
consider two functions
function a($param)
{
return $param;
}
function b($param)
{
return $param;
}
$array = array('a' => 'first function param', 'b' => 'second function param');
now if you want to execute all the function in a sequence you can do it with a loop.
foreach($array as $functionName => $param) {
call_user_func($functioName, $param);
}
plus array can hold any data type, be it function call, nested arrays, object, string, integer etc. etc.
I'm currently working on an OO PHP application. I have a class called validation which I would like to use to check all of the data submitted is valid, however I obviously need somewhere to define the rules for each property to be checked. At the moment, I'm using arrays during the construction of a new object. eg:
$this->name = array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter
)
One array for each property.
I would then call a static method from the validation class which would carry out various checks depending on the values defined in each array.
Is there a more efficient way of doing this?
Any advice appreciated.
Thanks.
I know the associative array is used commonly to configure things in PHP (it's called magic container pattern and is considered bad practice, btw), but why don't you create multiple validator classes instead, each of which able to handle one rule? Something like this:
interface IValidator {
public function validate($value);
}
$validators[] = new StringLengthValidator(2, 10);
$validators[] = new NotNollValidator();
$validators[] = new UsernameDoesNotExistValidator();
This has multiple advantages over the implementation using arrays:
You can document them (very important), phpdoc cannot parse comments for array keys.
Your code becomes typo-safe (array('reqiured' => true))
It is fully OO and does not introduce new concepts
It is more readable (although much more verbose)
The implementation of each constraint can be found intuitively (it's not in a 400-line function, but in the proper class)
EDIT: Here is a link to an answer I gave to a different question, but that is mostly applicable to this one as well.
Since using OO it would be cleaner if you used classes for validating properties. E.g.
class StringProperty
{
public $maxLength;
public $minlength;
public $required;
public $value;
function __construct($value,$maxLength,$minLength,$required)
{
$this->value = $value;
$this-> maxLength = $maxLength;
$this-> minLength = $minLength;
$this-> required = $required;
}
function isValidat()
{
// Check if it is valid
}
function getValidationErrorMessage()
{
}
}
$this->name = new StringProperty($namefromparameter,10,2,true);
if(!$this->name->isValid())
{
$validationMessage = $this->name-getValidationErrorMessage();
}
Using a class has the advantage of encapsulating logic inside of it that the array (basically a structure) does not have.
Maybe get inspired by Zend-Framework Validation.
So define a master:
class BaseValidator {
protected $msgs = array();
protected $params = array();
abstract function isValid($value);
public function __CONSTRUCT($_params) {
$this->params = $_params;
}
public function getMessages() {
// returns errors-messages
return $this->msgs;
}
}
And then build your custom validators:
class EmailValidator extends BaseValidator {
public function isValid($val=null) {
// if no value set use the params['value']
if ($val==null) {
$val = $this->params['value'];
}
// validate the value
if (strlen($val) < $this->params['maxlength']) {
$this->msgs[] = 'Length too short';
}
return count($this->msgs) > 0 ? false : true;
}
}
Finally your inital array could become something like:
$this->name = new EmailValidator(
array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter,
),
),
);
validation could then be done like this:
if ($this->name->isValid()) {
echo 'everything fine';
} else {
echo 'Error: '.implode('<br/>', $this->name->getMessages());
}
This question already has answers here:
Does PHP allow named parameters so that optional arguments can be omitted from function calls?
(17 answers)
Closed 1 year ago.
I have this:
function foo($a='apple', $b='brown', $c='Capulet') {
// do something
}
Is something like this possible:
foo('aardvark', <use the default, please>, 'Montague');
If it’s your function, you could use null as wildcard and set the default value later inside the function:
function foo($a=null, $b=null, $c=null) {
if (is_null($a)) {
$a = 'apple';
}
if (is_null($b)) {
$b = 'brown';
}
if (is_null($c)) {
$c = 'Capulet';
}
echo "$a, $b, $c";
}
Then you can skip them by using null:
foo('aardvark', null, 'Montague');
// output: "aarkvark, brown, Montague"
If it's your own function instead of one of PHP's core, you could do:
function foo($arguments = []) {
$defaults = [
'an_argument' => 'a value',
'another_argument' => 'another value',
'third_argument' => 'yet another value!',
];
$arguments = array_merge($defaults, $arguments);
// now, do stuff!
}
foo(['another_argument' => 'not the default value!']);
Found this, which is probably still correct:
http://www.webmasterworld.com/php/3758313.htm
Short answer: no.
Long answer: yes, in various kludgey ways that are outlined in the above.
You pretty much found the answer, but the academic/high-level approach is function currying which I honestly never found much of a use for, but is useful to know exists.
You can use some quirks, either passing all arguments as an array like ceejayoz suggests, or some overcomplicated code that parses func_get_args() and merges with a list of defaults. Not to copy-paste it, you'll have to use objects and traits. Finally, to be able to pass all kinds of values (not excluding null or false by making them a signal for default param substitution), you'll have to declare a dummy special type DefaultParam.
Another minus is that you have to duplicate the names and default values in the function declaration, if you want to get type hints or help in any IDE.
class DefaultParam {}
trait multi_arg_functions
{
private static function multi_arg($defaults, $list, $preserve_index = false)
{
$arg_keys = array_slice(array_keys($defaults), 0, count($list));
if ($preserve_index) {
$listed_arguments = array_slice($list, 0, count($arg_keys));
$extras = array_slice($list, count($arg_keys), null, true);
} else {
$listed_arguments = array_splice($list, 0, count($arg_keys));
$extras = &$list;
}
unset($list);
$arguments = array_combine($arg_keys, $listed_arguments);
$arguments = array_filter($arguments, function ($entry) {
return !($entry instanceof DefaultParam); //remove entries that mean default, a special class in this case
});
$arguments = array_merge($defaults, $arguments);
return [$arguments, $extras];
}
}
class b {
use multi_arg_functions;
static function func1($an_argument = 'a value', $another_argument = 'another value', $third_argument = 'yet another value') { //give defaults here to get hints in an IDE
list($args, $extras) = self::multi_arg( //note: duplicate names and defaults
[
'an_argument' => 'a value',
'another_argument' => 'another value',
'third_argument' => 'yet another value!',
], func_get_args());
echo json_encode(['args' => $args, 'extras' => $extras])."\n";
}
}
$default_param = new DefaultParam();
b::func1('value 1');
b::func1('value 2', $default_param, 'third argument');
b::func1('value 3', $default_param, 'third argument', 'fourth argument');
Note: by using preserve_index = true you get the extra arguments to start from their original index.
As of PHP 8, use named parameters:
function foo($a='apple', $b='brown', $c='Capulet') {
// do something
}
foo('apple', c:'Montague');
This let's you bypass as many parameters as you want, allowing them to take on their default value. This is helpful in long-winded functions like setcookie:
setcookie('macadamia', httponly:true); // skips over 5 parameters
Note that named parameters require all non-optional parameters to be passed. These may be passed positionally (as I've done here, no names on them) or with names in any order.