I'm trying to test a function which walks through a class, takes the public properties and makes an object with it.
The non public properties are ignored in the output.
So, I mock the class that will be processed and add it some properties.
This is my code:
class GetSettingsTest extends TestCase
{
public function getExpected() {
return (object) [
"one" => (object) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (object) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
],
"two" => (object) [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
],
"three" => (object) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
]
// four is not here : it is protected or private.
];
}
public function getSettingsMock() {
$stub = $this->getMockBuilder('FakeSettingsClass')
->disableOriginalConstructor()
->getMock();
$stub->one = (array) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (array) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
];
$stub->two = (array) [// provide an array, must return an object
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [// provide an object, must return an object
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
];
$stub->three = (array) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
];
$stub->four = (array) [
// I want this to be protected or private to be not present in the output.
"fourOne" => "4.1",
"fourTwo" => "4.2"
];
return $stub;
}
public function testGetSettings() {
$expected = $this->getExpected();
$getSettings = new GetSettings($this->getSettingsMock());
$value = $getSettings->getSettings();
$this->assertEquals($expected, $value);
}
}
The function works well with a var_dump, it ignores non-public values as expected.
The test works without the non-public part, but I want to test it with the non-public part.
I can't figure how to test the non-public part in PhHPUnit.
Probably by setting a protected value in the getSettingMock function but how can I do that?
Here is a solution based on xmike's comment and with the Phpunit doc here : https://phpunit.readthedocs.io/en/9.0/fixtures.html.
make a fixture class like this :
class GetSettingsFixture
{
public array $one = [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
];
public array $two = [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
];
public array $three = [
"threeOne" => "3.1",
"threeTwo" => "3.2"
];
public string $four = "a string";
private array $five = [ // this should be ignored in the output.
"fiveOne" => "5.1",
"fiveTwo" => "5.2"
];
protected array $six = [ // this should be ignored in the output.
"sixOne" => "6.1",
"sixTwo" => "6.2"
];
public function testFunction() { // this should be ignored in the output.
return "something";
}
}
And this test pass :
class GetSettingsTest extends TestCase
{
private GetSettingsFixture $given;
public function setUp(): void {
// this function is executed before test.
$this->given = new GetSettingsFixture(); // this call the fixture class.
}
public function tearDown(): void {
// this function is executed after the test.
unset($this->given);
}
public function getExpected() {
return (object) [
"one" => (object) [
"oneOne" => "1.1",
"oneTwo" => "1.2",
"oneThree" => (object) [
"oneThreeOne" => "1.3.1",
"oneThreeTwo" => "1.3.2",
]
],
"two" => (object) [
"twoOne" => "2.1",
"twoTwo" => "2.2",
"twoThree" => (object) [
"twoThreeOne" => "1.3.1",
"twoThreeTwo" => "1.3.2",
]
],
"three" => (object) [
"threeOne" => "3.1",
"threeTwo" => "3.2"
],
"four" => "a string"
// five, six are not here : it is protected or private.
// testFunction is hot here too, it's not a property.
];
}
public function testGetSettings() {
$expected = $this->getExpected();
$getSettings = new GetSettings($this->given);
$value = $getSettings->getSettings();
$this->assertEquals($expected, $value);
}
}
Related
I have found a lot of question and some great answers in order to search into an array with PHP. But everytime, the script answered too perfectly to the quesiton, and not globaly OR all arrays are symetricals.
My Array can look like this:
$data = [
'steve' => [
'id' => [
'#text' => 1,
],
'pseudo' => [
'#text' => 'LOL'
],
],
'albert' => [
'id' => [
'#text' => 2,
],
'pseudo' => [
'#text' => 'KILLER'
],
],
'john' => [
'id' => [
'#text' => 3,
],
'pseudo' => [
'#text' => 'NOOBS'
],
],
];
Which mean that my Array can look beautifully symetrically generated, or completely messy and with random subarray.
My aim is to search inside and found the PSEUDO of AN ID. And unfortunately, I can't change the webservice which give me this result.
I had tried with array_column or array_search, but the only thing I had successfully returned is if it founds something, or not. But can't identify it. And my script was very slow.
Something like :
search($array, 2); //which return KILLER
or more optionable maybe?
My Array can have a lot of IDs (100+). So I'm trying to found something optimize. :/
There's probably a million ways to do this, but ultimately you'll need some recursive approach that's able to filter on the value and the path to that value. For this, the following would be one of those million ways.
It uses the predefined iterators:
CallbackFilterIterator
RecursiveIteratorIterator
RecursiveArrayIterator
in combination with a custom PathAsKeyDecorator iterator.
Demo here
<?php
declare(strict_types=1);
final class PathAsKeyDecorator implements \Iterator
{
private RecursiveIteratorIterator $inner;
public function __construct(RecursiveIteratorIterator $inner)
{
$this->inner = $inner;
}
public function current()
{
return $this->inner->current();
}
public function next(): void
{
$this->inner->next();
}
public function key()
{
$path = [];
for ($i = 0, $depth = $this->inner->getDepth(); $i <= $depth; $i++) {
$path[] = $this->inner->getSubIterator($i)->key();
}
return $path;
}
public function valid(): bool
{
return $this->inner->valid();
}
public function rewind(): void
{
$this->inner->rewind();
}
}
$input = [
'steve' => [
'id' => [
'#text' => 1,
],
],
'albert' => [
'id' => [
'#text' => 2,
],
],
'john' => [
'profil' => [
'id' => [
'#text' => 3,
],
],
],
];
// this is the filter function that should be customized given your requirements
// or create a factory function which produces these types of filter functions
$filter = static function ($current, array $path): bool {
// with help from the PathAsKeyDecorator
// we can decide on the path to the current value
return ['id', '#text'] === array_slice($path, -2)
// and the current value
&& 2 === $current;
};
// configure the iterator
$it = new CallbackFilterIterator(
new PathAsKeyDecorator(new RecursiveIteratorIterator(new RecursiveArrayIterator($input))),
$filter,
);
// traverse the iterator
foreach ($it as $path => $val) {
print_r([
'path' => $path,
'val' => $val
]);
}
An alternative approach could be one with simple functions, like:
Demo here
<?php
declare(strict_types=1);
function iterateWithPath(iterable $input, array $path = []): iterable {
foreach ($input as $key => $value) {
$pathToHere = [...$path, $key];
is_iterable($value)
? yield from iterateWithPath($value, $pathToHere)
: yield $pathToHere => $value;
}
}
function filterIterable(iterable $it, callable $filter): iterable {
foreach ($it as $key => $value) {
if ($filter($value, $key)) {
yield $key => $value;
}
}
}
$input = [
'steve' => [
'id' => [
'#text' => 1,
],
],
'albert' => [
'id' => [
'#text' => 2,
],
],
'john' => [
'profil' => [
'id' => [
'#text' => 3,
],
],
],
];
$it = filterIterable(iterateWithPath($input), static function ($current, array $path): bool {
return ['id', '#text'] === array_slice($path, -2)
&& 2 === $current;
});
foreach ($it as $path => $val) {
print_r([
'path' => $path,
'val' => $val
]);
}
complete edit cause of structure change of the array. the json_encode is used for changing the array to a string. Only does its job when there is in every array slice an id and an pseudo.
$data = [
'steve' => [
'id' => [
'#text' => 1,
],
'pseudo' => [
'#text' => 'LOL'
],
],
'albert' => [
'id' => [
'#text' => 2,
],
'pseudo' => [
'#text' => 'KILLER'
],
],
'john' => [
'id' => [
'#text' => 3,
],
'pseudo' => [
'#text' => 'NOOBS'
],
],
];
$data = json_encode($data, JSON_NUMERIC_CHECK);
preg_match_all('~"id":{"#text":([^{]*)}~i', $data, $ids);
preg_match_all('~"pseudo":{"#text":"([^{]*)"}~i', $data, $pseudos);
$lnCounter = 0;
$laResult = array();
foreach($ids[1] as $lnId) {
$laResult[$lnId] = $pseudos[1][$lnCounter];
$lnCounter++;
}
echo $laResult[2];
results in KILLER
I'd like to sort the following associative array:
$tree = [
"id" => 245974,
"children" => [
[
"id" => 111
],
[
"id" => 245982,
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225892
],
[
"id" => 225893
],
[
"id" => 225902
]
]
]
]
]
]
];
Desired sort order after the "search value" of id => 225902:
[
"id" => 245974,
"children" => [
[
"id" => 245982, // <-- this is moved up
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225902 // <-- this is moved up
],
[
"id" => 225892
],
[
"id" => 225893
]
]
]
]
],
[
"id" => 111
]
]
];
What I've tried:
<?php
$category_id = 225902;
function custom_sort(&$a, &$b) {
global $category_id;
if ($a['id'] === $category_id) {
return -1;
}
if ($b['id'] === $category_id) {
return 1;
}
if (array_key_exists('children', $a)) {
if (usort($a['children'], "custom_sort")) {
return -1;
}
}
if (array_key_exists('children', $b)) {
if (usort($b['children'], "custom_sort")) {
return 1;
}
}
return 0;
}
function reorder_tree($tree) {
usort($tree['children'], "custom_sort");
return $tree;
}
echo "<pre>";
var_dump(reorder_tree($tree));
echo "</pre>";
However, that returns:
[
"id" => 245974,
"children" => [
[
"id" => 245982, // <- this is moved up
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225892
],
[
"id" => 225893
],
[
"id" => 225902 // <- this is *not* moved up
]
]
]
]
],
[
"id" => 111
],
]
];
How would I be able to also sort the children arrays?
Great attempt and very much on the right track. The problem with recursion in the comparator is that usort will not call the comparator function when the array length is 1, so whether or not you explore the whole tree is at the whim of usort. This will abandon id => 245982's branch of the tree.
The solution is to avoid recursing in the usort's comparator function directly. Rather, use a regular recursive function that calls usort as needed, namely, the current array or a child array contains the target id. I use a separate array to keep track of which elements should be moved forward, but you can break out of the loop and splice/unshift a single element to the front if you prefer.
We can also make $category_id a parameter to the function.
Here's one approach:
function reorder_tree_r(&$children, $target) {
$order = [];
$should_sort = false;
foreach ($children as $i => &$child) {
$order[$i] = false;
if (array_key_exists("children", $child) &&
reorder_tree_r($child["children"], $target) ||
$child["id"] === $target) {
$order[$i] = true;
$should_sort = true;
}
}
if ($should_sort) {
$priority = [];
$non_priority = [];
for ($i = 0; $i < count($children); $i++) {
if ($order[$i]) {
$priority[]= $children[$i];
}
else {
$non_priority[]= $children[$i];
}
}
$children = array_merge($priority, $non_priority);
}
return $should_sort;
}
function reorder_tree($tree, $target) {
if (!$tree || !array_key_exists("children", $tree)) {
return $tree;
}
reorder_tree_r($tree["children"], $target);
return $tree;
}
var_export(reorder_tree($tree, 225902));
Output:
array (
'id' => 245974,
'children' =>
array (
0 =>
array (
'id' => 245982,
'children' =>
array (
0 =>
array (
'id' => 246093,
'children' =>
array (
0 =>
array (
'id' => 225902,
),
1 =>
array (
'id' => 225892,
),
2 =>
array (
'id' => 225893,
),
),
),
),
),
1 =>
array (
'id' => 111,
),
),
My collection:
($usersWithCommission) Illuminate\Support\Collection {#2625
#items: array:2 [
0 => array:3 [
"userId" => 1
"name" => "Sim Aufderhar"
"net_commission" => null
]
1 => array:3 [
"userId" => 2
"name" => "Carolyn Lang III"
"net_commission" => null
]
]
}
I would like modify the net_commission property, but I can not:
foreach ($soldProperties as $property) {
if (!$property->buyer_user_id && !$property->seller_transferring_user_id) {
$usersWithCommission->where('userId', $property->user_id)->first()['net_commission'] += $property->net_commission_of_sold;
}
}
How I can?
Thanks your answers.
Collections provide a method map which allows you to iterate your collection and add/modify fields.
function modify_net_commision($var) {
return YOUR_LOGIC_HERE;
}
$collection = [
[ "userId" => 1, "name" => "Sim Aufderhar", "net_commission" => null ],
[ "userId" => 2, "name" => "Carolyn Lang III", "net_commission" => null ],
];
$external_var = 'I will be used on modify_net_commision function';
$new_collection = collect($collection)->map(function ($arr) use ($external_var) {
$arr['net_commission'] = modify_net_commision($external_var);
return $arr;
})
If you want to remove some fields from your collection, use reject method.
Doc: https://laravel.com/docs/5.8/collections
Hope it helps you.
Have a great day.
public function test()
{
$collection = [
[ "userId" => 1, "name" => "Sim Aufderhar", "net_commission" => null ],
[ "userId" => 2, "name" => "Carolyn Lang III", "net_commission" => null ],
];
$data= collect($collection)->map(function($collection, $key) {
$collect = (object)$collection;
return [
'userId' => $collect->userId,
'name' => $collect->name,
'net_commission' => $this->modify_commision($key)
];
});
dd($data);
}
public function modify_commision($key) {
$property = [
['userId' => 1 ,'net_commission_of_sold' => 30],
['user_id' => 2,'net_commission_of_sold' => 40]
];
return $property[$key]['net_commission_of_sold'];
}
***Hope it helps you***
API result:
php laravel
the article_user and article_title is repeated.
i use transformer to transform my data.
public function transform(ArticleComment $comments)
{
$articleInfo = $comments->article;
$user = UserInfo::select('real_name')->find($articleInfo->created_user_id);
return [
'article_user' => $user->real_name,
'article_title' => $articleInfo->title,
'is_evaluator' => $comments->is_evaluation,
'comment_created_user' => $comments->user,
'created_at' => $comments->created_at,
'comment_content' => $comments->content,
'replied_comment' => $comments->reply,
'replied_user' => $comments->repliedUser,
];
}
}
i want it like this
article_info:{
article_user: "",
article_title: "",
}
article_comment:{
content: "",
is_evaluator: 0,
...
}
how to optimize it ?
what should i do ?
the response
You can use simple arrays to format your response. In your case it should be:
public function transform(ArticleComment $comments)
{
$articleInfo = $comments->article;
$user = UserInfo::select('real_name')->find($articleInfo->created_user_id);
return [
'article_info' => [
'article_user' => $user->real_name,
'article_title' => $articleInfo->title
],
'article_comment' => [
'is_evaluator' => $comments->is_evaluation,
'comment_created_user' => $comments->user,
'created_at' => $comments->created_at,
'comment_content' => $comments->content,
'replied_comment' => $comments->reply,
'replied_user' => $comments->repliedUser
]
];
}
I need to "reformat" some data coming from an external API so it works with the nested list module of Sencha touch. I cannot change the data output of that external API. Here's an example of the data I get from the API:
$quest = array(
'gastronomy' => [
'restaurants' => [
'italians' => [
[
'title' => 'Al Castello',
'leaf' => true
],
[
'title' => 'Italia',
'leaf' => true
]
],
'asians' => [
[
'title' => 'Gautam',
'leaf' => true
],
[
'title' => 'Wok',
'leaf' => true
]
]
]
]
);
In order to make it work with sencha touch the data must look like this after "reformatting" it with a PHP Service:
$result = array(
'items' => [
[
'title' => 'gastronomy',
'items' => [
[
'title' => 'restaurants',
'items' => [
[
'title' => 'italians',
'items' => [
[
'title' => 'Al Castello',
'leaf' => true
],
[
'title' => 'Italia',
'leaf' => true
]
]
],
[
'title' => 'asians',
'items' => [
[
'title' => 'Gautam',
'leaf' => true
],
[
'title' => 'Wok',
'leaf' => true
]
]
]
]
]
]
]
]
);
I have tried every way I could think of but with no success. What really bugs me is that all keys must be renamed to items. (It's hard for me to access the deeper nested items because of that when I'm using a recursive function)
Haven't tested it, but it seems like a fairly simple recursive function should handle it.
For example:
function parseApi($arr) {
$result = array();
foreach ($arr as $key => $value) {
if (isset($value['leaf'])) {
$result[] = $value;
} else {
$result[] = array(
'title' => $key,
'items' => parseApi($value)
);
}
}
return $result;
}
$result = array( 'items' => $parseApi($quest);
You need a recursive function, and it needs to be able to tell the difference between associative and numerically-indexed arrays.
// from: http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
function isAssoc($arr) { return array_keys($arr) !== range(0, count($arr) - 1); }
function itemize($foo) {
$output = [];
if( ! isAssoc($foo) ) {
foreach( $foo as $value ) {
if( is_array($value) ) {
$output[] = itemize($value);
} else {
$output[] = $value;
}
}
} else {
foreach( $foo as $key => $value ) {
if( is_array($value) ) {
$output[] = [
'title' => $key,
'items' => itemize($value)
];
} else {
$output[$key] = $value;
}
}
}
return $output;
}
echo json_encode(itemize($quest), JSON_PRETTY_PRINT);
Output:
[
{
"title": "gastronomy",
"items": [
{
"title": "restaurants",
"items": [
{
"title": "italians",
"items": [
{
"title": "Al Castello",
"leaf": true
},
{
"title": "Italia",
"leaf": true
}
]
},
{
"title": "asians",
"items": [
{
"title": "Gautam",
"leaf": true
},
{
"title": "Wok",
"leaf": true
}
]
}
]
}
]
}
]