php5 magic function __toString($param =null ) can not take args - php

i used to have a function defined like this (that is working fine under ubuntu 9.10):
public function __toString( $surNameFirst = false) {
if ($this->givenName . $this->surname == '') return null;
else .......
}
after i have updated my machine to ubuntu 10.04( and php version Version: 5.3.2-1ubuntu4.2
) my app starts to show an error like this one ==>
Fatal error: Method Application_Model_Person::__tostring() cannot take arguments in /home/speshu/Development/where/application/models/Person.php on line 39
Call Stack:
0.0001 616576 1. {main}() /home/speshu/Development/where/public/index.php:0
0.0294 1008248 2. Zend_Application->bootstrap() /home/speshu/Development/where/public/index.php:35
0.0294 1008328 3. Zend_Application_Bootstrap_BootstrapAbstract->bootstrap() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application.php:355
0.0294 1008328 4. Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:582
0.0387 1991416 5. Zend_Application_Bootstrap_BootstrapAbstract->_executeResource() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:618
0.0387 1991776 6. Bootstrap->_initDoctrineCLI() /usr/local/lib/ZendFramework-1.10.0/library/Zend/Application/Bootstrap/BootstrapAbstract.php:665
0.0387 1991856 7. Bootstrap->_initDoctrine() /home/speshu/Development/where/application/Bootstrap.php:66
0.0406 2245200 8. Doctrine_Core::loadModels() /home/speshu/Development/where/application/Bootstrap.php:93

As of version 5.3 The __toString magic method can no longer accept arguments.
In any case should you really be doing that with a magic method? Why not declare toString($whatever) instead?

