What am I doing wrong with my optional PHP arguments? - php

I have a the following class:
class MyClass {
public function __construct($id = 0, $humanIdentifier = '') {
$this->id = $id;
$this->humanID = $humanIdentifier;
}
}
So from my interpretation I should be able to pass either $id or $humanIdentifier to that constructor, neither or both if I wanted. However, when I call the code below I am finding that its the $id in the constructor args being set to hello world and not the $humanIdentifier, despite me specifying the $humanIdentifier when calling the constructor. Can anyone see where I am going wrong?
$o = new MyClass($humanIdentifier='hello world');

Edit: As of PHP8, named arguments are now supported. This wasn’t the case at the time of this post.
PHP does not support named arguments, it will set the value according to the order in which you pass the parameters.
In your case, you're not passing $humanIdentifier, but the result of the expression $humanIdentifier='hello world', to which $this->id is later set.
The only way I know to mimick named arguments in PHP are arrays. So you could do (in PHP7) :
public function __construct(array $config)
{
$this->id = $config['id'] ?? 0;
$this->humanId = $config['humanId'] ?? '';
}

Can anyone see where I am going wrong?
Yes, you think these are named parameters. They are not. They are positional parameters. So you'd call it like this:
new MyClass(0, 'hello world')
Adding support for named parameters has been suggested and rejected in the past. A newer RFC is proposed, but it still is to be refined and implemented.

You need to overload the constructor, but php does not have built-in functionality for it but there's a great workaround for it in documentation:
http://php.net/manual/en/language.oop5.decon.php#Hcom99903
Also here's a discussion why it might be a bad idea: Why can't I overload constructors in PHP?

