3

is there a way in PHP, perhaps with an external library, to stream results from an API that responds with JSON data?

For instance I have the following code to get the data:

$resultsAPI = "https://www.example.com/api/results.json? 
app_id=$app_id&token=$token&page=1&limit=10";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $resultsAPI);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/json;api_version=2' ));
$resp = curl_exec($curl);
curl_close($curl);
$results = json_decode($resp, true)['results'];

foreach ($results as $key=>$resultImage) {
    $resultImage= "$resultImage[images]?app_id=$app_id&token=$token";
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $resultImage);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $resp = curl_exec($curl);
    curl_close($curl);
    $image = json_decode($resp, true);
    $results[$key]['image1'] = $image['image1'];
}

echo '<div class="card"><ul>';
foreach ($results as $result) {
echo '<li>';
echo '<span><p>'.$result['title'].'</p></span>';
echo '<span><p>'.$result['description'].'</p></span>';
echo '<span><img src="'.$result['image1'].'"></span>';
echo '</li>';
}
echo '</ul></div>';

It can take some time to load all data because it is going to loop over some large files. Is it possible to start streaming the results when it has the first data?

In the image below it shows what I am trying to explain. The data is being loaded in to the skeleton one by one:

Stream loading results

Any thoughts on this would be very helpful and or if it is possible at all.

Mark
  • 731
  • 2
  • 10
  • 29
  • Why not load the base page structure (without all the cards) and then load them asynchronous using AJAX? – Nico Haase Apr 15 '20 at 10:54
  • And if the data is not varying between the requests, why not cache the data? – Nico Haase Apr 15 '20 at 10:56
  • Perhaps the API itself might have a way already to get the data in one single request, instead of making one for each image separately …? With `www.example.com/api/` we can’t really tell. – CBroe Apr 15 '20 at 11:03
  • this is a preloader, it has nothing to do with streaming a API, if you mean the animation its just a normal pre-loader you can implement in [HTML/JS/CSS](https://codepen.io/naazim/pen/JKjgwL) but if you mean swipe up pagination with ajax, then its different story. – ROOT Apr 15 '20 at 12:02
  • So you're saying there's no way of streaming JSON from an API? I've seen some libraries than can do that but have no idea on how to use them – Mark Apr 17 '20 at 04:47

5 Answers5

3

I think you're run into the wrong direction.

HTML begin to render after load all the html. So fetch the html with stream is not work for you.

In the demo, it just load a simple html page. Then load the other parts of the page with something like ajax. Each time a part loaded then render it.

LF00
  • 27,015
  • 29
  • 156
  • 295
  • I already tried something with Ajax, but it just prints out the result when loading finishes. I am no expert in Ajax. Care to share an example? – Mark Apr 15 '20 at 04:11
  • you can make it a lazy load. – LF00 Apr 15 '20 at 04:12
  • Or you can make it with animation. – LF00 Apr 15 '20 at 04:14
  • I want to display 10 results and start printing them right away when php script is looping over those json files. Will lazy load start displaying results right away then? But I would still be stuck with the problem that the PHP has to run first and fetch all data. – Mark Apr 15 '20 at 04:14
1

Why not merge the the foreach loops? I haven't tested this but items should be echoed very iteration.

echo '<div class="card"><ul>';
foreach ($results as $key=>$result) {
    $resultImage= "$result[images]?app_id=$app_id&token=$token";
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $resultImage);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $resp = curl_exec($curl);
    curl_close($curl);
    $image = json_decode($resp, true);
    $results[$key]['image1'] = $image['image1'];

   echo '<li>';
   echo '<span><p>'.$result['title'].'</p></span>';
   echo '<span><p>'.$result['description'].'</p></span>';

   if(isset($image['image1'])){
       echo '<span><img src="'.$result['image1'].'"></span>';
   }
   echo '</li>';
}
echo '</ul></div>';

I hope that helps

AstralProgrammer
  • 169
  • 1
  • 12
  • I will give this a go and see if this works. I've already set something up in PHP with ob_flush/flush which outputs results one by one. But the stream is not direct! It still loads all data first and then outputs it – Mark Apr 17 '20 at 04:49
  • I tested it, and it works! I have got it working now finally! I've used your example and added `ob_flush();` and `flush();` to it, and now it outputs the results after every iteration. – Mark Apr 19 '20 at 03:58
1

As Kris Roofe said you may need ajax or axios call to output your data with some effects and animations exactly like the image that you attached in your question, your application need to be rendered first with all of it's HTML tags and assets like CSS and Javascript files then you can output some data and show it with animations by using ajax or axios and ofcurse you have more control over your data streaming in this case by using ajax or axios in client-side but you can also do it in your server-side but typically I prefer to do these things in client-side.

by the way if you insist to doing this in this way you can use flush() and ob_flush() to immediately output your data before the while loop ends. Someone has already mentioned this in php official documentation link. You can check and read full documentation about output buffering and these methods.

You should merge your two foreach loops together and then add these two methods add the end of your loop so it will make it to output your data immediately after each loop iterate.

foreach($results as $key=>$resultImage){
    //fetch images data such as title, description, and image itself in this loop
    // and aslo echo your html tags in here. 
    // echo '<li>';
    // echo '<span><p>'.$result['title'].'</p></span>';
    // echo '<span><p>'.$result['description'].'</p></span>';
    // echo '<span><img src="'.$result['image1'].'"></span>';
    // echo '</li>';

}

