Can anyone explain why this happens? Side question: is there a way to make it work using DirectoryIterator so the correct objects are stored in $j and $k?
<?php
// my name is dirtest.php and I live on my own in a directory
$i = new DirectoryIterator(".");
$j = [];
$k = [];
function println($val) {
echo $val . "\n";
}
println('First time:');
foreach ($i as $x) {
$j[] = $x;
println($x->getFilename());
}
println('Second time:');
foreach ($j as $y) {
$k[] = $y->getFilename();
println($y->getFilename());
}
Expected:
First time:
.
..
dirtest.php
Second time:
.
..
dirtest.php
Actual:
First time:
.
..
dirtest.php
Second time:
First time through all seems as expected but, after storing references to each file in $j , each element appears to lose its reference. (I started out trying to use filter/map functions from this library but reduced the problem down to the listing above.)
Tested with PHP 5.4.27.
$x is an object reference of type DirectoryIterator, and as a result, all elements in $j are identical. Once the iterator has finished working, it has nothing to display.
http://www.php.net/manual/en/language.oop5.object-comparison.php
When you attribute a value of an object, the object isn't copied, but the reference to that object is attributed, so they are identical.
Now, when DirectoryIterator iterates in a foreach(), the next() method is called on each iteration, so your $x is the same reference each time, but the object has changed. But all the references still point to the same object, which, in the end, after the iterations have ended, doesn't refer to any file.
For fun, you can do a:
for($i=1;$i<count($j);$i++) {
var_dump($j[$i-1]===$j[$i]); echo "\n";
}
you should get all lines bool(true)
What I really wanted to do was along the lines of this:
<?php
use Functional as F;
$i = new DirectoryIterator('.');
$j = F\filter($i, function ($x) { return $x->isDot(); });
looks good, but $j is not what you might think!
But I can add an extra step...
<?php
use Functional as F;
$i = new DirectoryIterator('.');
function deref($xs) {
return F\map($i, function ($x) { return clone $x; });
}
$j = F\filter(
deref($i),
function ($x) { return $x->isDot(); })
);
$j is now a cool filtered beer array containing my directory entries.
Related
I'd like to implement a for loop using the $array as $element syntax in an anonymous function
Is this possible? I can't use forEach as the $array is actually an object, and forEach doesn't copy the object as it would with an array
I'm using laravel so I'm trying to write a blade directive like forEach but in a way that it makes a copy of the object.
something like:
Blade::directive('safeForEach', function ($array as $el){
return '<?php for ($i = 0; $i < count($array); $i++){ ?>';
});
where after #safeForEach is called, I can use each $el.
Blade::directive('endSafeForEach', function (){
return '<?php { ?>';
});
Any ideas please? I've tried to find the source for the blade directive but can't find it.
Thanks
The directive command's callback takes a single parameter which is the string that is passed in the directive which you need to parse. You can check the source code of Laravel and adapt it to your own needs:
Blade::directive('safeForEach', function ($expression) {
preg_match('/\( *(.*) +as *(.*)\)$/is', $expression, $matches);
$iteratee = trim($matches[1]);
$iteration = trim($matches[2]);
$initLoop = "\$__currentLoopData = {$iteratee}; \$__env->addLoop(\$__currentLoopData);";
$iterateLoop = '$__env->incrementLoopIndices(); $loop = $__env->getLastLoop();'.$iteration.'=clone '.$iteratee.'[$i]';
return '<?php '. $initLoop.' for ($i = 0; $i < count('.$iteratee.'); $i++) { '.$iterateLoop.' ?>';
});
Blade::directive('endSafeForEach', function () {
return '<?php } ?>';
});
If for example you pass #safeForEach($a as $b) this should compile to:
$__currentLoopData = $a;
$__env->addLoop($__currentLoopData);
for ($i = 0;$i < count($a);$i++) {
$__env->incrementLoopIndices();
$loop = $__env->getLastLoop();
$b = clone $a[$i];
however you need to ensure that
$iteratee is countable
$i does not conflict. Laravel uses a variable called $__env to store loop information and assumes that it's not used elsewhere but given the name it's less likely to be used.
clone will only make a shallow copy of an object and might break for non-clonable elements so you might also need to refine that part
I have this code:
<?php
function generator() {
yield 'First value';
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
//$gen->next();
foreach ($gen as $value) {
echo $value . '<br/>';
}
This outputs:
First value
First value
1
2
3
I need the 'First value' to yielding only once. If i uncomment $gen->next() line, fatal error occured:
Fatal error: Uncaught exception 'Exception' with message 'Cannot rewind a generator that was already run'
How can I solve this?
The problem is that the foreach try to reset (rewind) the Generator. But rewind() throws an exception if the generator is currently after the first yield.
So you should avoid the foreach and use a while instead
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
chumkiu's answer is correct. Some additional ideas.
Proposal 0: remaining() decorator.
(This is the latest version I am adding here, but possibly the best)
PHP 7+:
function remaining(\Generator $generator) {
yield from $generator;
}
PHP 5.5+ < 7:
function remaining(\Generator $generator) {
for (; $generator->valid(); $generator->next()) {
yield $generator->current();
}
}
Usage (all PHP versions):
function foo() {
for ($i = 0; $i < 5; ++$i) {
yield $i;
}
}
$gen = foo();
if (!$gen->valid()) {
// Not even the first item exists.
return;
}
$first = $gen->current();
$gen->next();
$values = [];
foreach (remaining($gen) as $value) {
$values[] = $value;
}
There might be some indirection overhead. But semantically this is quite elegant I think.
Proposal 1: for() instead of while().
As a nice syntactic alternative, I propose using for() instead of while() to reduce clutter from the ->next() call and the initialization.
Simple version, without your initial value:
for ($gen = generator(); $gen->valid(); $gen->next()) {
echo $gen->current();
}
With the initial value:
$gen = generator();
if (!$gen->valid()) {
echo "Not even the first value exists.<br/>";
return;
}
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
for (; $gen->valid(); $gen->next()) {
echo $gen->current() . '<br/>';
}
You could put the first $gen->next() into the for() statement, but I don't think this would add much readability.
A little benchmark I did locally (with PHP 5.6) showed that this version with for() or while() with explicit calls to ->next(), current() etc are slower than the implicit version with foreach(generator() as $value).
Proposal 2: Offset parameter in the generator() function
This only works if you have control over the generator function.
function generator($offset = 0) {
if ($offset <= 0) {
yield 'First value';
$offset = 1;
}
for ($i = $offset; $i <= 3; $i++) {
yield $i;
}
}
foreach (generator() as $firstValue) {
print "First: " . $firstValue . "\n";
break;
}
foreach (generator(1) as value) {
print $value . "\n";
}
This would mean that any initialization would run twice. Maybe not desirable.
Also it allows calls like generator(9999) with really high skip numbers. E.g. someone could use this to process the generator sequence in chunks. But starting from 0 each time and then skipping a huge number of items seems really a bad idea performance-wise. E.g. if the data is coming from a file, and skipping means to read + ignore the first 9999 lines of the file.
solutions provided here does not work if you need to iterate more than once.
so I used iterator_to_array function to convert it to array;
$items = iterator_to_array($items);
I'm using json_decode to parse JSON files. In a for loop, I attempt to capture specific cases in the JSON in which one element or another exist. I've implemented a function that seems to fit my needs, but I find that I need to use two for loops to get it to catch both of my cases.
I would rather use a single loop, if that's possible, but I'm stuck on how to get both cases caught in a single pass. Here's a mockup of what I would like the result to look like:
<?php
function extract($thisfile){
$test = implode("", file($thisfile));
$obj = json_decode($test, true);
for ($i = 0; $i <= sizeof($obj['patcher']['boxes']); $i ++) {
//this is sometimes found 2nd
if ($obj['patcher']['boxes'][$i]['box']['name'] == "mystring1") {
}
//this is sometimes found 1st
if ($obj['patcher']['boxes'][$i]['box']['name'] == "mystring2") {
}
}
}
?>
Can anyone tell me how I could catch both cases outlined above within a single iteration?
I clearly could not do something like
if ($obj['patcher']['boxes'][$i]['box']['name'] == "string1" && $obj['patcher']['boxes'][$i]['box']['name'] == "string2") {}
...because that condition would never be met.
Generally what I do when I have raw data that is in an order that isn't ideal to work with is to run a first loop pass to generate a a list of indexes for me to pass through a second time.
So a quick example from your code:
<?php
function extract($thisfile){
$test = implode("", file($thisfile));
$obj = json_decode($test, true);
$index_mystring2 = array(); //Your list of indexes for the second condition
//1st loop.
$box_name;
for ($i = 0; $i <= sizeof($obj['patcher']['boxes']); $i ++) {
$box_name = $obj['patcher']['boxes'][$i]['box']['name'];
if ( $box_name == "mystring1") {
//Do your code here for condition 1
}
if ($box_name == "mystring2") {
//We push the index onto an array for a later loop.
array_push($index_mystring2, $i);
}
}
//2nd loop
for($j=0; $j<=sizeof($index_mystring2); $j++) {
//Your code here. do note that $obj['patcher']['boxes'][$j]
// will refer you to the data in your decoded json tree
}
}
?>
Granted you can do this in more generic ways so it's cleaner (ie, generate both the first and second conditions into indexes) but i think you get the idea :)
I found that something like what #Jon had mentioned is probably the best way to attack this problem, for me at least:
<?php
function extract($thisfile){
$test = implode("", file($thisfile));
$obj = json_decode($test, true);
$found1 = $found2 = false;
for ($i = 0; $i <= sizeof($obj['patcher']['boxes']); $i ++) {
//this is sometimes found 2nd
if ($obj['patcher']['boxes'][$i]['box']['name'] == "mystring1") {
$found1 = true;
}
//this is sometimes found 1st
if ($obj['patcher']['boxes'][$i]['box']['name'] == "mystring2") {
$found2 = true;
}
if ($found1 && $found2){
break;
}
}
}
?>
i have a very simple question. How can i make this code
$i = 0;
foreach($Array as $Value)
{
echo $i;
$i++
}
but written like this?
foreach($Array as $Value)
{
$i = 0;
echo $i;
$i++
}
should i use a STATIC variable? or what? I don't have a clear view on this.
Thank you!
You shouldn't really do that. static variables are used to persist a variable's value between invocations of the function they're embedded in. They're not useful for a simple loop. Your second code will simply reset the counter to zero on every iteration.
e.g. this is a correct usage:
function count() {
static $x = 0; // executed the first time count() is called, then never again"
echo ++$x;
}
count(); // 1
count(); // 2
count(); // 3
You can certainly have
foreach($array as $val) {
static $x = 0;
echo ++$x;
}
but you don't gain anything, since that particular piece of code never goes out of scope for the duration of the loop, so $x's value would never get "lost".
you may want use
foreach($Array as $i => $Value)
{
echo $i;
}
or
foreach(array_values($Array) as $i=>$Value)
{
echo $i;
}
While first your example is correct, too
I'm experimenting with classes and objects for the first time and I thought I'd make a template for a Box that can store things like Books. (Thinking in terms of real-world items)
<?php
function feetToInches($feet){
$feet = $feet * 12;
return $feet;
}
class Book{
var $l = 6;
var $w = 5;
var $h = 1;
}
class Box{
//This is a box. It has length, width, and height, and you can put things in it.
var $length = 0;
var $width = 0;
var $height = 0;
var $storedArray = array();
function setDimensions($l, $w, $h){
$this->length = feetToInches($l);
$this->width = feetToInches($w);
$this->height = feetToInches($h);
}
function storeThings($thing){
$this->storedArray[] = $thing;
}
function getThings(){
return $this->storedArray;
}
}
$thatBook = new Book;
$BookBox = new Box;
$BookBox->setDimensions(6,5,1);
for($i = 0; $i < 5; $i++){
$BookBox->storeThings($thatBook);
}
echo $BookBox->getThings() . "<br />";
/*
foreach($BookBox->getThings() as $item){
echo $item;
}
*/
var_dump($BookBox);
?>
So what I have is simple here, you have boxes of a dimension, and you throw books of a fixed dimension in them.
Putting things in it is no problem, but when I try to retrieve them, I either get errors or nothing happens. And when I try to specify a key for the array like
echo $BookBox->getThings()[2];
I get an error that it's not an array or something.
So can someone please point me in the right direction here?
And normally the class would be a separate file, but I'm just learning here.
What version of PHP are you using.
Array dereferencing (referencing into a returned array) was only added in PHP 5.4.
If you're using a previous version, you'd have to do this:
$books = $BookBox->getThings();
echo $books[2];
Edit
Since you are pushing Books into a box, $books[2] returns you an instance of the Book object. Echo is used to output a string, hence the error.
You can either echo a particular property of the book, or print out all of the properties by doing:
print_r($books[2]);
First, you cannot do echo since what you'll get is not a string but an object. Use print_r() or var_dump() instead.
Second, just like the other answers here, you should do this.
$books = $BookBox->getThings();
print_r($books[2]);
But I suggest you to make getThings() accept a variable for getting a specified array element:
function getThings($key = null) {
if ($key) {
return isset($this->storedArray[$key]) ? $this->storedArray[$key] : null;
} else {
return $this->storedArray;
}
}
// Then you can do this
// print_r($BookBox->getThings(2));
What you are calling when you call $BookBox->getThings() is actually an object and not an array.
You might try something along the lines of:
$books = $BookBox->GetThings();
echo $books[2];