like another answer said, php does not support named arguments. You can accomplish something similar with:
class MyClass {
public function __construct($args = array('id' => 0, 'humanIdentifier' => '') {.
// some conditional logic to emulate the default values concept
if(!isset($args['id'])){
$this->id = 0;
}else{
$this->id = $args['id'];
}
if(!isset($args['humanIdentifier'])){
$this->humanID = '';
}else{
$this->humanID = $args['humanIdentifier'];
}
}
}
you can then call it like:
new MyClass(array('humanIdentifier'=>'hello world'));
and the default id will be there. I am sure you can come up with some fancy iteration to accomplish this if there are enough parameters to make it worth while.

You can not create new object of class by this way:
$o = new MyClass($humanIdentifier='hello world');
You can use array as parameter of __construct:
class MyClass {
public function __construct(array $arg) {
$this->id = isset($arg['id']) ? $arg['id'] : 0;
$this->humanID = isset($arg['humanID']) ? $arg['humanID'] : 0;
}
}
Then you can create new object of class by this way:
$o = new MyClass(['humanId'=>hello world']);

Related

Assign value directly in php constructor with a external function

Can I assign to a property a value in the constructor, without define any parameter, using for instance an external function?
Example
function my_external_function() {
return 'Good Morning World';
}
class MyClass {
protected $_my_property;
public function __construct() {
$this->_my_property = my_external_function() != '' ? my_external_function() : 'Good night World!';
}
public function getOtherMethod() {
return $this->_my_property;
}
}
$obj = new MyClass();
echo $obj->getOtherMethod();
You can do this. The code in your question will work, but there is a problem with this approach.
If you write your class this way, it will always depend on that external function, but it will have no control over whether or not it even exists, let alone whether or not it will return a value the constructor can use. If you move, rename, or modify the external function, it could change the behavior of your class in unpredictable ways.
I would recommend something like this instead, which I think may accomplish what you're trying to accomplish (not sure) without forcing your class to blindly depend on an external function.
class MyClass {
protected $_my_property = 'Good night World!'; // set a default value here
public function __construct($x = null) { // give your constructor an optional argument
if ($x) { // use the optional argument if it's provided
$this->_my_property = $x;
}
}
public function getOtherMethod() {
return $this->_my_property;
}
}
You can still create an instance of your class with no argument
$obj = new MyClass();
and when you call $obj->getOtherMethod(); you'll get the default value.
You can still use the external function; just let it pass its value into your object's constructor instead of using it within the constructor.
$obj = new MyClass(my_external_function());
Yes, but you better avoid such tricky dependencies.

Get PHP callable arguments as an array?

Say I have a callable stored as a variable:
$callable = function($foo = 'bar', $baz = ...) { return...; }
How would I get 'bar'?
if (is_callable($callable)) {
return func_get_args();
}
Unfortunately func_get_args() is for the current function, is it possible to get a key value pair of arguments?
You can use reflection:
$f = new ReflectionFunction($callable);
$params = $f->getParameters();
echo $params[0]->getDefaultValue();
You may want to use get_defined_vars to accomplish this, this function will return an array of all defined variables, specifically by accessing the callable index from the output array.
I came across this question because I was looking for getting the arguments for a callable which is not just the function itself. My case is
class MyClass{
public function f(){
// do some stuff
}
}
$myclass = new MyClass();
$callable = array($myclass, "f);
This is a valid callback in php. In this case the solution given by #Marek does not work.
I worked around with phps is_callable function. You can get the name of the function by using the third parameter. Then you have to check whether your callback is a function or a (class/object) method. Otherwise the Reflection-classes will mess up.
if($callable instanceof Closure){
$name = "";
is_callable($callable, false, $name);
if(strpos($name, "::") !== false){
$r = new ReflectionMethod($name);
}
else{
$r = new ReflectionFunction($name);
}
}
else{
$r = new ReflectionFunction($callable);
}
$parameters = $r->getParameters();
// ...
This also returns the correct value for ReflectionFunctionAbstract::isStatic() even though the $name always uses :: which normally indicates a static function (with some exceptions).
Note: In PHP>=7.0 this may be easier using Closures. There you can do someting like
$closure = Closure::fromCallable($callable);
$r = new ReflectionFunction($closure);
You may also cause have to distinguish between ReflectionFunction and ReflectionMethod but I can't test this because I am not using PHP>=7.0.

Passing a reference using call_user_func_array with variable arguments

I have the following code:
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
call_user_func_array(array('Testme', 'foo'), array(&$test));
var_dump($test);
And correctly displays "1". But I want to do the same, using an "Invoker" method, like the following:
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
class Invoker {
public static function invoke($func_name) {
$args = func_get_args();
call_user_func_array(array('Testme', $func_name), array_slice($args,1));
}
}
$test = 2;
Invoker::invoke('foo', $test);
var_dump($test);
This throws a strict standards error (PHP 5.5) and displays "2"
The question is, is there a way to pass arguments by reference to Testme::foo, when using func_get_args()? (workarounds are welcome)
There is no way to get a reference out of func_get_args() because it returns an array with a copy of the values passed in. See PHP Reference.
Additionally, since runtime pass by reference is no longer supported, you must denote the reference in each method/function signature. Here is an example that should work around the overall issue of having an Invoker that does pass by reference, but there is no work around for func_get_args().
<?php
class Testme {
public static function foo(&$ref) {
$ref = 1;
}
}
class Invoker {
public static function invoke($func_name, &$args){
call_user_func_array(array('Testme', $func_name), $args);
}
}
$test = 10;
$args[] = &$test;
Invoker::invoke('foo', $args);
var_dump($test);
If you know you want to invoke by reference, this can work for you and perhaps have two invokers, one Invoker::invokeByRef an another normal Invoker::invoke that does the standard invoking by copy.
It is not possible because func_get_args() returns copy value not reference. But there is an ugly workaround for it by using many optional parameters.
class Testme {
public static function foo(&$ref, &$ref2) {
$ref = 1;
$ref2 = 2;
}
}
class Invoker {
public static function invoke($func_name,
&$arg1 = null, &$arg2 = null, &$arg3 = null,
&$arg4 = null, &$arg5 = null, &$arg6 = null)
{
$argc = func_num_args();
$args = array();
for($i = 1; $i < $argc; $i++) {
$name = 'arg' . $i;
if ($$name !== null) {
$args[] = &$$name;
}
}
call_user_func_array(array('Testme', $func_name), $args);
}
}
$test = 5;
$test2 = 6;
Invoker::invoke('foo', $test, $test2);
var_dump($test);
var_dump($test2);
The problem
This is not possible to do easily because func_get_args does not deal in references, and there is no alternative that does.
The idea
If you are willing to limit yourself to a maximum known number of arguments and don't mind working with the dark arts, there is a horrible workaround that I believe works correctly in all cases.
First, declare the invoker as accepting an able number of parameters, all of them by reference and having default values (the exact default does not really matter):
public static function invoke(callable $callable, &$p1 = null, &$p2 = null, ...);
Then, inside invoke determine what type of callable you are dealing with. You need to do this in order to create an appropriate instance of ReflectionFunctionAbstract that describes the invocation target. This is important because we absolutely need to determine how many parameters the target requires, and it also enables amenities like detecting a call with an incorrect number of arguments.
After assembling an array of arguments, use call_user_func_array like you were intending to in the first place.
This approach is based on the same idea that invisal uses, but there is an important difference: using reflection allows you to always correctly determine how many arguments to pass (invisal's solution uses a guard value), which in turn does not limit the values that can be passed to the invocation target (with invisal's solution you cannot ever pass the guard value to the invocation target as a legitimate parameter).
The code
public static function invoke(callable $callable, &$p1 = null, &$p2 = null)
{
if (is_string($callable) && strpos($callable, '::')) {
// Strings are usually free function names, but they can also
// specify a static method with ClassName::methodName --
// if that's the case, convert to array form
$callable = explode('::', $callable);
}
// Get a ReflectionFunctionAbstract instance that will give us
// information about the invocation target's parameters
if (is_string($callable)) {
// Now we know it refers to a free function
$reflector = new ReflectionFunction($callable);
}
else if (is_array($callable)) {
list ($class, $method) = $callable;
$reflector = new ReflectionMethod($class, $method);
}
else {
// must be an object -- either a closure or a functor
$reflector = new ReflectionObject($callable);
$reflector = $reflector->getMethod('__invoke');
}
$forwardedArguments = [];
$incomingArgumentCount = func_num_args() - 1;
$paramIndex = 0;
foreach($reflector->getParameters() as $param) {
if ($paramIndex >= $incomingArgumentCount) {
if (!$param->isOptional()) {
// invocation target requires parameter that was not passed,
// perhaps we want to handle the error right now?
}
break; // call target will less parameters than it can accept
}
$forwardedArguments[] = &${'p'.(++$paramIndex)};
}
return call_user_func_array($callable, $forwardedArguments);
}
See it in action.

Making PHP code shorter

I have found that writing PHP code within classes can become rather long:
$this->parser->parse_syntax($this->get_language_path($this->language),
$this->elements,
$this->regex);
For example, in Java a class instance can call its methods by name without referencing this, is there a way to achieve shorter code in PHP without using this and self every time you have to read a value?
No, in PHP if you want to run object's method or read/write property from inside of object you must write $this to refer that object instance.
PS. You can do at the beginning of each method something like this: $s = $this and use $s then, but it's strongly unrecommended, you shouldn't do that and it will be best, if you forget that you read this paragraph :)
I think it all comes down to your data structure, for instance your parse_syntax() method can default to $this->elements and $this->regex if null is passed instead:
class foo
{
public $parser = null;
public $language = null;
public $elements = null;
public $regex = null;
public function __construct()
{
$this->parser = new parser();
}
public function get_language_path($language = null)
{
$language = (isset($language)) ? $language : $this->language;
// your code here
}
}
class parser extends foo
{
public function parse_syntax($path = null, $elements = null, $regex = null)
{
$path = (isset($path)) ? $path : parent::get_language_path();
$elements = (isset($elements)) ? $elements : $this->elements;
$regex = (isset($regex)) ? $regex : $this->regex;
// your code here
}
}
Yes, Java does not always need this, it also does not need the $ in front of variables, and you use just one dot (.) instead of -> to reference object's variable.
But Python is still shorter than Java - you don't need to use braces in Python, then use Python.
Scala also much shorter than Java, so if you really need to write less code, then php is not the best choice.
Look into Python or Scala

PHP syntax to call methods on temporary objects

Is there a way to call a method on a temporary declared object without being forced to assign 1st the object to a variable?
See below:
class Test
{
private $i = 7;
public function get() {return $this->i;}
}
$temp = new Test();
echo $temp->get(); //ok
echo new Test()->get(); //invalid syntax
echo {new Test()}->get(); //invalid syntax
echo ${new Test()}->get(); //invalid syntax
I use the following workaround when I want to have this behaviour.
I declare this function (in the global scope) :
function take($that) { return $that; }
Then I use it this way :
echo take(new Test())->get();
What you can do is
class Test
{
private $i = 7;
public function get() {return $this->i;}
public static function getNew() { return new self(); }
}
echo Test::getNew()->get();
Why not just do this:
class Test
{
private static $i = 7;
public static function get() {return self::$i;}
}
$value = Test::get();
Unfortunately, you can't do that. It's just the way PHP is, I'm afraid.
No. This is a limitation in PHP's parser.
i often use this handy little function
function make($klass) {
$_ = func_get_args();
if(count($_) < 2)
return new $klass;
$c = new ReflectionClass($klass);
return $c->newInstanceArgs(array_slice($_, 1));
}
usage
make('SomeCLass')->method();
or
make('SomeClass', arg1, arg2)->foobar();
Impossible and why would you create an object this way at all?
The point of an object is to encapsulate unique state. In the example you gave, $i will always be 7, so there is no point in creating the object, then getting $i from it and then losing the object to the Garbage collector because there is no reference to the object after $i was returned. A static class, like shown elsewhere, makes much more sense for this purpose. Or a closure.
Related topic:
http://www.mail-archive.com/internals#lists.php.net/msg44919.html
http://bugs.php.net/bug.php?id=23022&edit=1
http://www.mail-archive.com/internals#lists.php.net/msg07610.html
This is an old question: I'm just providing an updated answer.
In all supported versions of PHP (since 5.4.0, in 2012) you can do this:
(new Test())->get();
See https://secure.php.net/manual/en/migration54.new-features.php ("Class member access on instantiation").
This has come up very recently on php-internals, and unfortunately some influential people (e. g. sniper) active in development of PHP oppose the feature. Drop an email to php-internals#lists.php.net, let them know you're a grownup programmer.

Categories