I wrote some comments in your for loop to show you that you should merge your loops together, because you are trying to initialize $results variable in your first loop, and then after finishing that loop you are iterating in $results variable to show output data. so you can't output data immediately with two loops in here because your second loop depends on first one and it will not start iterating until the first one finishes. check this little code that I wrote to demonstrate the usage of these two methods:

$curl = curl_init();
for($i=0;$i<5;$i++){
    curl_setopt($curl, CURLOPT_URL, 'example.com');
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    $output = curl_exec($curl);
    $output = json_decode($output);
    foreach($output as $key=>$value){
        echo 'key: '.$key;
        echo '$value: '.$value;
        ob_flush();
        flush();
    }
}

I would highly recommend you to read about output buffering to implement it correctly in your projects.

I hope this could help you.

Parsa Mir Hassannia
  • 351
  • 2
  • 6
  • 17
  • It does make sense since I am depending on the 2 for loops and would be better to merge them. I'll have a look into that. I've looked into ob_flush. Does that mean with ob_flush it'll start outputting the data immediately? – Mark Apr 17 '20 at 04:51
  • ob_flush function is used to flush output buffers to display output to the browsers, in this case if you use ob_flush() function at the end of your loops it will flush data to your browser after each loop iterate but make sure you have merged your two loops. it would be better if you could read about output buffering. by the way if there is any question about it, feel free and ask . – Parsa Mir Hassannia Apr 17 '20 at 08:41
  • adding `ob_flush();` and `flush();` indeed helped, I had been fiddling around with this but with the help and example of AstralProgrammer I have got it working finally! – Mark Apr 19 '20 at 04:00
  • @Mark well, I can't find the accepted answer usefel since in this case you can't get echoed output in php immediately before the loop ends without using flush() or ob_flush() methods which it has not mentioned in that answer. The accepted answers for every questions should help anyone who has the same problem. BTW I'm glad that you've found a solution. – Parsa Mir Hassannia Apr 19 '20 at 04:40
  • That's why I am in dubio here because his code did help me the most since it was merely a copy paste for me to get it to work. Adding the ob_flush method made it so it flushes out results right away. Wish I could accept multiple answers here since none of them are full. Yours does have the most info but didn't use your code example though. – Mark Apr 19 '20 at 04:43
  • @Mark great, if multiple answers helped you to find your solution, it would be better to update your question and explain briefly how you make it works to help others who has the same problem. Thank you – Parsa Mir Hassannia Apr 19 '20 at 04:50
0

HTML starts rendering when some of it arrives. It does not have to be complete source. In PHP you can "send what you already echoed" via ob_flush() and flush() calls. This way it will immediately display in the browser. This can be paired with JSON stream parsing using for example halaxa/json-machine, so the result can look something like this:

<?php
echo '<div class="card"><ul>';
foreach (JsonMachine::fromStream($jsonStreamResource) as $result) {
    echo '<li>';
    echo '<span><p>'.$result['title'].'</p></span>';
    echo '<span><p>'.$result['description'].'</p></span>';
    echo '<span><img src="'.$result['image1'].'"></span>';
    echo '</li>';
    ob_flush();
    flush();
}
echo '</ul></div>';
Filip Halaxa
  • 728
  • 6
  • 13
  • Can you please elaborate on why is this answer downvoted, so I can improve it? Thanks – Filip Halaxa Apr 18 '20 at 10:59
  • I have been trying on using json machine but could not get it to work. P.S. I haven't down voted your answer and think it is a good option for me to try and get JsonMachine working. – Mark Apr 19 '20 at 03:54
  • I added the snippet to use your markup logic. Did you read the github readme of JsonMachine? PS: The downvote note was to whomever did it, not to you particularly. – Filip Halaxa Apr 19 '20 at 16:04
0

Fetch limited records while rendering html first load. Once page load fully then call a ajax function which will fetch next page rows. Definitely it is the tested method.

    <script>
    var items = [{item1}, {item2}];
    $(document).ready(function() {
    $.each(items, function(index, item) {
    $('.card').append('<li>'+ '<span><p>'+item['title']+'</p></span>' + 
    '</li>');
     });
     });`enter code here`
    </script>

OR You may call ajax function for first page records after html rendered fully.

How do I load a JSON object from a file with ajax?

Krishan Kumar
  • 394
  • 4
  • 13
  • Some example would be helpful for your first method. – Mark Apr 21 '20 at 15:50
  • I have edited the answer in first method. let me know if you got it. I will write full code if you not get my idea. – Krishan Kumar Apr 21 '20 at 17:51
  • When I use Ajax to load my PHP it doesn't iterate anymore. When using PHP directly and ob_flush it works and outputs the data one by one. When using Ajax it waits until it loads data fully and then outputs the data – Mark Apr 22 '20 at 05:41
  • It does not mean that you are going to copy exact loading / syncing style as you shown in picture. The main purpose of the SPA is to minimize the server load. So just put a loader image first ( showing progress to user ) after html render and then call the ajax in pagination manner ( i.e. page 1 ). If it is taking much time while loading your ajax results then it might be issue with your database strategy. – Krishan Kumar Apr 22 '20 at 06:04
  • It’s not that it has to be a exactly like the picture. I can do the styling and loading animation. With my PHP working now I can totally make that happen. I just thought with Ajax it would be better and had more options in loading the results. – Mark Apr 22 '20 at 06:07