I am new to PHP and encountered some odd behavior that may or may not be a bug in my version of PHP (5.4.13). I found a case where the sequence of function arguments matters in the function declaration. The in the following example:
class Book {
function __construct() { }
public static function withDescriptors($isNotOrmObject, $isArray, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
Invoking withDescriptors causes an 'array to string conversion' exception. The error is thrown when withDescriptors is invoked, ie. withDescriptors is never actually executed. However, switching the object parameter with the array parameter solves the problem. I.e.
public static function withDescriptors($isArray, $isNotOrmObject, $isScalar){ ....
Is this a known characteristic of PHP or is this a bug?
More explicitly:
class Book {
function __construct() { }
public static function withDescriptors($isNotOrmObject, $isArray, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
$book = Book::withDescriptors($isNotORMobject, $isArray, $isScalar);
FAILS and
class Book {
function __construct() { }
public static function withDescriptors($isArray, $isNotORMobject, $isScalar) {
$instance = new self();
// initialization here
return $instance;
}
}
$book = Book::withDescriptors($isArray, $isNotORMobject, $isScalar);
WORKS great. The ONLY difference is the parameter sequence, the "initialization here" code is identical.
The reason you don't get the warning in your second example, is that the Object you're passing as the second parameter is implementing a magic __toString() method. PHP is not a strong typed language, but more recent versions have limited capabilities of type hinting.
To illustrate the warning;
function saySomething($shouldBeAString)
{
echo $shouldBeAString
}
saySomething(array('hello'));
Outputs
'Array'
and a warning array to string conversion
class Foo {
public function __toString() {
return 'Hi I am Foo';
}
}
$bar = new Foo();
saySomething($bar);
Will output
'Hi I am Foo'
Without warning
As mentioned, PHP offers limited type hinting. You can specify a required 'class/object type' and 'array' as accepted arguments, but not scalar types, like 'string', 'int', 'bool' etc.
function (array $myarray) {
// will only accept arrays
// or something that implements array-access
}
function (Foo $var) {
// Will only accept 'Foo' objects
}
Polymorphism / Function overloading
Some other languages allow defining the same method/function multiple times, but with a different signature (other argument types). PHP does not explicitly support this.
For example, in other languages, this is allowed:
function doSomething(string $arg1) { ......}
function doSomething(array $arg1) { .... }
function doSomething(string $arg1, string $arg2) { ... }
In those languages, depending on the type and number of arguments, variant 1, 2 or 3 will be executed. PHP does not support this as it requires functions/methods to have a unique name. PHP will therefore complain that function doSomething() is already defined.
However, you can create something similar in PHP in several ways;
// rename the functions/methods so that they have a unique name
function _doSomething1(string $arg1) { ......}
function _doSomething2(array $arg1) { .... }
function _doSomething3(string $arg1, string $arg2) { ... }
// create the 'wrapper' function *without arguments specified*
function doSomething() {
// determin which variant should be executed
$numargs = func_num_args();
$args = func_get_args();
if ($numargs == 2) {
// 2 arguments -> variant 3
return call_user_func_array('_doSomething3', $args);
} else if {$numargs == 1) {
if (is_array($args[0]) {
// first argument is an array
return call_user_func_array('_doSomething2', $args);
} else {
return call_user_func_array('_doSomething1', $args);
}
}
}
Note: code above is 'fake' code, just to illustrate the idea!
In general, order matters in function arguments.
The only time it doesn't is when arguments are the same type or your function implements something like get opt long.
There are some good answers here but this particular situation arose from a bug in the PHP interpreter.
Related
I have a function which expects a Callable parameter. I want to determine that this callable returns a string and if it doesn't an exception should be thrown.
I tried searching for this, but no luck. Does the PHP reflection API provide functionality like this? I don't want to run the method and see if it actually returns a string.
Example of what I need:
class MyClass
{
protected static $overrider = null;
public static function setOverrider(Callable $callback)
{
// Pseudo code start
if (!$callback returns string) {
throw new \Exception('Wasnt a string!');
}
// Pseudo code end
self::$overrider = $callback;
}
}
Maybe you need something like this:
class MyClass
{
protected static $overrider = null;
public static function setOverrider(Callable $callback)
{
$reflection = new ReflectionFunction($callback);
if ('string' != $reflection->getReturnType()) {
throw new \Exception('Wasnt a string!');
}
self::$overrider = $callback;
}
}
So, as I mentioned previously in comments: You need to declare returning type of your callable (which is a PHP7+ feature). It is a MUST, otherwise, it will not work
Like this:
function my_function(): string
{
return 'hello';
}
or like this if you prefer anonymous functions (Closure):
$my_callable = function(): string {
return 'hello';
}
It is as simple as this:
The interpreter cannot know the returning data type of a function without invoking it if you don't first tell the interpreter what should return the function in question.
I want to make a type-inferring linter for a subset of PHP, but am unsure about how much type information you can get out of it without having to enforce phpdoc annotations or similar. Consider this example:
function a() {
b(10); // This is wrong, but we don't know the type of b()
}
function b($c) {
print($c . " hallo"); // Only allow concatenations with strings
}
Without the in OCaml present and for mutual recursion, things have to be defined in order of use. The way to get around it is to make a two-stage type-checker, where the first stage checks interfaces, but type-inferring needs to go through the body of the functions.
One way to get around this is to enforce phpdoc docblocks:
function a() {
b(10); // Type error: b() expects a string
}
/**
* #param string $c
*/
function b($c) {
print($c . " hallo");
}
Enforcing docblocks to be used in type-checking feels... wrong. Is there any way around this? Of course the scalar type-hints v5, using declare(strict_types=1), would allow scalar type-hints in function signatures, but that's too far into the future.
Edit: I'm stupid, b() could of course be inferred from it's usage in a(), so we would have:
function a() {
b(10); // b() inferred to int -> void
}
function b($c) {
print($c . " hallo"); // Wrong, $c used as a string
}
You could create classes that represent the scalar values you want to type hint.
class String {
protected $string;
public function __construct($value) {
if (!is_string($value)) {
throw new Exception('Not a string');
}
$this->string = $value;
}
public function __toString() {
return $this->string;
}
}
Then in your function declaration:
function b(String $c) {
...
}
b(new String('Some String'));
I'm aware of the existence of call_user_func_array, but I don't believe you can use this to construct a class (if you can, how do you do it?). As long as I'm not mistaken, how do you instantiate a class with an array as parameters?
for example:
class Test {
public function __construct($var1, $var2) {
// do something
}
}
how would I instantiate it with this:
array("var1_data", "var2_data")
class Test {
public function __construct(array $params) {
// ...
}
}
Don't use “magic” unless you really need it.
EDIT:
If what you need is varagrs, you can find an answer here.
If you must have multiple constructors, you should name them something other than __construct, then define a __construct method that can accept any number of arguments. Within that method, you can determine which of your custom constructors to use. If all you want to do is allow the constructor to be passed an array instead of a list of arguments, you can do it this way (note that this example lacks any error checking):
public function __construct() {
$args = func_get_args();
if(count($args) == 1 && is_array($args[0])) {
$cArgs = $args[0];
} else {
$cArgs = $args;
}
__do_construct($cArgs[0], $cArgs[1]);
}
private function __do_construct($arg1, $arg2) {
// do something
}
How do you validate/manage your PHP method arguments and why do you do it this way?
Well, assuming that you're talking about type-checking method arguments, it depends:
If it's expecting an object, I use type-hinting with an interface:
public function foo(iBar $bar)
If it's expecting an array only, I use type-hinting with the array keyword.
public function foo(array $bar)
If it's expecting a string, int, bool or float, I cast it:
public function foo($bar) {
$bar = (int) $bar;
}
If it's expecting mixed, I just check in a cascade:
public function foo($bar) {
if (is_string($bar)) {
//handle string case
} elseif (is_array($bar)) {
//...
} else {
throw new InvalidArgumentException("invalid type");
}
}
Lastly, if it's expecting an iterable type, I don't use type-hinting. I check if it's an array first, then re-load the iterator:
public function foo($bar) {
if (is_array($bar)) {
$bar = new ArrayIterator($bar);
}
if (!$bar instanceof Traversable) {
throw new InvalidArgumentException("Not an Iterator");
}
}
If it's expecting a filename or directory, just confirm it with is_file:
public function foo($bar) {
if (!is_file($bar)) {
throw new InvalidArgumentException("File doesn't exist");
}
}
I think that handles most of the cases. If you think of any others, I'll gladly try to answer them...
Typechecking is something you should do at the development stage, not in production. So the appropriate syntactic feature for that would be:
function xyz($a, $b) {
assert(is_array($a));
assert(is_scalar($b));
However I'll try to avoid it, or use type coercion preferrably. PHP being dynamically typed does quite well adapting to different values. There are only few spots where you want to turndown the basic language behaviour.
What's the best way to do something like this in PHP?:
$a = new CustomClass();
$a->customFunction = function() {
return 'Hello World';
}
echo $a->customFunction();
(The above code is not valid.)
Here is a simple and limited monkey-patch-like class for PHP. Methods added to the class instance must take the object reference ($this) as their first parameter, python-style.
Also, constructs like parent and self won't work.
OTOH, it allows you to patch any callback type into the class.
class Monkey {
private $_overload = "";
private static $_static = "";
public function addMethod($name, $callback) {
$this->_overload[$name] = $callback;
}
public function __call($name, $arguments) {
if(isset($this->_overload[$name])) {
array_unshift($arguments, $this);
return call_user_func_array($this->_overload[$name], $arguments);
/* alternatively, if you prefer an argument array instead of an argument list (in the function)
return call_user_func($this->_overload[$name], $this, $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
/* static method calling only works in PHP 5.3.0 and later */
public static function addStaticMethod($name, $callback) {
$this->_static[$name] = $callback;
}
public static function __callStatic($name, $arguments) {
if(isset($this->_static[$name])) {
return call_user_func($this->_static[$name], $arguments);
/* alternatively, if you prefer an argument list instead of an argument array (in the function)
return call_user_func_array($this->_static[$name], $arguments);
*/
} else {
throw new Exception("No registered method called ".__CLASS__."::".$name);
}
}
}
/* note, defined outside the class */
function patch($this, $arg1, $arg2) {
echo "Arguments $arg1 and $arg2\n";
}
$m = new Monkey();
$m->addMethod("patch", "patch");
$m->patch("one", "two");
/* any callback type works. This will apply `get_class_methods` to the $m object. Quite useless, but fun. */
$m->addMethod("inspect", "get_class_methods");
echo implode("\n", $m->inspect())."\n";
Unlike Javascript you can't assign functions to PHP classes after the fact (I assume you are coming from Javascript becuase you are using their anonymous functions).
Javascript has a Classless Prototypal system, where as PHP has a Classical Classing System. In PHP you have to define every class you are going to use, while in Javascript, you can create and change each object however you want.
In the words of Douglas Crockford: You can program in Javascript like it is a Classical System, but you can't program in a Classical System like it is Javascript. This means that a lot of the stuff you are able to do in Javascript, you can't do in PHP, without modifications.
I smell Adapter Pattern, or maybe even Decorator Pattern!