Repeat escaped foreach until count is met

Hi there,

I’m trying to repeat a foreach loop from a files field until the count meets at least 10 images. The foreach needs to escape so i can use thumbs and srcset.

For example, if the user has uploaded 5 images it’ll run twice, if 2 images then 5 times, if 10 images then once, if 11 then it’ll load the first 10.

I think i’m completely barking down the wrong tree but so far i’ve been pulling range to repeat the list then was thinking to cull it down to 10 images - is this the right way to go around this?

Any help is much appreciated!! Thanks.

<?php $projectimg = $project->imagery()->toFiles();
foreach (range(1,10) as $i): ?>
<?php foreach($projectimg as $file): ?>
<img class="lazy" src="<?= $file->thumb('standard')->url() ?>" srcset="<?= $file->thumb('standard')->url() ?>" data-src="<?= $file->url() ?>" data-srcset="<?= $file->srcset([720, 1280, 1920]) ?>" />
<?php endforeach ?>
<?php endforeach ?>

One of probably many ways to do this:

<?php $i = 0;
foreach($project->imagery()->toFiles() as $file): ?>
<img class="lazy" src="<?= $file->thumb('standard')->url() ?>" srcset="<?= $file->thumb('standard')->url() ?>" data-src="<?= $file->url() ?>" data-srcset="<?= $file->srcset([720, 1280, 1920]) ?>" />
<?php $i++;
if($i == 10) break;
endforeach; ?>

Thanks for your response Sebastian.

This however cuts off anything over 10 but it doesn’t repeat the loop if theres less than 10 objects in the field. I need it to repeat it until theres 10 iterations of the objects.

So if the user only uploads say 2 images into the field, it will repeat it 5 times to make sure theres 10 images being rendered.

Something like this (not tested);

$images = $project->imagery()->toFiles();
$count = $images->count();
$repeat = ceil(10/$count);
$imageArray = array_merge(...array_fill(0, $repeat, $images->toArray));
$i = 0 ;
while (  $i <= 10 ) {
  dump( $imageArray[$i]);
  $i++
}

You could prepend a test that if you already have enough images, you don’t have to do this array stuff…

1 Like

Sorry Paul, I indeed missed that aspect of your question! I like the Array approach above, yet here’s how I would probably have approached this without Sonja’s input (also untested, extending on my earlier suggestion of utilizing the break statement):

<?php
$projectimg = $project->imagery()->toFiles();
$i = 0;
while ($i < 10):
    foreach($projectimg as $file): ?>
        <img class="lazy" src="<?= $file->thumb('standard')->url() ?>" srcset="<?= $file->thumb('standard')->url() ?>" data-src="<?= $file->url() ?>" data-srcset="<?= $file->srcset([720, 1280, 1920]) ?>" alt="" />
        <?php $i++;
        if($i == 10)
            break;
    endforeach;
endwhile; ?>

The while loop restarts as long as $i has not reached 10 yet, then the foreach loop gets executed on every iteration of the while loop. In case we reach 10 within the foreach loop, it is interrupted by break and then the while loop will be exited because its condition is no longer true.

…that said, I’d personally drop my own approach and go with @texnixe’s suggestion.

PS: I took the freedom to add the missing alt attribute to your <img> tag for accessibility :wink:

Hey,

Thank you for your answers, very insightful.

I haven’t dealt with arrays much so stumbling my way through it and don’t quite get the result i’m after. Probably doing this completely wrong but the last result I got loaded the src with 10 iterations of the same URL wrapped in <pre> tags.

Very lost :woozy_face: This is where I am at…

<?php
$images = $project->imagery()->toFiles();
if($images->isNotEmpty()): ?>
<img src="<?php 
$count = $images->count();
$repeat = ceil(10/$count);
$imageArray = array_fill(0, $repeat, $images->toArray());
$merged = array_merge($imageArray);
$marray = array_values($merged)[0];
$i = 0 ;
while ($i <= 4) {
  $m2 = array_values($marray)[$i];
  print($m2['url']);
  $i++;
}
 ?>" alt="<?= $project->title() ?>"><?php endif ?>
<?php

// Get a max of 10 files
// Convert collection to array to reset item keys
$images = $project->imagery()->toFiles()->limit(10)->values();

// Add items until there are at least 10 of them
while (count($images) < 10) {
  array_push($images, ...$images);
}

// Ensure there are 10 items in the end
$images = array_slice($images, 0, 10);

?>

<?php foreach ($images as $image) : ?>
  <img class="lazy" src="<?= $image->thumb('standard')->url() ?>" srcset="<?= $image->thumb('standard')->url() ?>" data-src="<?= $image->url() ?>" data-srcset="<?= $image->srcset([720, 1280, 1920]) ?>" alt="" />
<?php endforeach ?>

* Requires PHP 7.4 (for earlier versions see)

1 Like

Another alternative:

// ...

// Add items until there are at least 10 of them
while (count($images) < 10) {
-  array_push($images, ...$images);
+  array_push($images, ...array_slice($images, 0, 10 - count($images)));
}
- 
- // Ensure there are 10 items in the end
- $images = array_slice($images, 0, 10);

Thanks for your suggestion but both these give me a 500 error and the page times out with:

Maximum execution time of 30 seconds exceeded

The PHP spread operator requires PHP 7.4.

You can replace it with array_merge if PHP 7.4 is not an option for you:

- array_push($images, ...array_slice($images, 0, 10 - count($images)));
+ $images = array_merge($images, array_slice($images, 0, 10 - count($images)));

Hi Pedro, running 7.4.2 and still timing out.

Its wrapped in a foreach element - is this a potential cause?

<ul>
        <?php foreach($page->children()->listed() as $project): ?>
        <li>
            <button onclick="projectop(this)">
                <?= $project->title() ?>
                <div class="imagecontainer">
                    <div class="images">
                        <div class="ticker" data-ticker>
                        <?php

// Get a max of 10 files
// Convert collection to array to reset item keys
$images = $project->imagery()->toFiles()->limit(10)->values();

// Add items until there are at least 10 of them
while (count($images) < 10) {
  array_push($images, ...$images);
}

// Ensure there are 10 items in the end
$images = array_slice($images, 0, 10);

?>

<?php foreach ($images as $image) : ?>
  <img class="lazy" src="<?= $image->thumb('standard')->url() ?>" srcset="<?= $image->thumb('standard')->url() ?>" data-src="<?= $image->url() ?>" data-srcset="<?= $image->srcset([720, 1280, 1920]) ?>" alt="" />
<?php endforeach ?>
                        </div>

                    </div>
                </div>
            </button>
          </li>
</ul>

You need to end the first foreach:

        </li>
+   <?php endforeach ?>
</ul>

You should be getting an error message about this. If you are not, enable the debug option in Kirby.

Another tip, getting into the habit of properly indenting your code helps prevent this kind of bug :wink:

Sorry that was in, I was paraphrasing the closing as there are other parts to the loop that are irrelevant to this.

That doesnt resolve the error.

Try to locate this error log, it will point you in the right direction.

My solution should work, at least it does in a project of mine I tested it on.

* The array_map line is not part of the solution, it just outputs the result in a way I can easily verify it.

No error log is being generated and its still not working.
Completely crashes the page with a 500 error so it doesn’t even hit the debug screen which is turned on in config.