I just found something "kinda strange" about PHP 7.4 and I am not sure if it is just me missing something or maybe if it is an actual bug. Mostly I am interested in your opinion/confirmation.
So in PHP, you can iterate over objects properties like this:
class DragonBallClass
{
public $goku;
public $bulma = 'Bulma';
public $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
RESULT
NULL
string(5) "Bulma"
string(6) "Vegeta"
Now if we start using strongly typed properties like that:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
We will get a different result:
string(5) "Bulma"
string(6) "Vegeta"
Now what is different:
When you DO NOT assign a default value to strongly typed property it will be of Uninitialized type. Which of course makes sense. The problem is though that if they end up like this you cannot loop over them they will simply be omitted - no error, no anything as you can see in the second example. So I just lose access to them.
It makes sense but just imagine that you have a custom Request/Data class like this:
namespace App\Request\Request\Post;
use App\Request\Request\Request;
class GetPostsRequest extends Request
{
public string $title = '';
}
Do you see that ugly string assignment? If I want to make my properties on the class iterable then I have to either:
drop types
assign dummy values
I might want to have an object with typed properties without any values in them to loop over them and populate them if that makes sense.
Is there any better way of doing this? Is there any option to keep types and keep em iterable without having to do this dummy value abomination?
If you want to allow a typed attribute to be nullable you can simply add a ? before the type and give NULL as default value like that:
class DragonBallClass
{
public ?string $goku = NULL;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
In this case NULL is a perfectly legitimate value (and not a dummy value).
demo
Also without using ?, you can always merge the class properties with the object properties lists:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
$classProperties = get_class_vars(get_class($dragonBall));
$objectProperties = get_object_vars($dragonBall);
var_dump(array_merge($classProperties, $objectProperties));
// array(3) {
// ["goku"]=>
// NULL
// ["bulma"]=>
// string(5) "Bulma"
// ["vegeta"]=>
// string(6) "Vegeta"
// }
Before we start - I think that the answer accepted by me and provided by Casimir is better and more correct than what I came up with(that also goes for the comments).
I just wanted to share my thoughts and since this is a working solution to some degree at least we can call it an answer.
This is what I came up with for my specific needs and just for fun. I was curious about what I can do to make it more the way I want it to be so don't freak out about it ;P I think that this is a quite clean workaround - I know it's not perfect though.
class MyRequest
{
public function __construct()
{
$reflectedProperties = (new \ReflectionClass($this))->getProperties();
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
}
}
class PostRequest extends MyRequest
{
public ?string $title;
}
$postRequest = new PostRequest();
// works fine - I have access here!
foreach($postRequest as $property) {
var_dump($property);
}
The downfall of this solution is that you always have to make types nullable in your class. However for me and my specific needs that is totally ok. I don't mind, they would end up as nullables anyway and it might be a nice workaround for a short deadline situation if someone is in a hurry.
It still keeps the original PHP not initialized error though when the type is not nullable. I think that is actually kinda cool now. You get to keep all the things: Slim and lean classes, PHP error indicating the true nature of the problem and possibility to loop over typed properties if you agree to keep them nullable. All governed by native PHP 7 nullable operator.
Of course, this can be changed or extended to be more type-specific if that makes any sense.
Update: this answer may be obsolete, but the comments contain an interesting discussion.
#Robert's workaround is buggy; in this part:
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
the ?? must be corrected to &&.
Moreover that's a misuse of the ternary conditional; just use a classic if:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this)
&& $property->getType()->allowsNull()
) {
$property->setValue($this, null);
}
}
or:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this) && $property->getType()->allowsNull()) {
$property->setValue($this, null);
}
}
Probably not what you want, but you could use reflection:
<?php
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$ob = new DragonBallClass;
$reflector = new ReflectionClass($ob);
foreach($reflector->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
echo $prop->name, ':', ($ob->{$prop->name} ?? 'NOT INITIALISED'), "\n";
}
Output:
goku:NOT INITIALISED
bulma:Bulma
vegeta:Vegeta
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 $mysettings can be true while we are initializing it with null? is this a method to prevent SQL injection? It would be appreciated if you could explain the code below.
public function __construct($mysettings = null)
{
$this->shop_version = Mage::getVersion();
$this->moduleversion = Mage::getConfig()->getModuleConfig('Messagemodule')->version;
$this->apppid = Mage::getStoreConfig('magemessage/appId');
if (empty($this->apppid)) {
$this->apppid = 'no-appId';
}
$this->connectortype = ($settingvariable = Mage::getStoreConfig('Messagemodule/magemessage/connector', 0)) ? $settingvariable : 'auto';
if ($mysettings) {
$this->connectortype = $mysettings;
}
}
When you specify a default value in a PHP method (including a constructor), that's all it is - a default.
So if you have
class Foo {
public function __construct($mysettings = null) {
...
}
}
then you are providing two ways of constructing the class. You can either call
$foo = new Foo();
with no arguments, in which case $mysettings will be initialised to null. Or you can call
$settings = array('key' => 'value');
$foo = new Foo($settings);
in which case the $settings array will be passed into the new instance. The benefit this provides is that you don't need to provide an empty array to new instances for which you don't need custom settings; you can just omit the argument.
The check if ($mysettings)... in the class ensures that the settings are only used if they are provided - a PHP if statement can operate on lots of different types, not just booleans. In this case, if the variable is null, the condition will evaluate to false.
Have a look at this code:
<?php
function required($something)
{
echo $something;
}
required();
It throws a fatal error, because $something was required, but not passed. https://3v4l.org/fIKB9
Now look here:
<?php
function required($something = 'hello')
{
echo $something;
}
required();
required(' R.Toward');
Which outputs Hello R.Toward https://3v4l.org/nQF8r
So in essence, it is a way of setting a default optional value.
I found a php class in the internet that uses array $options = [] in method argument:
class TADFactory
{
private $options;
public function __construct(array $options = [])
{
$this->options = $options;
}
//some other methods here
}
and in page.php file
$tad_factory = new TADFactory(['ip'=>'192.168.0.1']);
//some other stuffs here
But after executing the page.php file in the browser, it is showing:
Unexpected `[` in page.php file at line 1, expecting `)`....
But according to the php library documentation, I have to use the multidimensional array in argument by that way.
I could not understand what does it mean by array $options = [] in TADFactory class argument and why the error is throwing?
That is a default argument value. It is how you declare that a parameter is optional, and, if not provided, what value it should have by default.
function add($x, $y = 5) {
return $x + $y;
}
echo add(5, 10); // 15
echo add(7); // 12
As for the array annotation, that is a type hint (also called a type declaration), which means that you must pass the function an array or it will throw an error. Type hinting is fairly complicated and debatably necessary in a dynamic language, but it's probably worth knowing about.
function sum(array $nums) {
return array_sum($nums);
}
echo sum([1, 2, 3]); // 6
echo sum(5); // throws an error
NOTE: You can only combine type hints with default argument values if your default argument value is null.
In javascript I can pass an object literal to an object as a parameter and if a value does not exist I can refer to a default value by coding the following;
this.title = params.title || false;
Is there a similar way to do this with PHP?
I am new to PHP and I can't seem to find an answer and if there is not an easy solution like javascript has, it seems pure crazy to me!!
Is the best way in PHP to use a ternary operator with a function call?
isset($params['title']) ? $params['title'] : false;
Thanks
Don't look for an exact equivalent, because PHP's boolean operators and array access mechanism are just too different to provide that. What you want is to provide default values for an argument:
function foo(array $params) {
$params += array('title' => false, ...);
echo $params['title'];
}
somethig like this $title = (isset($title) && $title !== '') ? $title : false;
Or using the empty function:
empty($params['title']) ? false : $params['title'];
$x = ($myvalue == 99) ? "x is 99": "x is not 99";
PHP one liner if ...
if ($myvalue == 99) {x is 99} else {x is not 99 //set value to false here}
<?php
class MyObject {
// Default value of object property
public $_title = null;
// Default value of argument (constructor)
public function __construct($title = null){
$this->_title = $title;
}
// Default value of argument (setter)
public function setTitle($title = null){
// Always validate arguments if you're serious about what you're doing
if(!is_null($title) and !is_string($title)){
trigger_error('$title should be a null or a string.', E_USER_WARNING);
return false;
}
$this->_title = $title;
return true;
}
} // class MyObject;
?>
This is how you do an object with default values. 3 ways in 1. You either default the property value in the class definition. Or you default it on the __construct assignment or in a specific setter setTitle.
But it all depends on the rest of your code. You need to forget JS in order to properly use PHP. This is a slightly stricter programming environment, even if very loose-typed. We have real classes in PHP, not imaginary function classes elephants that offer no IDE code-completion support like in JS.