Consider this scenario where I want to SOMETIMES pass data through to my view:
public function show($orderId)
{
if ($sometimesTrue == true)
{
$optionalParameter = 'optional parameter';
}
return view('show', compact([$a, $b, $c, $optionalParameter]));
}
$optionalParameter is not always set, so I want to know what my options are for setting individual, optional view parameters without re-arranging the structure of the function.
In Zend, the following is possible:
$this->view->optionalParameter = $optionalParameter;
Which can go anywhere in the controller method, not just at the end where the view is instantiated. Of course, back in Laravel, I could do something this:
public function show($orderId)
{
$paramaterArray = [$a, $b, $c];
if ($sometimesTrue == true)
{
$optionalParameter = 'optional parameter';
$paramaterArray[] = $optionalParameter;
}
return view('show', compact($paramaterArray));
}
But re-arranging entire functions because a optional parameter is introduced seems a bit limiting. Is there any way I can set an individual parameter for a view?
You can just built your own protected function + protected property in the Controller class. You could do something like this:
Beneath is using the splat operator so it will only work in php 5.6 >=
protected $optionalParameter;
protected function optionalcompact(...$parameters) {
if(!empty($this->optionalParameter)){
return compact($parameters, $this->optionalParameter);
} else {
return compact($parameters);
}
}
Then back in your own built controller class you can do this:
public function show($orderId)
{
if ($sometimesTrue == true)
{
$this->optionalParameter = 'optional parameter';
}
return view('show', $this->optionalcompact($a, $b, $c));
}
I would simply use the following
public function show($orderId)
{
$paramaterArray = [$a, $b, $c];
$paramaterArray['optional'] = $sometimesTrue == true ? 'optional parameter' : '';
return view('show', $paramaterArray);
}
Since this is an optional parameter so I don't need to check every time in my view whether it's set or not, simply {{ $optional }} will work better, it'll be printed if any value is set or nothing will be printed if the $optional variable is empty. This way, I'll remain consistant.
Also, you may check the Larave's View Composers, it may help you.
Researching further, it seems that using compact with the names of the variables (as opposed to the variables themselves) will silently ignore the missing variable which gives the intended behaviour:
$var1 = 'bob';
$var2 = 'nigel';
var_dump(compact('var1', 'var2', 'var3'));
Returns:
array(2) { ["var1"]=> string(3) "bob" ["var2"]=> string(5) "nigel" }
Which works perfectly for my scenario. Using it this way, the controller method doesn't require refactoring and no additional coding is required. Whether relying on compact to not issue a warning is good coding practice is another question.
Just to confirm, calling compact with the variables themselves WILL throw a notice warning:
$var1 = 'bob';
$var2 = 'nigel';
var_dump(compact($var1, $var2, $var3));
Returns:
NOTICE Undefined variable: var3 on line number 4
Related
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
I'm aware that you can have PHP functions with optional arguments like so:
function do_something($argument = null)
{
// argument not provided
if ($argument === null)
........
}
Consider the case that null/false and other values are all valid arguments to my function. How can I determine whether an argument was provided or not?
do_something(null); // argument provided
do_something(false); // argument provided
do_something(0); // argument provided
do_something("test"); // argument provided
do_something(new stdClass()); // argument provided
do_something(); // nothing provided
How can I detect the last case? I have thought about using func_num_args which would work in most cases but it doesn't work if I have several optional arguments.
Is there anything that solves this problem?
func_num_args() should work exactly as you want it to, because you might be assuming something that's actually not the case: You can't have optional arguments left out if they are in the middle of your arguments list.
So let's look at this function:
function test1 ($param1 = null, $param2 = null) {
return func_num_args();
}
If I call that with different parameter combinations I get the following results:
test1() => 0
test1(true) => 1
test1(true, true) => 2
There is just no way to call the function in a way where $param2 would be set while $param1 isn't. So you can map every possible output of func_num_args() to exactly one parameter configuration.
In the example above you can rely on the fact that
if the return value is 1, $param2 definitely hasn't been set, while $param1 has been.
For 0 it's 100% sure that neither one has been given.
And, of course, if it's 2 both are there.
What you actually would need are named parameters, as many other languages have them. PHP doesn't at the moment. NikiC actually wrote an RFC that suggests the addition of named parameters to PHP, but I think that's still way off in the future. You can check that out here: https://wiki.php.net/rfc/named_params
As these are not yet available, here are a few workarounds you can try:
Workaround 1
If you really need to be able to have all the parameters optional, try a parameter array:
function test1 (array $opts) {
if (!isset($opts['opt1'])) { $opts['opt1'] = 'default1'; }
if (!isset($opts['opt2'])) { $opts['opt2'] = 'default2'; }
}
Then you can call it like this:
test1(array('opt2' => true))
It would set the first parameter to "default1" while keeping the second. And there are definitely better and more elegant ways to do this (e.g. using an object instead), but the general idea is the same.
Workaround 2
You could also go with alias functions:
function test ($param1, $patam2) { ... ]
function testNoParam1 ($param2) {
test("default1", $param2);
}
That at least makes it very easy to read, but of course you need to pick the right function depending on the parameters you have.
Workaround 3
By adding a lot of additional code you could get really fancy and use a FactoryObject:
class FunctionExecutor {
private $param1 = "default1";
private $param2 = "default2";
public function param1($val) {
$this->param1 = $val;
return $this;
}
public function param2($val) {
$this->param2 = $val;
return $this;
}
public function execute() {
return yourFunction($this->param1, $this->param2);
}
}
This could be used like this:
$doSomething = new FunctionExecutor();
$returnValue = $doSomething->param2(42)->execute();
In this approach it would probably be a better idea to actually put your function into the object instead of defining it globally. Anyway...this is definitely a possibility, but not the most practical one. Just wanted to add it, because it has some benefits.
perhaps this will help: http://www.php.net//manual/en/function.func-get-args.php
$args = func_get_args();
if(!isset($arg[0])) {
echo 'no argument';
}
or
isset(func_get_arg(0));
Passing "null", "0", or "false" means that you allocate memory to store a variable, regardless it's scope, type, or size. Then it is used as a parameter to a function.
In PHP you cannot override functions by arguments, but you can access them by calling the "func_get_args()", and this is the only way to handle different numbers / types of arguments passed to a function:
function do_something() {
$args = func_get_args();
//do_something(stdClass, 1)
if($args[0] instanceof stdClass && is_numeric($args[1])) {
//handle
//return
//do_something(1, "string")
} else if(is_numeric($args[0]) && is_string($args[1])) {
//handle
//return
}
throw new Exception('invalid arguments');
}
do_something(new StdClass(), 100); //ok
do_something(100, "hell world") // ok
do_someting(); //throws Exception('invalid arguments');
In PHP 7, you can do:
function action(...$args) {
if (count($args) === 0) {
return action_default();
}
$var1 = array_shift($args);
$var2 = array_shift($args);
$var3 = array_shift($args);
// etc.
}
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.
I have lots of code like this in my constructors:-
function __construct($params) {
$this->property = isset($params['property']) ? $params['property'] : default_val;
}
Some default values are taken from other properties, which was why I was doing this in the constructor. But I guess it could be done in a setter instead.
What are the pros and cons of this method and is there a better one?
Edit: I have some dependencies where if a property is not supplied in the $params array then the value is taken from another property, however that other property may be optional and have a default value, so the order in which properties are initialized matters.
This means that if I used getters and setters then it is not obvious which order to call them in because the dependencies are abstracted away in the getter instead of being in the constructer...
I would suggest you, to write proper getter/setter functions, which assert you the correct data-type and validations (and contain your mentioned default-value logic). Those should be used inside your constructor.
When setting multiple fields, which depend on each other, it seems to be nice to have a separate setter for this complex data. In which kind of way are they depending anyway?
e.g.:
// META-Config
protected $static_default_values = array(
"price" => 0.0,
"title" => "foobar"
// and so on
);
protected $fallback_getter = array(
"price" => "getfallback_price"
);
// Class Logic
public function __construct($params){
$this->set_properties($params);
}
public set_properties($properties){
// determines the sequence of the setter-calls
$high_prio_fields = array("price", "title", "unimportant_field");
foreach($high_prio_fields as $field){
$this->generic_set($field, $properties[$field]);
// important: unset fields in properties-param to avoid multiple calls
unset($properties[$field]);
}
foreach($properties as $field => $value){
$this->generic_set($field, $value);
}
}
// this could also be defined within the magic-setter,
// but be aware, that magic-functions can't be resolved by your IDE completely
// for code-completion!
private function generic_set($field, $value){
// check if setter exists for given field-key
$setter_func = "set_".$v;
if(method_exists($this, $setter_func){
call_user_func_array(array($this, $setter_func), array($v));
}
// else => just discard :)
}
// same comment as generic-set
private function generic_get($field){
// check if value is present in properties array
if(isset($this->properties[$field]){
return $this->properties[$field];
}
// check if fallback_getter is present
if(isset($this->fallback_getter[$field]){
return call_user_func_array(array($this, $this->fallback_getter[$field]));
}
// check for default-value in meta-config
if(isset($this->static_default_values[$field]){
return $this->static_default_values[$field];
}
// else => fail (throw exception or return NULL)
return null;
}
public function get_price(){
// custom getter, which ovverrides generic get (if you want to)
// custom code...
return $this->generic_get("price");
}
private function getfallback_price(){
return $this->properties["other_value"] * $this->properties["and_another_value"];
}
public function set_price($price){
$price = (float) $price; // convert to correct data-type
if($price >= 0.0){
$this->properties["price"] = $price;
}
// else discard setting-func, because given parameter seems to be invalid
// optional: throw exception or return FALSE on fail (so you can handle this on your own later)
}
Update to your edit:
the modified source-code should solve all your demands (order of setter-funcs, different resolvings of get-value).
Create "globally available" function array_get.
public static function array_get($array, $property, $default_value = null) {
return isset($array[$property]) ? $array[$property] : $default_value;
}
When having a lot of default options and you need to be able to overwrite them - as you have maybe seen in jQuery using .extend() before - I like to use this simple and quick method:
class Foo {
private $options;
public function __construct($override = array()) {
$defaults = array(
'param1' => 'foo',
'param2' => ...,
'paramN' => 'someOtherDefaultValue');
$this->options= array_replace_recursive($defaults, $override);
}
}
Especially for getting classes started this is a very easy and flexible way, but as already has been mentioned if that code is going to be heavily used then it probably not a bad idea to introduce some more control over those options with getters and setters, especially if you need to take actions when some of those options are get or set, like in your case dependencies if I understood your problem correctly.
Also note that you don't have to implement getters and setters yourself, in PHP you can use the __get and __set magic methods.
It follows some useless code that hopefully gives some ideas:
[...inside Foo...]
public function __set($key, $value){
switch(true){
//option exists in this class
case isset($this->options[$key]):
//below check if $value is callable
//and use those functions as "setter" handlers
//they could resolve dependencies for example
$this->options[$key] = is_callable($value) ? $value($key) : $value;
break;
//Adds a virtual setter to Foo. This so called 'magic' __set method is also called if the property doesn't exist in the class, so you can add arbitrary things.
case $key === 'someVirtualSetterProp': Xyzzy::Noop($value); break;
default:
try{ parent::__set($key, $value); } catch(Exception $e){ /* Oops, fix it! */ }
}
}
Note that in the above examples I squeezed in different approaches and it usually doesn't make sense to mix them like that. I did this only to illustrate some ideas and hopefully you will be able to decide better what suits your needs.
I find in my PHP pages I end up with lines and lines of code that look like this:
$my_id = isset($_REQUEST['my_id']) ? $_REQUEST['my_id'] : '';
$another_var = isset($_REQUEST['another_var']) ? $_REQUEST['another_var'] : 42;
...
Is there a better, more concise, or more readable way to check this array and assign them to a local variable if they exist or apply a default if they don't?
EDIT: I don't want to use register_globals() - I'd still have the isset problem anyway.
How about wrapping it in a function?
<?php
function getPost($name, $default = null) {
return isset($_POST[$name]) ? $_POST[$name] : $default;
}
a better method might be to create a singleton/static class to abstract away the details of checking the request data.
Something like:
class Request {
private $defaults = array();
private static $_instance = false;
function getInstance () {
if (!self::$_instance) {
$c = __CLASS__;
self::$_instance = new $c;
}
return self::$_instance;
}
function setDefaults($defaults) {
$this->defaults = $defaults;
}
public function __get($field) {
if (isset($_REQUEST[$field]) && !empty($_REQUEST[$field])) {
return $_REQUEST['field'];
} elseif (isset($this->defaults[$field])) {
return $this->defaults[$field];
} else {
return ''; # define a default value here.
}
}
}
you can then do:
# get an instance of the request
$request = Request::getInstance();
# pass in defaults.
$request->setDefaults(array('name'=>'Please Specify'));
# access properties
echo $request->name;
echo $request->email;
I think this makes your individual scripts loads cleaner and abstracts away the validation etc. Plus loads of scope with this design to extend it/add alternate behaviours, add more complicated default handling etc etc.
First, use $_POST for POSTed variables. $_REQUEST is a mashup of many different incoming variables, not just $_POST and could cause problems.
One solution for your question would be to create a function that handles the isset() logic.
function ForceIncomingValue($Key, $Default) {
if (!isset($_POST[$Key]))
return $Default;
else return $_POST[$Key];
}
first of all, NEVER use the $_REQUEST variable, it'll lead to bugs and other problems during development
function getPOST($key) {
if(isset($_POST[$key])) {
return $_POST[$key];
}
}
note that this code leaves the variable empty when $_POST[$key] was not set
you could also adapt that code to enable you to instead provide you with a (sensible) default when the value could not be loaded.
function getPOST($key, $default = NULL) {
if(isset($_POST[$key])) {
return $_POST[$key];
} else {
return $default;
}
}
Is the set of variables you're expecting known at the time of the script's writing, or do you want to do this for an arbitrary set of values? If the former is true, you could do something like this:
# This array would hold the names of all the variables you're expecting
# and a default value for that variable name
$variableNames = array (...);
foreach ($variableNames as $key => $default) {
if (isset ($_REQUEST[$key])) $$key = $_REQUEST[$key];
else $$key = $default;
}
Basically, this takes advantage of PHP's ability to evaluate variables to create other variables (hence the double-dollar for $$key--this means create a new variable whose name is the value of $key).
I haven't yet come up with a good solution to the latter situation.
PHP's null coalescing operator!
$username = $_GET['user'] ?? 'nobody';
For a lot of variables, with a requirement check, anyone is free to use my expect function.