PHP - What's the best way to handle empty function/method arguments? - php

I have the function/method below inside a class that I'm creating and I'm just wondering what's the best way to handle empty/null arguments.
For example, in the following example, if I wanted to just set just the category when calling the function, I would need to use:
$data = $class->get_top_headlines( null, 'technology' );
Is there any way of calling the function more efficiently? I know I could pass the arguments as an array instead, but just wondered if there's any way of doing something like:
$data = $class->get_top_headlines( $category='technology' ); and automatically leaving the other arguments as their default of null?
public function get_top_headlines( $query=null, $category=null, $country=null, $sources=null, $page_size=null, $page=null ){
$url = $this->api_url . $this->endpoint_top_headlines;
$params = array();
if ( $query !== null ){
$params['q'] = urlencode( $query );
}
if ( $category !== null ){
$params['category'] = $category;
}
if ( $country !== null ){
$params['country'] = $country;
}
if ( $sources !== null ){
$params['sources'] = $sources;
}
if ( $page_size !== null ){
$params['pageSize'] = $page_size;
}
if ( $page !== null ){
$params['page'] = $page;
}
$params['apiKey'] = $this->api_key;
$url_query = http_build_query( $params );
$url = $url . '?' . $url_query;
echo $url;
$obj = $this->get_response( $url );
return $obj;
}

Try passing an array, and then using an array_merge
$data = $class->get_top_headlines(['category' => 'technology']);
Then in your function, have an array of defaults, then do your merge.
$settings = array_merge($settings, $passedInArray);
http://php.net/manual/en/function.array-merge.php

I think
(null, 'technology' );
might be less actual coding but a different solution might be to use OOP. You said it's already a method of a class so you could do something like:
$obj = new thatClass;
$obj->technology = $technology;
$obj->get_top_headlines();
in the Class:
Class thatClass{
$technology = null;
$category = null;
$query = null;
//...
public function get_top_headlines(){
if ( $this->query !== null ){
$params['q'] = urlencode( $this->query );
}
if ( $this->category !== null ){
$params['category'] = $this->category;
}
if ( $this->technology !== null ){
$params['technology'] = $this->technology;
}
//method code..
}
//class code..
}
The problem with this approach is if you need to call that same function again passing a different parameter in the same class instance, depending on your application you might need to manually set back to null the previous parameter (now an object attribute)

I would solve this problem by creating a new class or data structure which will encapsulate all the logic of validating and generating the URL and then use it everywhere I need it.
Here's a sample class.
class HeadLineParameters
{
private $params = [];
public function setQuery($query)
{
// validate/transform query data
$this->params['q'] = urlencode($query);
return $this;
}
public function setCategory($category)
{
// validate/transform category data
$this->params['category'] = $category;
return $this;
}
public function generateUrl()
{
return http_build_query( $this->params );
}
}
$params = new HeadLineParameters;
$params->setQuery($query)
->setCategory($category);
You just pass one argument and you know that it's just an instance of HeadLineParameters.
$class->get_top_headlines($params);
This solution doesn't pollute your current class with unnecessary state or fields. It is easy to test, and it has only one job. You can extend it easily, you can set default values, you can also validate it as you like.
Edit: Why you shouldn't add more fields to your current class?
If you add more fields to your current class you'll be breaking the single responsibility principle, and any method of this class can change these fields too. It shouldn't be a problem if these fields really belong there and more methods require them. This is fine if you are using OOP.
I am not sure what other people think about passing associated arrays to functions, but they are hard to handle if you have no documentation available. I have had trouble with them when reading some external code, and most of time I wasn't sure what's the data I was dealing with.

Related

Optional parameters in PHP function without considering order

