"yield" and "yield from" at same function - php

I need use yield and yield from at same function, but it seems not works as intended, once that it yields only the last yield from or yield (what comes last).
My code is (https://3v4l.org/jFDXh):
function yieldItems() {
yield 1;
yield 2;
yield 3;
yield from [4, 5, 6];
yield from [7, 8, 9];
}
var_dump(
iterator_to_array(
yieldItems()
)
);
For all PHP versions it will only outputs [ 7, 8, 9 ], but seems clear to me that it should be [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] instead.
However, if I do the same through a foreach, everything seems normal. It looks like it is a problem related to iterator_to_array(), but in fact, I need to convert this Generator to an array.
So, what I am missing?

When I was concluding, I realized that the problem was really related to iterator_to_array(), so I decided to search more deeply in the PHP Documentation / Generators and it shows precisely this type of problem.
Following the documentation:
yield from does not reset the keys. It preserves the keys returned by the Traversable object, or array. Thus some values may share a common key with another yield or yield from, which, upon insertion into an array, will overwrite former values with that key.
A common case where this matters is iterator_to_array() returning a keyed array by default, leading to possibly unexpected results. iterator_to_array() has a second parameter use_keys which can be set to FALSE to collect all the values while ignoring the keys returned by the Generator.
What this means, in general, is that when using yield/yield from, it will outputs keys too (the first yield will be 0, for instance), just as it actually exists for pure arrays. So the code below will fail similarly (https://3v4l.org/pWeWT):
function willOutputSingle() {
yield 'sameKey' => 'originalValue';
yield 'sameKey' => 'otherValue';
}
var_dump(
iterator_to_array(
willOutputSingle()
)
);
// Outputs: [ 'sameKey' => 'otherValue' ]
It will happen because we yields to pairs like [ 'sameKey' => 'originalValue' ] and then [ 'sameKey' => 'otherValue' ], so when we converts it to array, via iterator_to_array() the results is basically that:
[ 'sameKey' => 'originalValue',
'sameKey' => 'otherValue ]
But how PHP does not allows identical keys, only the last one is preserved.
The solution to this is pass false as second argument to iterator_to_array(), because it will not preserve keys yielded, so result will be an zero-index array. The result will be:
[ 0 => 'originalValue',
1 => 'otherValue' ]

Related

PHP pack with multiple values with different lengths

I'd like to pack a string consisting of a 64 bits, 32 bits and 32 bits ints.
I don't have much experience with the pack function (or bits altogether) so I'm trying to do something like this:
pack('JNN', 1, 2, 3);
// and
unpack('JNN');
But that does not yield the result I'm after.
The problem is that when I run that code I receive the following:
array [
"NN" => 1
]
But I expected this:
array [
1,
2,
3
]
Any idea how to approach this?
Thanks in advance,
pack creates a 16-character string.
$str = pack('JNN', 1, 2, 3);
//string(16) "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03"
unpack requires keys for the format elements. Example:
$arr = unpack('Jint64/N2int32_',$str);
/*
array (
'int64' => 1,
'int32_1' => 2,
'int32_2' => 3,
)
*/
For more examples, see Unpack in the PHP manual.
If purely numeric keys are required, the array_values function can be used.

Why does array_uintersect_assoc() need the comparison function to return non-boolean values?

I wondered why array_uintersect_assoc()'s custom comparison function:
must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second
When I compare two arrays, I only need a boolean return value: the elements either match or they don't.
What's the actual reason of this behavior?
The function has been implemented this way to allow the usage of "classical" comparison functions which use such a return strategy. Such a function typically needs to be able to express three cases which is not possible with a boolean return value, for obvious reasons.
You can, however, also use a comparison function which does return a boolean result, since php as a weak typed language will automatically convert that for you. Take a look at that example which is a slightly modifed version of the one given in the function documentation:
<?php
function mystrcasecmp($a, $b) {
return strcasecmp($a, $b) ? true : false;
}
$array1 = array("a" => "green", "b" => "brown", "c" => "blue", "red");
$array2 = array("a" => "GREEN", "B" => "brown", "yellow", "red");
print_r(array_uintersect_assoc($array1, $array2, "mystrcasecmp"));
You can see that the comparison function used here returns a boolean, yet the result is exactly the same.
Bottom line: the existing implementation is more flexible, whilst allowing for the use of a comparison function returning a boolean result too.
I only need boolean value: the elements either match or they dont.
TL;DR
You need to invert the boolean value which will be converted to an int because these array_intersect and array_diff functions that call custom functions only qualify data that returns a zero-ish result (i.e.: null, false, "0", 0, empty string, empty array). Here is a ternary implementation:
array_uintersect_assoc($array1, $array2, fn($a, $b) => str_contains($a, $b) ? 0 : 1)
The explanation...
It's easy to get confused with this operation. Your question led me to post Unexpected results with array_uintersect_assoc() when callback returns non-numeric string.
Let's say you want to use array_uintersect_assoc() and you have these two input arrays:
$array1 = ["a" => "green", "b" => "brown", "c" => "blue", 0 => "red"];
$array2 = ["a" => "GREEN", "B" => "brown", 0 => "yellow", 1 => "red", "c" => "blue"];
Now let's say you want to make a custom function call which returns a boolean value. I'll nominate PHP8's str_contains() which will be sufficient for this demonstration. The first array will contain the haystack strings and the second array will contain the needle strings.
var_export(array_uintersect_assoc($array1, $array2, 'str_contains'));
This will check for identical keys from the first array in the second array THEN among those qualifying elements, it will check if the second array's value is found within the first array's string. Being an "intersect" call, you would intuitively expect:
['c' => 'blue']
because only the c-keyed element in the second array has a value where the second array's value case-sensitively exists in the first array's value.
However, what you actually get is:
['a' => 'green', 0 => 'red']
What?!? The reason that you get elements with keys a and 0 in the result is because any of the array_ diff/intersect functions that include u in their name are making a qualifying match when a 0 result is returned.
When the c elements' values are fed to str_contain(), a true boolean is returned. array_uintersect_assoc() then forces the boolean value to be converted to an int type. When converting booleans to ints, false becomes 0 and true becomes 1.
To fix this behavior to get the intended result, you cannot simply change the intersect word inside of the function's name to diff -- that creates:
['b' => 'brown', 'c' => 'blue']
This is because b doesn't have an identical corresponding key in the second array. c does have an identical corresponding key, and the true result from str_contains() is evaluated as "don't keep" by array_udiff_assoc().
Finally, the fix is to invert the boolean value so that a true becomes 0 and a false becomes a non-zero. (Demo)
var_export(
array_udiff_assoc(
$array1,
$array2,
fn($a, $b) => str_contains($a, $b) ? 0 : 1;
// or !str_contains($a, $b) until the day when PHP throws a DEPRECATED warning for returning a boolean
)
);

Using Laravel's pluck method without an associative array

Let's say I've got an Articles collection, and I want to fetch the popular articles.
So first, I make a scopePopular() method in the Article method. Very simple.
Now I want to fetch their ids, so I'll do this:
Article::popular->pluck('id');
The result will be an associative array:
[
0 => 1,
1 => 34,
2 => 17
...
];
I want to get a normal array, without the keys, Like:
[1, 34, 17]
I know I can just do something like this:
array_values(Article::popular->pluck('id'));
But I believe Laravel has much more cleaner way to do this. Any ideas?
All arrays have indexes.
[
0 => 1,
1 => 34,
2 => 17
];
equals to
[1, 34, 17]
In other words:
$a1 = [0 => 1, 1 => 34, 2 => 17];
$a2 = [1, 34, 17];
$a1 === $a2;
// returns True
You can use values() method which is wrapper for array_values():
Article::popular->pluck('id')->values();
Its exactly what you need and what you get, By default php has the incremental key from 0.
You want to see something like a JSON array I assume.
Just do a return the array and you will see the JSOn array in browser, but internally its like this only.
Please confirm and let me know.Thanks
$store_type = \App\Websites::find($request->store_id)->first()->store_type;
// Outputs - just a string like "live" // there was option like a demo, live, staging ...
It might be useful for someone else.

Return all records that match array of conditions using CakePHPs find

I'm currently attempting to return all records that match an array of conditions that I have. Currently I can get my code to work, but instead of returning all records that match an array of conditions that I've passed, it just returns the first one and then stops, instead of the four that I know exist in the table I'm accessing. This is with the all parameter set for find.
Here's the code snippet for a better view:
$array = implode(',', array('1','2','3','4'));
$a = $this->Assets->find('all', array(
'conditions' => array(
'id' => $array
)
)
);
var_dump($a);
var_dumping $a will just provide the record that has id 1, when there's records that exist for 2, 3, and 4 as well.
That is the expected result.
You are working against the ORMs auto-magic. Passing a string will result in an equality comparison, ie WHERE x = y, and since id is most probably an integer, the casting will turn the string 1,2,3,4 into 1, so ultimately the condition will be WHERE id = 1.
You should pass the array instead
'conditions' => array(
'id' => array(1, 2, 3, 4)
)
that way the ORM will generate an IN condition, ie WHERE id IN (1,2,3,4).
This is also documented in the cookbook:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#complex-find-conditions

Is there a way to check if one of the array entries contains a sub-array in MongoDB?

Due to a bug in my PHP script, I have created multiple erroneous entries in my MongoDB. Specifically, I was using $addToSet and $each and under certain circumstances, the MongoDB object gets updated wrongly as below:
array (
'_id' => new MongoId("4fa4f815a6a54cedde000000"),
'poster' => 'alex#randommail.com',
'image' =>
array (
'0' => 'image1.jpg',
'1' => 'image2.jpg',
'2' => 'image3.png',
'3' =>
array (
'$each' => NULL,
),
),
You can see that "image.3" is different from the other array entries, and that was the incorrect entry. I have fixed the related in my PHP script, however I am having difficulties tracking down all the affected entries in MongoDB to remove such entries.
Is there any way in MongoDB to check if any of the image array entry contains another array instead of string? Since the index of containing the sub-array is a variable, it would not be possible to perform a $type check on image.3 for every entry.
Could use any suggestion. Thanks!
Since this was an error in your PHP, I suppose, now you want to upgrade all erroneous documents.
If so, you could just find all documents where array item is an array itself.
> db.arr.insert({values: [1, 2, 3, [4, 5]]})
> db.arr.insert({values: [6, 7, 8]})
>
> db.arr.find({values: {$type: 4}})
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3, [ 4, 5 ] ] }
Now let's fix this. To remove such entries without fetching them to PHP I offer this simple two-step operation.
First, find all documents with arrays and unset those arrays. This will leave nulls in place of them. Note, it will only match and update first array in the document. If there are several, you want to repeat the operation
> db.arr.update({values: {$type: 4}}, {$unset: {'values.$': 1}}, false, true);
> db.arr.find()
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3, null ] }
Remove nulls.
> db.arr.update({values: null}, {$pull: {values: null}}, false, true);
> db.arr.find()
{ "_id" : ObjectId("4fae0750d59332f28c702618"), "values" : [ 1, 2, 3 ] }

Categories