function __toString() {
if(func_num_args()>0) {
$surNameFirst=func_get_arg(0);
} else {
$surNameFirst=false;
}
....
This is how I got around the problem, it's dirty and ugly... but it worked to get the system working again before finding a more permanent solution.
You should be able to figure out how best to extend this to suite your needs, be aware that it's probably best to pass in an assosiative array if using this method, as it would be easier to access the data.

I think it's very ugly to invoke $obj->__toString();
it should be better to call just echo $obj ; if you don't want to pass arguments and call $obj->toString($param) ; if you want to specify arguments.

Another Possible work around.
class test
{
var $toStringArgs = array();
function SetToStringArgs()
{
$this->toStringArgs = func_get_args();
}
function __toString()
{
var_dump($this->toStringArgs);
}
}
$test = new test();
$test->SetToStringArgs('firstname','lastname');
$test->__toString();

Related

PHPUnit test method is called once with each argument

I have a method which will be called multiple times during a single test but only once with each argument. So I want to test that the method only received each argument once. For example, I have here a mkdir function that is called with each directory to create:
The test
$dirs = [
"$parentDir/$siteName/assets/components",
"$parentDir/$siteName/assets/layouts",
];
// iterate each directory
foreach($dirs as $dir) {
// and verify that mkdir was called with that argument only once
$fileSystemMock->expects($this->once())
->method('mkdir')
->with($this->equalTo($dir));
}
The method being tested
public function createSite($siteName) {
$fileSystem = $this->fileSystem;
$parentDir = $this->parentDir;
$componentsDir = "$parentDir/$siteName/assets/components";
$layoutsDir = "$parentDir/$siteName/assets/layouts";
$mediaDir = "$parentDir/$siteName/content/media";
$sectionsDir = "$parentDir/$siteName/assets/sections";
if (!$fileSystem->exists($componentsDir)) {
$fileSystem->mkdir($componentsDir);
}
if (!$fileSystem->exists($layoutsDir)) {
$fileSystem->mkdir($layoutsDir);
}
However, the test fails:
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'/path/to/parent/best-widgets/assets/layouts'
+'/path/to/parent/best-widgets/assets/components'
Hopefully it makes sense what I'm trying to. Does the once() not take into consideration the with() argument? I don't know how to just check the method was called once with each argument
You may use withConsecutive:
$fileSystemMock->expects($this->exactly(count($dirs)))
->method('mkdir')
->withConsecutive(...array_map(function (string $dir) {
return [$this->equalTo($dir)];
}, $dirs));
withConsecutive expects one parameter for each set of argument expectations, so array_map combined with the unpacking operator comes in handy.
Note that this should only pass if the calls are made in same order defined by the $dirs array. It seems a bit harder to pull off otherwise (btw, a GitHub issue was recently created about it).
Bonus PHP 7.4 version of the above:
$fileSystemMock->expects($this->exactly(count($dirs)))
->method('mkdir')
->withConsecutive(...array_map(fn(string $dir) => [$this->equalTo($dir)], $dirs));

Recall chained methods on PHP

I call an object that returns an array given certain chained methods:
Songs::duration('>', 2)->artist('Unknown')->genre('Metal')->stars(5)->getAllAsArray();
The problem lies that every time I want to get this array, for example, in another script, I have to chain everything again. Now imagine that in over 10 scripts.
Is there a way to recall the chained methods for later use?
Since you can't cache the result, you could cache the structure of the call chain in an array.
$chain = [
'duration' => ['>', 2],
'artist' => 'Unknown',
'genre' => 'Metal',
'stars' => 5,
'getAllAsArray' => null
];
You could use that with a function that emulates the chained call using the cached array:
function callChain($object, $chain) {
foreach ($chain as $method => $params) {
$params = is_array($params) ? $params : (array) $params;
$object = call_user_func_array([$object, $method], $params);
}
return $object;
}
$result = callChain('Songs', $chain);
If you can not cache your results as suggested, as I commented, here are a couple ideas. If your application allows for mixing of functions (as in you are permitted by standards of your company's development rules) and classes, you can use a function wrapper:
// The function can be as complex as you want
// You can make '>', 2 args too if they are going to be different all the time
function getArtists($array)
{
return \Songs::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(getArtists(array('Unkown','Metal',5)));
If you are only allowed to use classes and __callStatic() is not forbidden in your development and is also available in the version of PHP you are using, you might try that:
// If you have access to the Songs class
public __callStatic($name,$args=false)
{
// This should explode your method name
// so you have two important elements of your chain
// Unknown_Metal() should produce "Unknown" and "Metal" as key 0 and 1
$settings = explode("_",$name);
// Args should be in an array, so if you have 1 value, should be in key 0
$stars = (isset($args[0]))? $args[0] : 5;
// return the contents
return self::duration('>', 2)->artist($settings[0])->genre($settings[1])->stars($stars)->getAllAsArray();
}
This should return the same as your chain:
print_r(\Songs::Unknown_Metal(5));
It should be noted that overloading is hard to follow because there is no concrete method called Unknown_Metal so it's harder to debug. Also note I have not tested this particular set-up out locally, but I have notated what should happen where.
If those are not allowed, I would then make a method to shorten that chain:
public function getArtists($array)
{
// Note, '>', 2 can be args too, I just didn't add them
return self::duration('>', 2)->artist($array[0])->genre($array[1])->stars($array[2])->getAllAsArray();
}
print_r(\Songs::getArtists(array('Unkown','Metal',5)));
I wrote a lib doing exactly what you're looking for, implementing the principle suggested by Don't Panic in a high quality way: https://packagist.org/packages/jclaveau/php-deferred-callchain
In your case you would code
$search = DeferredCallChain::new_(Songs::class) // or shorter: later(Songs::class)
->duration('>',2) // static syntax "::" cannot handle chaining sadly
->artist('Unknown')
->genre('Metal')
->stars(5)
->getAllAsArray();
print_r( $search($myFirstDBSongs) );
print_r( $search($mySecondDBSongs) );
Hoping it will match your needs!

PHP function missing argument error

My validate function looks like that
function validate($data, $data2 = 0, $type)
{
...
Function call example
if ($result = validate($lname, 'name') !== true)
response(0, $result, 'lname');
As you see, my validate function has 3 input vars. I'm not using second var - $data2 often, that's why set it to 0 by default. But when I'm calling this function as given example (as far as I know it means $data=$lname, $data2=0, $type='name') getting error message
Missing argument 3 ($type) for validate()
How can I fix that?
Missing argument 3 ($type) for validate() [1]
Always list optional arguments as the last arguments, never before non-optional arguments.
Since PHP doesn't have named parameters1 nor "overloading ala Java", that's the only way:
function validate($data, $type, $data2 = 0) {
}
1 Error with severity E_WARNING until PHP 7.0 (including); Uncaught ArgumentCountError starting with PHP 7.1rfc (and starting with PHP 8.0 as well for internal functionsrfc).
2 before PHP 8.0, see Named Arguments
You should at least set the $type in this line:
function validate($data, $data2 = 0, $type)
at NULL or '' as you can see here:
function validate($data, $data2 = 0, $type = null)
PHP let you to set a value for the parameters, but you can't define a parameter WITHOUT a preset value AFTER parameter(s) which HAVE a preset value. So if you need to always specify the third param, you have to switch the second and the third like this:
function validate($data, $type, $data2 = 0)
From http://php.net/manual/en/functions.arguments.php
Note that when using default arguments, any defaults should be on the right side of any non-default arguments; otherwise, things will not work as expected
Your should switch the second and third arguments of the function, making the optional argument the last one. So it becomes:
function validate($data, $type, $data2 = 0)
{ ....
function validate($data, $data2, $data3, $data4, $data5)
im a beginner but i think that you can use a thousand arguments
as long as you call like that
if ($result = validate($lname, 'name','','','') !== true)
Notice that starting with PHP 7.1 this will throw a PHP Fatal error, not just a warning:
PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function validate(), 2 passed in /path/to/file.php on line X and exactly 3 expected
More info: http://php.net/manual/en/migration71.incompatible.php

PHP Optional Parameters - specify parameter value by name?

I know it is possible to use optional arguments as follows:
function doSomething($do, $something = "something") {
}
doSomething("do");
doSomething("do", "nothing");
But suppose you have the following situation:
function doSomething($do, $something = "something", $or = "or", $nothing = "nothing") {
}
doSomething("do", $or=>"and", $nothing=>"something");
So in the above line it would default $something to "something", even though I am setting values for everything else. I know this is possible in .net - I use it all the time. But I need to do this in PHP if possible.
Can anyone tell me if this is possible? I am altering the Omnistar Affiliate program which I have integrated into Interspire Shopping Cart - so I want to keep a function working as normal for any places where I dont change the call to the function, but in one place (which I am extending) I want to specify additional parameters. I dont want to create another function unless I absolutely have to.
No, in PHP that is not possible as of writing. Use array arguments:
function doSomething($arguments = array()) {
// set defaults
$arguments = array_merge(array(
"argument" => "default value",
), $arguments);
var_dump($arguments);
}
Example usage:
doSomething(); // with all defaults, or:
doSomething(array("argument" => "other value"));
When changing an existing method:
//function doSomething($bar, $baz) {
function doSomething($bar, $baz, $arguments = array()) {
// $bar and $baz remain in place, old code works
}
Have a look at func_get_args: http://au2.php.net/manual/en/function.func-get-args.php
Named arguments are not currently available in PHP (5.3).
To get around this, you commonly see a function receiving an argument array() and then using extract() to use the supplied arguments in local variables or array_merge() to default them.
Your original example would look something like:
$args = array('do' => 'do', 'or' => 'not', 'nothing' => 'something');
doSomething($args);
PHP has no named parameters. You'll have to decide on one workaround.
Most commonly an array parameter is used. But another clever method is using URL parameters, if you only need literal values:
function with_options($any) {
parse_str($any); // or extract() for array params
}
with_options("param=123&and=and&or=or");
Combine this approach with default parameters as it suits your particular use case.

How to pass specific variable in PHP function

I have a PHP function that requires can take 3 parameteres... I want to pass it a value for the 1st and 3rd parameters but I want the 2nd one to default...
How can I specify which ones I am passing, otherwise its interpreted as me passing values for the 1st and 2nd slots.
Thanks.
You cannot "not pass" a parameter that's not at the end of the parameters list :
if you want to specify the 3rd parameter, you have to pass the 1st and 2nd ones
if you want to specify the 2nd parameter, you have to pass the 1st one -- but the 3rd can be left out, if optionnal.
In your case, you have to pass a value for the 2nd parameter -- the default one, ideally ; which, yes, requires your to know that default value.
A possible alternative would be not have your function take 3 parameters, but only one, an array :
function my_function(array $params = array()) {
// if set, use $params['first']
// if set, use $params['third']
// ...
}
And call that function like this :
my_function(array(
'first' => 'plop',
'third' => 'glop'
));
This would allow you to :
accept any number of parameters
all of which could be optionnal
But :
your code would be less easy to understand, and the documentation would be less useful : no named parameters
your IDE would not be able to give you hints on which parameters the function accepts
Once you've defined a default parameter, all the parameters after that one cannot be required. You could do something like:
const MY_FUNCTION_DEFAULT = "default";
public function myFunction($one, $two = "default", $three = 3)
{
if (is_null($two)) $two = self::MY_FUNCTION_DEFAULT;
//...
}
// call
$this->myFunction(1, null, 3);
You might also define an empty parameter set and use func_get_args to pull in parameters and analyze those using instanceof or typeof/gettype for type checking if your function is simple enough.
You could use ReflectionFunction. This problem has already been solved by an anonymous contributor at php.net, see orinal here: http://www.php.net/manual/en/function.call-user-func-array.php#66121
For those wishing to implement call-by-name functionality in PHP, such as implemented e.g. in DB apis, here's a quick-n-dirty version for PHP 5 and up
function call_user_func_named($function, $params)
{
// make sure we do not throw exception if function not found: raise error instead...
// (oh boy, we do like php 4 better than 5, don't we...)
if (!function_exists($function))
{
trigger_error('call to unexisting function '.$function, E_USER_ERROR);
return NULL;
}
$reflect = new ReflectionFunction($function);
$real_params = array();
foreach ($reflect->getParameters() as $i => $param)
{
$pname = $param->getName();
if ($param->isPassedByReference())
{
/// #todo shall we raise some warning?
}
if (array_key_exists($pname, $params))
{
$real_params[] = $params[$pname];
}
else if ($param->isDefaultValueAvailable()) {
$real_params[] = $param->getDefaultValue();
}
else
{
// missing required parameter: mark an error and exit
//return new Exception('call to '.$function.' missing parameter nr. '.$i+1);
trigger_error(sprintf('call to %s missing parameter nr. %d', $function, $i+1), E_USER_ERROR);
return NULL;
}
}
return call_user_func_array($function, $real_params);
}
function ($foo, $mate, $bar = "") {
// ... some code
}

Categories