I have a unit test which uses the following mocked class
$this->stubHandle = $this->getMockBuilder(Handle::class)->disableOriginalConstructor()->getMock();
When I run
$key = new Key($this->stubHandle);
$this->stubHandle->method('__call')->will($this->returnCallback('var_dump'));
$key->nonExistingMethod('element0', 'element1', []);
it outputs
string(17) "nonExistingMethod"
and
array(3) {
[0] =>
string(8) "element0"
[1] =>
string(8) "element1"
[2] =>
array(0) {
}
}
which I think it means the first argument of method __call is a string and the second argument is an array. No more arguments passed.
When I mock the return value of method __call like
$this->stubHandle->method('__call')->willReturn(1);
The return value is always 1 as expected.
When I mock the return value of method __call like
$this->stubHandle->method('__call')->willReturnMap(
[
['argument1', null, 'returnValue'],
]
);
I expect the return value to be 'returnValue' when the first argument of method __call is 'argument1' while the seconds argument doesn't care.
If the first argument isn't 'argument1', I expect a return value of null.
Are these expectations correct?
No matter what I try, the return value is always null when I mock this value with method willReturnMap.
If my expectations are wrong, please lead me to a way to accomplish my goals to mock the return value, depending on the first argument (only) and also explain to me how to apply willReturnMap.
It seems my search for method willReturnMap mislead me a bit.
I was under the assumption using null in the map, it would act as a "don't care", but it literally means null.
$this->stubHandle->method('__call')->willReturnMap(
[
['nonExistingMethod', null, 'returnValue'],
]
);
means the arguments of method __call must explicitly be 'nonExistingMethod' & null if you want the return value to be 'returnValue'. Any other argument-values will return null instead.
E.g. $key->nonExistingMethod(null);
For the map to return 'returnValue' in my example, it should have been:
$this->stubHandle->method('__call')->willReturnMap(
[
['nonExistingMethod', ['element0', 'element1', []], 'returnValue'],
]
);
$key->nonExistingMethod('element0', 'element1', []);
In short...
When using argument mapping, the values can't be a "don't Care".
Related
I'm using route binding to determine if each part of the URL is related to the previous. I'm trying to access the route parameter/variable ($stage_number) in my query builder but no luck. To confuse things, if I substitute the variable with a hard value it works, e.g. 4
How do I use the $stage_number variable in my query?
/*
* Route
*/
Route::get('/application/{application_id}/stage/{stage_number}', [
'as' => 'application.stage',
'uses' => 'ApplicationController#stage'
]);
/*
* Route Service Provider
*/
// Application
$router->bind('application_id', function($application_id)
{
$application = Application::find($application_id);
if (! $application)
{
abort(404);
}
return $application;
});
// Stage
$router->bind('stage_number', function($stage_number)
{
$application = $this->getCurrentRoute()->application_id;
$stage = collect($application->stages)->where('order', $stage_number)->all();
if (! $stage)
{
abort(404);
}
return $stage;
});
Update in response to patricus:
Thanks for the information about calling where() on collections; I did not realise it handled differently to the query builder. Updating my where() for the collection works perfectly - thanks. However, I am still having trouble when using the query builder:
// Route with data
/application/1/stage/4
// Actual data returned
array:4 [▼
0 => array:2 [▼
"name" => "Information about the University or Education Course"
"order" => 3
]
1 => array:2 [▼
"name" => "Information about your education to date"
"order" => 4
]
2 => array:2 [▼
"name" => "Other Information"
"order" => 5
]
3 => array:2 [▼
"name" => "Declaration"
"order" => 6
]
]
// Desired data to be returned
array:1 [▼
0 => array:2 [▼
"name" => "Information about your education to date"
"order" => 4
]
]
Regardless of what order I specify in my route I seem to get everything returned that is not null unless I choose 1 or 2 (which are rows with no order number and excluded from the array example above) and then I get that row returned with all of the other rows that is not null (as shown in the example above) but any other null rows are excluded. Is this a problem caused by the relationship on my Application object?
public function stages()
{
return $this->hasMany('App\Entities\Application\Stage', 'type_id')->orWhereNull('type_id')->orderBy('order', 'asc');
}
Update:
Setting the foreign_key and local_key on my relationship seemed to resolve the other issues:
public function stages()
{
return $this->hasMany('App\Entities\Application\Stage', 'type_id', 'type_id')->orWhereNull('type_id')->orderBy('order', 'asc');
}
You're actually calling the where() method on a Collection object, not on a Builder object.
The where() method on the Collection works a little differently. First, it can only do an equals comparison. Second, by default, it does a strict equals (===) comparison, and this is your issue. Since your $stage_number is a string, and your order field is most likely an integer, the strict comparison doesn't return any results.
You can change the comparison to a loose equals (==) by passing false as the third parameter. You can also use the whereLoose() method, which does the same thing.
Also note, assuming that stages is a hasMany or belongsToMany relationship on the Application object, there is no need for the call to collect(). $application->stages will already return a Collection.
// pass false as third parameter
$stage = $application->stages->where('order', $stage_number, false)->all();
// or use whereLoose
$stage = $application->stages->whereLoose('order', $stage_number)->all();
Now, having said all that, you probably want to be calling where() on the query builder. While $application->stages will give a Collection of related stage objects, $application->stages() returns the Relation object for the relationship, which gives you access to the query builder. Since you have access to the builder, you can add your where clause to the query being run and will provide a performance boost (and also doesn't care about variable type).
$stage = $application->stages()->where('order', $stage_number)->get();
Note that get() will return a Collection object that contains the matching stage objects. If order is unique and you want to get that one stage object, you can use first() instead of get():
$stage = $application->stages()->where('order', $stage_number)->first();
I have a laravel collection object.
I want to use the nth model within it.
How do I access it?
Edit:
I cannot find a suitable method in the laravel documentation. I could iterate the collection in a foreach loop and break when the nth item is found:
foreach($collection as $key => $object)
{
if($key == $nth) {break;}
}
// $object is now the nth one
But this seems messy.
A cleaner way would be to perform the above loop once and create a simple array containing all the objects in the collection. But this seems like unnecessary duplication.
In the laravel collection class documentation, there is a fetch method but I think this fetches an object from the collection matching a primary key, rather than the nth one in the collection.
Seeing as Illuminate\Support\Collection implements ArrayAccess, you should be able to simply use square-bracket notation, ie
$collection[$nth]
This calls offsetGet internally which you can also use
$collection->offsetGet($nth)
and finally, you can use the get method which allows for an optional default value
$collection->get($nth)
// or
$collection->get($nth, 'some default value')
#Phil's answer doesn't quite obtain the nth element, since the keys may be unordered. If you've got an eloquent collection from a db query it'll work fine, but if your keys aren't sequential then you'll need to do something different.
$collection = collect([0 => 'bish', 2 => 'bash']); $collection[1] // Undefined index
Instead we can do $collection->values()[1] // string(4) bash
which uses array_values()
Or even make a macro to do this:
Collection::macro('nthElement', function($offset, $default = null) {
return $this->values()->get($offset, $default);
}):
Example macro usage:
$collection = collect([0 => 'bish', 2 => 'bash']);
$collection->nthElement(1) // string(4) 'bash'
$collection->nthElement(3) // undefined index
$collection->nthElement(3, 'bosh') // string (4) bosh
I am late to this question, but I thought this might be a useful solution for someone.
Collections have the slice method with the following parameters:
$items->slice(whereToStartSlice, sizeOfSlice);
Therefore, if you set the whereToStartSlice parameter at the nth item and the sizeOfSlice to 1 you retrieve the nth item.
Example:
$nthItem = $items->slice($nth,1);
If you are having problems with the collection keeping the indices after sorting... you can make a new collection out of the values of that collection and try accessing the newly indexed collection like you would expect:
e.g. Get the second highest priced item in a collection
$items = collect(
[
"1" => ["name" => "baseball", "price" => 5],
"2" => ["name"=> "bat", "price" => 15],
"3" => ["name" => "glove", "price" => 10]
]
);
collect($items->sortByDesc("price")->values())[1]["name"];
// Result: glove
Similar to morphs answer but not the same. Simply using values() after a sort will not give you the expected results because the indices remain coupled to each item.
Credit to #howtomakeaturn for this solution on the Laravel Github:
https://github.com/laravel/framework/issues/1335
For most of my models in CakePHP I often create a function that handles saving a record. The default behavior for a Model's save is to return the data array or false.
I prefer the function to return true/false only. So I cast the result to (bool). Is this a valid way to cast something to boolean?
It's never not worked, but I've often wondered if it was a poor practice.
public function queue($url,$order=0)
{
$result = $this->save(array(
$this->alias => array(
'agg_domain_id' => $domain_id,
'order' => $order,
'url' => $url
)
));
return (bool)$result;
}
From php.net:
To explicitly convert a value to boolean, use the (bool) or (boolean) casts. However, in most cases the cast is unncecessary, since a value will be automatically converted if an operator, function or control structure requires a boolean argument.
So if you do if($this->queue('url',0)) then the cast is not necessary.
But if you do if($this->queue('url',0) === true) then you need to cast. And casting with (bool) is absolute legitimate.
Is this a valid way to cast something to boolean?
Yes
It's fine to do that where you only want to know success or fail.
When it's not ok
The only potential problems with casting the return value like that, is if the return value could be a falsey type e.g.:
array()
0
""
" "
a call to save always returns a nested array or false - there is no scope for getting a false-negative result with a call to save.
In PHP, if I have a function written like this
function example($argument1, $argument2="", $argument3="")
And I can call this function in other place like this
example($argument1)
But if I want to give some value to the thrid argument,
Should I do this
example($argument1, "", "test");
Or
example($argument1, NULL, "test");
I think both will work but what is the better way? Because in future if the value for the $argument2 is changed in the main function and if I have "", then will there be any problem? Or NULL is the best option?
Thanks
Passing null or "" for a parameter you don't want to specify still results in those nulls and empty strings being passed to the function.
The only time the default value for a parameter is used is if the parameter is NOT SET ALL in the calling code. example('a') will let args #2 and #3 get the default values, but if you do example('a', null, "") then arg #3 is null and arg #3 is an empty string in the function, NOT the default values.
Ideally, PHP would support something like example('a', , "") to allow the default value to be used for arg #2, but this is just a syntax error.
If you need to leave off arguments in the middle of a function call but specify values for later arguments, you'll have to explicitly check for some sentinel value in the function itself and set defaults yourself.
Here's some sample code:
<?php
function example($a = 'a', $b = 'b', $c = 'c') {
var_dump($a);
var_dump($b);
var_dump($c);
}
and for various inputs:
example():
string(1) "a"
string(1) "b"
string(1) "c"
example('z'):
string(1) "z"
string(1) "b"
string(1) "c"
example('y', null):
string(1) "y"
NULL
string(1) "c"
example('x', "", null):
string(1) "x"
string(0) ""
NULL
Note that as soon you specify ANYTHING for the argument in the function call, that value is passed in to the function and overrides the default that was set in the function definition.
You can use either:
example($argument1, '', 'test');
Or
example($argument1, NULL, 'test');
You can always check for NULL using instead of empty string:
if ($argument === NULL) {
//
}
I think it all depends on what happens inside the function with the args.
I think you will be better served if you define the optional parameters as null in the function definition and keep it that way, thus calling them as example($argument1, NULL, $argument="test"). Just be sure of how you use the null'd parameters in the function itself to not raise any warnings. You can use false too as long as you're certain of how you use that parameter in the function itself.
If you seem to get this issue alot, a better way may be to use a single associative array for the variable options:
function example($optionsIn = array()) {
$options = array(
'var1' => null,
'var2' => 'something',
'var3' => 'else'
) ;
$options = array_merge($options, $optionsIn) ;
}
$options = array(
'var2' => 'different'
) ;
example($options) ;
That way you can pass in any function parameters you wish, leaving any others out and making sure there are the appropriate defaults defined within the function.
The only drawback is that the expected parameters are hidden from the function signature but you can document them in the function's docblock.
It depends on spesification of function.
function a($b=true,$c=false){
echo $b?'true':'false';
}
executing
a(NULL, 1);
results : false. but simply a() results : true
That means you want default argument, then you should use as default:
a(true, 1);
Using PHP 5.3 I'm experiencing weird / non-intuitive behavior when applying empty() to dynamic object properties fetched via the __get() overload function. Consider the following code snippet:
<?php
class Test {
protected $_data= array(
'id'=> 23,
'name'=> 'my string'
);
function __get($k) {
return $this->_data[$k];
}
}
$test= new Test();
var_dump("Accessing directly:");
var_dump($test->name);
var_dump($test->id);
var_dump(empty($test->name));
var_dump(empty($test->id));
var_dump("Accessing after variable assignment:");
$name= $test->name;
$id= $test->id;
var_dump($name);
var_dump($id);
var_dump(empty($name));
var_dump(empty($id));
?>
The output of this function is as follow. Compare the results of the empty() checks on the first and second result sets:
Set #1, unexpected result:
string(19) "Accessing directly:"
string(9) "my string"
int(23)
bool(true)
bool(true)
Expecting Set #1 to return the same as Set #2:
string(36) "Accessing after variable assignment:"
string(9) "my string"
int(23)
bool(false)
bool(false)
This is really baffling and non-intuitive. The object properties output non-empty strings but empty() considers them to be empty strings. What's going on here?
Based on a reading of the empty's manual page and comments (Ctrl-F for isset and/or double underscores), it looks like this is known behavior, and if you want your __set and __get methods and empty to play nice together, there's an implicit assumption that you implement a __isset magic method as well.
It is a little bit unintuitive and confusing, but that tends to happen with most meta-programming, particularly in a system like PHP.
In this example, empty() calls the __isset() overload function, not the __get() overload function. Try this:
class Test {
protected $_data= array(
'id'=> 23,
'name'=> 'my string'
);
function __get($k) {
return $this->_data[$k];
}
function __isset($k) {
return isset($this->_data[$k]);
}
}