Is there a way in PHP to use a function which has optional parameters in its declaration where I do not have to pass an optional arguments which already have values declared and just pass the next argument(s) which have different values that are further down the parameter list.
Assuming I have a function that has 4 arguments, 2 mandatory, 2 optional. I don't want to use null values for the optional arguments. In usage, there are cases where I want to use the function and the value of the 3rd argument is the same as the default value but the value of the 4th argument is different.
I am looking for a not so verbose solution that allows me to just pass the argument that differs from the default value without considering the order in the function declaration.
createUrl($host, $path, $protocol='http', $port = 80) {
//doSomething
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
I find myself repeating declaring variables so that I could use a function i.e to use $port, I redeclare $protocol with the default value outside the function scope i.e
$protocol = "http";
$port = 8080;
Is there any way to pass the 2nd optional parameter($port) without passing $protocol and it would "automatically" fill in the default value of $protocol i.e
getHttpUrl($server, $path, $port);
This is possible in some languages like Dart in the form of Named Optional parameters.See usage in this SO thread. Is their a similar solution in PHP
You could potentially use a variadic function for this.
Example:
<?php
function myFunc(...$args){
$sum = 0;
foreach ($args as $arg) {
$sum += $arg;
}
return $sum;
}
Documentation:
http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
PHP doesn't allow at this state to call functions parameters in the order we want.Maybe in the future it will.However you can easily achieve your purpose by using an associative array as the only argument, and then define, the default parameter in the function.For the call you will need to pass an array with only the values which interest you.This array will be merged with the default array.You can even implement required parameters and call them in any order you want.
example:
function mysweetcode($argument){
$required=['first'];//specify required parameters here
$default=['first'=>0,'second'=>1,'third'=>2];//define all parameters with their default values here
$missing=[];
if(!is_array($argument)) return false;
$argument=array_intersect_key($argument,$default);
foreach($required as $k=>$v){//check for missing required parameters
if(!isset($argument[$v]))
$missing[]=$v;
}
if(!empty($missing)){// if required are missing trigger or throw error according to the PHP version
$cm=count($missing);
if (version_compare(PHP_VERSION, '7.0.0') < 0) {
trigger_error(call_user_func_array('sprintf',
array_merge(array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s,',$cm).(($cm>1)?' are':' is').' missing'),$missing)),
E_USER_ERROR);
}else{
throw new Error(call_user_func_array('sprintf',array_merge(
array('Required '.(($cm>1)?'parameters:':'parameter:').
str_repeat('%s',$cm).(($cm>1)?' are':' is').' missing'),$missing)));
}
}
$default=array_merge($default,$argument);//assign given values to parameters
extract($default);/*extract the parameters to allow further checking
and other operations in the function or method*/
unset($required,$missing,$argument,$default,$k,$v);//gain some space
//then you can use $first,$second,$third in your code
return $first+$second+$third;
}
var_dump(mysweetcode(['first'=>9,'third'=>8]));//the output is 18
var_dump(mysweetcode(['third'=>8]));//this throws Error on PHP7 and trigger fatal error on PHP5
You can check a live working code here
Well, this should work:
function myFunc($arg1, $arg2, $arg3=null, $arg4= null){
if ( is_null( $arg3 ) && is_null( $arg4 ) {
$arg3 = 3;
$arg4 = 4;
} else if ( is_null( $arg4 ) ) {
$arg4 = $arg3;
$arg3 = 3;
}
echo $arg1 + $arg2 + $arg3 + $arg4;
}
However I suggest you to rethink your problem (as a whole) because this is not a very good idea.
You could refactor this to use a parameter object; this way, you could include the default parameters in this object and set them in any order (with a trade-off of more verbose code). As an example using your above code,
<?php
class AdditionParameters
{
private $arg1 = 0;
private $arg2 = 0;
private $arg3 = 3;
private $arg4 = 4;
public function getArg1() { return $this->arg1; }
public function getArg2() { return $this->arg2; }
public function getArg3() { return $this->arg3; }
public function getArg4() { return $this->arg4; }
public function setArg1($value) { $this->arg1 = $value; return $this; }
public function setArg2($value) { $this->arg2 = $value; return $this; }
public function setArg3($value) { $this->arg3 = $value; return $this; }
public function setArg4($value) { $this->arg4 = $value; return $this; }
}
From there, you could simply call the function while passing in this new object.
function myFunc(AdditionParameters $request) {
return $request->getArg1()
+ $request->getArg2()
+ $request->getArg3()
+ $request->getArg4();
}
echo myFunc((new AdditionParameters)->setArg1(1)->setArg2(2)->setArg4(6));
// or echo myFunc((new AdditionParameters)->setArg1(1)->setArg4(6)->setArg2(2));
Otherwise, PHP doesn't allow you to have named optional parameters. (e.g. myFunc(1, 2, DEFAULT, 4);)
You have the response in your question, you can declare your function like
function myFunc($arg1, $arg2, $arg3 = null, $arg4 = null){
//here you check if the $arg3 and $arg4 are null
}
then you call your function using
myFunc($arg1, $arg2);
There is no such way in PHP(like in python for example).
You have to use some tricks in order to do that but will not always work.
For example:
// creates instances of a class with $properties.
// if $times is bigger than 1 an array of instances will be returned instead.(this is just an example function)
function getInstance($class, $properties = [], $times = 1){
//my code
}
$user = getInstance("User", ["name" => "John"]); // get one instance
$users = getInstance("User", ["name" => "John"],2); // get two instances.
If you want to use the function without passing the $parameters argument, like this:
$users = getInstance("User",2);
you can change the function to:
// creates instances of a class with $properties.
// if times is bigger than 1 an array of instances will be returned instead.
function getInstance($class, $properties = [], $times = 1){
if(is_numberic($properties)){
$times = $properties;
$properties = [];
}
//my code
}
Of course, this strategy will work only if you parameters have different types.
PS. This method is use in the Laravel Framework a lot. From there I got the inspiration.
This is modified from one of the answers and allows arguments to be added in any order using associative arrays for the optional arguments
function createUrl($host, $path, $argument = []){
$optionalArgs = [
'protocol'=>'http',
'port'=>80];
if( !is_array ($argument) ) return false;
$argument = array_intersect_key($argument,$optionalArgs);
$optionalArgs = array_merge($optionalArgs,$argument);
extract($optionalArgs);
return $protocol.'://'.$host.':'.$port.'/'.$path;
}
//No arguments with function call
echo createUrl ("www.example.com",'no-arguments');
// returns http://www.example.com:80/no-arguments
$argList=['port'=>9000];
//using port argument only
echo createUrl ("www.example.com",'one-args', $argList);
//returns http://www.example.com:9000/one-args
//Use of both parameters as arguments. Order does not matter
$argList2 = ['port'=>8080,'protocol'=>'ftp'];
echo createUrl ("www.example.com",'two-args-no-order', $argList2);
//returns ftp://www.example.com:8080/two-args-no-order
As of version 8.0, PHP now has named arguments. If you name the arguments when calling the function, you can pass them in any order and you can skip earlier default values without having to explicitly pass a value for them.
For example:
function createUrl($host, $path, $protocol = 'http', $port = 80)
{
return "$protocol://$host:$port/$path";
}
createUrl(host: 'example.com', path: 'foo/bar', port: 8080);
// returns: "http://example.com:8080/foo/bar"

How can I pass a function as an optional variable to call_user_func in PHP

So I'd like to use the call_user_func to pass data to an optional parameter of a function.
Here's the example of a code, the optional parameter $data represents a functional called data that was declared in another file. I just want it to be called by using call_user_func that will set the parameter with the function's name and call it within the createtable function, but doesn't seem to work.
I got the example from the PHP Manual, but createTable contains many parameters. How can I make call_user_func only assign the string data to the optional parameter $data set to NULL as default?
function createTable($database, $table, $patch,$data = NULL)
{
echo "INFO: Adding '$table'in database '$database'.\n";
$sql = "SHOW TABLES FROM $database WHERE Tables_in_$database='$table';";
$result = mysql_query($sql);
$result_count = mysql_num_rows($result);
if ( $result_count != 1 ) {
echo "ERROR: Can not find table '$table' in database '$database'.\n";
$result = mysql_query($patch);
if ( false === $result ) {
echo "ERROR: Adding Table '$table' in database '$database' ... Failed\n";
return false;
}
else {
echo "INFO: Adding Table '$table'in database '$database' ... Success\n";
// using the optional parameter here
$data();
return true;
}
} else {
if ( $result_count == 1 ) {
echo "ERROR: Table '$table'already in database '$database'.\n";
return false;
}
}
}
// Now I'm passing value to the optional parameter $ data that is NULL as default.
call_user_func('createTable', "data");
Even with call_user_func you have to pass all the parameters.
Anyway, call_user_func is intended for use when the name of the function isn't necessarily known up front. For instance, you might have several functions and a variable, and the variable contains the name of the function to call.
Personally I think it's on par with eval and variable variables: A horrible idea. After all, if you have $foo = "function_name"; then you can call $foo() and it will call function_name.
Anyway, back to the point, just call it as a normal function and give it the parameters it needs. Pass null if you have to.
you must pass value like this
call_user_func('createTable', $database, $table, $patch,$data);
or this for call from class
call_user_func(array(&$objectName->{$anotherObject},$functionName), $arg1, $arg2, $arg2);
or you can use this can get arg as array
call_user_func_array("createTable", array("one", "two"));
or this for call from class can get arg as array
call_user_func_array(array($foo, "bar"), array("three", "four"));
or This can help you too it not need to pass all args
function __named($method, array $args = array())
{
$reflection = new ReflectionFunction( $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs( $pass);
}
I hope It Work
sample:
__named('createTable', array('data' => 'value'));
and it is for use in class
public function __named($method, array $args = array())
{
$reflection = new ReflectionMethod($this, $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs($this,$pass);
}
if you Don't set any value __named Put Null instead of Unset Value
It seems you just want to pass the last param, and not worry about the 1st three. I don't think call_user_func is the right tool here at all.
Why not just make a function that calls your function?
function call_createTable($data){
$database = '...';
$table = '...';
$patch = '...';
return createTable($database, $table, $patch, $data);
}
Then just simply call it like this: call_createTable("data");.

How to get a strings data from a different function?

I have a php file with different functions in it. I need to get data from strings in a function, but the strings have been specified in a different function. How can this be done please?
... To clarify, I have two functions.
function a($request) { $username = ...code to get username; }
the username is over retreivable during function a.
function b($request) { }
function b need the username, but cannot retrieve it at the point its called, so need it from function a. I am very much a beginer here (so bear with me please), I tried simply using $username in function b, but that didn't work.
Can you please explain how I can do this more clearly please. There are another 5 strings like this, that function b needs from function a so I will need to do this for all the strings.
...Code:
<?php
class function_passing_variables {
function Settings() {
//function shown just for reference...
$settings = array();
$settings['users_data'] = array( "User Details", "description" );
return $settings;
}
function a( $request ) {
//This is the function that dynamically gets the user's details.
$pparams = array();
if ( !empty( $this->settings['users_details'] ) ) {
$usersdetails = explode( "\n", Tool::RW( $this->settings['users_data'], $request ) );
foreach ( $usersdetails as $chunk ) {
$k = explode( '=', $chunk, 2 );
$kk = trim( $k[0] );
$pparams[$kk] = trim( $k[1] );
}
}
$email=$pparams['data_email'];
$name=$pparams['data_name'];
$username=$pparams['data_username'];
//These variables will retrieve the details
}
function b( $request ) {
//Here is where I need the data from the variables
//$email=$pparams['data_email'];
//$name=$pparams['data_name'];
//$username=$pparams['data_username'];
}
}
?>
#A Smith, let me try to clarify what you mean.
You have several variables, example : $var1, $var2, etc.
You have two function (or more) and need to access that variables.
If that what you mean, so this may will help you :
global $var1,$var2;
function a($params){
global $var1;
$var1 = 1;
}
function b($params){
global $var1,$var2;
if($var1 == 1){
$var2 = 2;
}
}
Just remember to define global whenever you want to access global scope variable accross function. You may READ THIS to make it clear.
EDITED
Now, its clear. Then you can do this :
class function_passing_variables{
// add these lines
var $email = "";
var $name = "";
var $username = "";
// ....
Then in your function a($request) change this :
$email=$pparams['data_email'];
$name=$pparams['data_name'];
$username=$pparams['data_username'];
to :
$this->email=$pparams['data_email'];
$this->name=$pparams['data_name'];
$this->username=$pparams['data_username'];
Now, you can access it in your function b($request) by this :
echo $this->email;
echo $this->name;
echo $this->username;
In the functions where the string has been set:
Global $variable;
$variable = 'string data';
Although you really should be returning the string data to a variable, then passing said variable to the other function.

CakePHP 1.3 paginate with custom query

I've managed to over-ride the default methods for a custom query in my model as suggested elsewhere,
function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array())
and
function paginateCount($conditions = null, $recursive = 0, $extra = array())
Unfortunately this approach over-rides all pagination for this model, and affects other pagination elsewhere. I found some code which may help where I could select whether I wanted the custom pagination used based on a variable e.g.
In my model
var $useCustom = false;
function paginateCount($conditions = null, $recursive = 0, $extra = array())
{
if(!$this->useCustom)
return parent::paginateCount($conditions, $recursive);
// code to handle custom paginate count here
}
I have found that using this method gives me an error,
Fatal error: Call to undefined method
AppModel::paginateCount() in....
What am I doing wrong? I assume that I would also need similar code in the paginate function as well? Am I also correct in thinking that I can set this variable in my controller i.e. $this->useCustom = 'true';
After a bit of delving into the code I found that the methods of paginateCount and paginate do not exist in the Model, or anywhere else for that matter, which is why I could not call them. The solution was copy the code from the main controller, which tests for the existence of the over-ride
For those that would like a similar solution use the following in paginateCount
if(!$this->useCustom)
{
$parameters = compact('conditions');
if ($recursive != $this->recursive) {
$parameters['recursive'] = $recursive;
}
$count = $this->find('count', array_merge($parameters, $extra));
return $count;
} else {
custom method...
}
and in paginate use
if(!$this->useCustom)
{
$parameters = compact('conditions', 'fields', 'order', 'limit', 'page');
if ($recursive != $this->recursive) {
$parameters['recursive'] = $recursive;
}
$results = $this->find('all', array_merge($parameters, $extra));
return $results;
} else {
custom method...
}
Hope this helps someone else.
I think you need the public keyword before your function to use the scope resolution operator in this way.
i.e. public function paginateCount(....

Static method, Zend_View error

I have a static method 'findAll' on a model which basically gets all rows with certain criteria. This method works fine and I can call it using:
$m::findAll();
Where $m is the model name as a variable. I can output this and it returns correct results. However, when assigning this to a variable in the Zend_View object, as:
$this->view->viewvariable = $m::findAll();
I get the error:
Zend_Db_Table_Exception: Too many
columns for the primary key
Any ideas why?
Find all function:
final public static function findAll($where = false, array $options = array()) {
$object = new static();
if (!empty($options)) $options = array_merge($object->options, $options);
else $options = $object->options;
$run = $object->buildDefaultSelect($where, $options);
$rows = $run->fetchAll();
if ($options['asObject'] == true) {
$result = array();
foreach ($rows as $r) {
$class = new static();
$class->setInfo($r);
$result[] = $class;
}
return $result;
} else {
if (count($rows) > 0) return $rows;
else return array();
}
}
Note: This function works fine everywhere apart from when assigning to a view variable. If I run the below (not assigning it to a view variable), it shows the correct array data.
var_dump($m::findAll($module['where'], $module['options']));
exit;
In my view (I have replaced the actual name with viewvariable for the sake of this post):
<?php foreach($this->viewvariable as $item) { ?>
//Do some echoing of data in $item
//Close foreach
I doubt the issue is with Zend_View. It's hard to tell without seeing your code, but my guess is that findAll() is using the Zend_Table_Db find() function incorrectly.
To my knowledge, the only place that throws that exception is the find() function on Zend_Db_Table_Abstract.
Perhaps, inside the findAll() function (or in a function it calls) you're doing one of these:
$zendDbTable->find(1,2) //is looking for a compound key
$zendDbTable->find(array(1,2)) //is looking for two rows
When you really want the opposite.

Categories