Keep checking if statement in Laravel - php

What is the best way to keep checking a condition until it is true? I know there is a while statement in PHP but was not sure if there is a better packaged way in Laravel.
Basically I am transcoding a video through AWS. I want the frontend to keep saying "Uploading" until I know the video is transcoded and saved in AWS and all info is in the database. The videos will be short, but still transcoding is not instant so if I do:
if ($job['Status'] == 'complete') {
$submission = new Submission();
$submission->email = $request->input('email');
$submission->original = config('filesystems.disks.s3.url') . $original_key;
$submission->save();
return response()->json([
'submission' => $submission,
'message' => 'Upload Successful. Good luck!!!!',
'job' => $job
]);
}
This if statement will be false right away. But if I checked again every few seconds it will pass after a little bit. Is there a pulse type function to continue to run that if statement every X amount of time until it passes? Using a while seems to be hitting max_execution_time the limit.

Does this have to be on the PHP side?
If JS works, I suppose one way would be to have a bool state in your JS to show "Uploading" with setTimeout() to check if the video is uploaded every x seconds. When it passes you can set the state to false, which would make "Uploading" disappear.
Also, don't forget to stop the setTimeout function when it passes.

In my opinion, there could be 02 solutions:
1) If I were you, I'd prefer to handle it in Frontend side with AJAX. This is a pseudo code to explain my idea
function uploadMyVideo() { // to be called once we confirm the upload
var jqxhr = $.ajax( "/upload/url" )
.done(function() {
// call another ajax to set the status = SUCCESS
})
.fail(function() {
// call another ajax to set the status = FAIL
})
.always(function() {
// in case you need it
});
}
2) You can use Laravel scheduler to call periodically (eg: every minute) a job which checks for completed status, and run the logic inside it. (it's a cron job with laravel way)

Related

Laravel command that now needs to be "available" via http

I have a web app created in Laravel that takes credit card payments.
Every day a scheduled command that I created runs to take "today's" payments (basically it submits one http request for each pending payment to the payment gateway).
Now I need to allow to trigger this payment submission process via a button in a dashboard.
The command takes a random long time to process (depending on the number of payments to process), so call it from the controller I think is not an option.
I'm thinking of just refactor it: move all the command code to a "middleman" class so I could call this class on both the command and the controller.
PaymentsSubmissionHelper::submit()
PaymentsSubmissionCommand: PaymentsSubmissionHelper::submit()
PaymentsSubmissionController: PaymentsSubmissionHelper::submit()
However, the command shows a progress bar and the estimated time to process and I will need to show a progress bar in the html interface as well. In the web interface I will need to make ajax requests to the server to get the current progress but in the command this progress is tracked in a completely different way using:
$bar = $this->output->createProgressBar($totalPayments);
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %message%');
and for each processed payment:
$bar->advance();
How can I create keep track of the progress on both the command and the controller?
Any help will be appreciated.
Thanks in advance!
As already pointed out in another answer, Laravel's queued event listeners are the way to handle long-running processes on the front end. You shouldn't need to refactor your console command at all.
As to showing progress on the front end, one simple solution would be to set up some AJAX polling. Ever few seconds have AJAX fire off a request to a controller method which simply looks at today's payments, calculates how many are processed (presumably you have some kind of status field which will show you whether or not the running job has handled it yet), and return a number representing the percentage that are done. The AJAX success handler would then update your progress tracker on the page.
// Check status every 2s
var timer = setInterval(function() {
pollStatus();
}, 2000);
pollStatus = function() {
$.ajax({
url: 'somewhere/jobStatus',
success: function(resp) {
$('#progress').html(resp . '%');
if (resp === 100) {
// We've reached 100%, no need to keep polling now
clearInterval(timer);
}
}
});
}
It might be wise to somehow make sure polls don't overrun, and maybe you'd want to tweak the frequency of polling.
I would suggest using queued event listeners in this use case. You would dispatch an event in your controller and have a listener which could trigger the command. By queueing the listener you avoid a long response time. No need to refactor the command itself!
Regarding a progress bar, you could have a static progess bar that updates on page load where you would read out the status from your DB and display it similarly to how Amazon displays how far along your order is at any moment.
For a real time updated progress bar, I suggest implementing web sockets. Socket.io seems great.
As you are using progress bar and advancing it, you will do same in ajax but the progress logic will be different off-course.
The common part in both the cases is handling each card payment. So I will say create separate class or service which takes card payment instance e.g. PaymentProcess, processes it and returns if successful or failed.
Then in command you can do (psuedocode) :
public function handle()
{
$pendingPayments = Payment::where('status', 'pending');
$bar = $this->output->createProgressBar($pendingPayments->count());
$pendingPayments->chunk(10, function($payments) use($bar){
$payments->each(function($payment) use ($bar){
$process = (new PaymentProcess($payment))->process();
$bar->advance();
});
});
$bar->finish();
}
Now if you trigger this from frontend, the ajax response should give you an id of current process stored somewhere. Then you will keep sending another ajx requests in an interval of lets say 1 second and get the current progress until it reaches to 100%. (If you are using XMLHttpRequest2 then the logic will differ)
For that you can create another table to store progresses and then keep updating it.
Now similarly you can use the PaymentProcess inside controller. :
public function processPendingPayments(Request $request)
{
// Authorize request
$this->authorize('processPendingPayments', Payment::class);
$pendingPayments = Payment::where('status', 'pending');
// Create a progress entry
$progress = PaymentProgress::create([
'reference' => str_random('6')
'total' => $pendingPayments->count(),
'completed' => 0
]);
$pendingPayments->chunk(10, function($payments) use($bar){
$payments->each(function($payment) use ($bar){
$process = (new PaymentProcess($payment))->process();
// Update a progress entry
$progress->update([
'completed' => $progress->completed + 1;
]);
});
});
return response()->json([
'progress_reference' => $progress->reference
], 200);
}
Now another endpoint to get the progress
public function getProgress(Request $request)
{
// Authorize request
$this->authorize('getProgress', Payment::class);
$request->validate([
'reference' => 'required|exists:payment_process,reference'
]);
$progress = PaymentProcess::where('reference', $request->reference)->first();
$percentage = $progress->completed / $progress->total * 100;
return response()->json(compact('percentage'), 200);
}

Long polling with Ajax with sleep()/time_sleep_until() in while() loop

I'm willing to set up a long polling Ajax call to check for orders in my e-commerce web app. There is a specificity in this application in the way that customers are able to place order in the future. As such, in the admin panel, we have past orders and futures orders (that can be 2 months or 20 minutes in the future).
Basically, I want the admin user in the back-end to be warned as soon as a future order comes to an end (the future date reaches the current time). To proceed, I make the user admin doing an Ajax call (as soon as they are connected to the admin) to the server to check for futures orders to arrive. This Ajax call is a long polling request as the call waits for the server to deliver result. If server has nothing to offer, the request keeps pending until there is an order to show.
Ajax request
(function poll() {
setTimeout(function() {
$.ajax({
url: '{{ path('commande_check') }}',
method: 'post',
success: function(r) {
if(r.ids) alert('New order!'); // I've simplified this part of the code to make it clean, admin are actually warned through Node.JS server
},
error: function() {},
complete: poll
});
}, 5000);
})();
{{ path('commande_check') }} (edited from Edit2)
public function checkAction(Request $request)
{
if($request->isXmlHttpRequest())
{
$response = new Response();
$em = $this->getDoctrine()->getManager();
$ids = array();
while(!$ids)
{
$ids = $em->getRepository('PaymentBundle:Commande')->findNewestOrders(new \DateTime());
if($ids)
break;
else
time_sleep_until(time() + self::SECONDS_TO_SLEEP);
}
if($ids)
{
return new JsonResponse(array(
'ids' => $ids
));
}
$response->setStatusCode(404);
return $response;
}
$response = new Response();
$response->setStatusCode(405);
return $response;
}
findNewestOrder() method
public function findNewestOrders(\DateTime $datetime)
{
$query = $this->createQueryBuilder('c')
->select('c.id')
->leftJoin('Kt\PaymentBundle\Entity\Paiement', 'p', \Doctrine\ORM\Query\Expr\Join::WITH, 'p.id = c.paiement')
->andWhere('p.etat = 0')
->where("DATE_FORMAT(c.date, '%Y-%m-%d %H:%i') = :date")
->setParameter('date', $datetime->format('Y-m-d H:i'))
->andWhere('c.kbis IS NULL')
->andWhere('c.notif = 0')
->getQuery();
return $query->getArrayResult();
}
My problem is the alert sometimes never get shown whereas the record in the DB gets updated. The weirdest things is it sometimes happens even when I've leaved the page making the Ajax call like if it keeps running in the background. I think the problem comes from the time_sleep_until() function. I tried with sleep(self::SECOND_TO_SLEEP) but the problem was the same.
Any help would by gladly appreciated. Thanks!
Edit 1
I sense there is something to do with connection_status() function as the while loop appears to continue even if the user has switched page causing the field notif to be updated in the background.
Edit 2
As per my answer, I've managed to overcome this situation but the problem still remains. The admin does get the notification properly. However, I do know the Ajax call still keeps going on as the request has been made.
My problem is now: could this result in a server resources overload?
I'm willing to start a bounty on this one as I'm eager to know the best solution to achieve what I want.
I think I got it all wrong.
The intent of long-polling Ajax is not that there is only one connection that stays opened such as websockets (as I thought it did). One would have to make several requests but much less than regular polling.
Regular polling
The intent for Ajax regular polling is one makes a request to the server every 2 or 3 seconds to have a semblance of real-time notification. These would result in many Ajax calls during one minute.
Long polling
As the server is waiting for new data to be passed on to the browser, one would need to make only a minimal number of requests per minute. As I'm checking in the database for new order every minute, using long polling can make me lower the number of requests per minute to 1.
In my case
In consequence, the specificity of the application makes the use of Ajax long-polling unnecessary. As soon as a MySQL query has been made for a specific minute, there is no need for the query to run again in the same minute. That means I can do regular polling with an interval of 60000 ms. There's also no need to use sleep() nor time_sleep_until().
Here's how I ended up doing it:
JS polling function
(function poll() {
$.ajax({
url: '{{ path('commande_check') }}',
method: 'post',
success: function(r) {
if(r.ids)
alert('New orders');
},
error: function() {},
complete: function() {
setTimeout(poll, 60000);
}
});
})();
{{ path('commande_check') }}
public function checkAction(Request $request)
{
if($request->isXmlHttpRequest())
{
$em = $this->getDoctrine()->getManager();
$ids = $em->getRepository('PaymentBundle:Commande')->findNewestOrders(new \DateTime());
if($ids)
{
return new JsonResponse(array(
'ids' => $ids
));
}
$response = new Response();
$response->setStatusCode(404);
return $response;
}
$response = new Response();
$response->setStatusCode(405);
return $response;
}
As such, I end up with one request per minute that will check for new orders.
Thanks #SteveChilds, #CayceK and #KevinB for their kind advice.
In general for this problem it is kinda rough to say. We don't have a lot of information as to exactly what your other functions do. Like findNewestOrders...
We can assume that it pulls all new orders that have yet to be fulfilled by the admin and therefore will be displayed. However, if it is looking only for orders that are exactly equal they will never be filled.
Theoretically this will run forever if no new order ever is filed. You have no time limit on this so it is possible that the server feels like you have a case in which while will never be false and executes an exceeded execution time.
As per your comment
time_sleep_until
Returns TRUE on success or FALSE on failure.
The only way it would ever fail is if the function itself failed or some server side issue caused a failure return. As you never officially visit the page and no act of leaving your ajax'd page submits a failure response it should never really fail.
I think it might be more wise to look into doing a CRON job for this and have a database of incomplete orders that you query instead. The CRON can run every minute and populate the database. The run on the server would not be that great as it would most likely take no more than 30 seconds any way.
Long-polling may be a great idea for many functions, but I'm not wholly confident it is in the case. I would seriously recommend setInterval as the load on the server and client would not be that great in a 30 seconds call every minute or two. That is your call in the end.
I personally would check frequently rather than have one request which runs for a long time - its not really ideal to have long running processes like this as they tie up server connections and really, its just bad practice. Plus the browser may well time the connection out, which is why you may not be seeing the responses you expect.
Have you tried changing the ajax call so it calls in say, every 60 seconds (or however often you want), checks for new orders since the last time it was polled (simply keep a track of this in the page / HTML5 local storage so it persists across pages and pass it in the ajax request as a parameter) and then simply returns an indication of yes there have been new orders, or no there hasn't?
You can then display a message if there have been new orders.
I have finally managed to overcome this bug but without digging deeply in the problem.
I have separated the code that updates the notif field from the code that fetch new orders. In that way, the while loop still goes on but cannot update the field.
The field is therefore updated on success of the first ajax call by making a new ajax request to update the field. Therefore, the admin always receives the notification.
I just have to enquiry on a memory/thread level to see what consumption of resources this loop uses.
As no solution has been found despites my workaround for the initial bug, I won't accept my answer as the problem remains still.
Many thanks for all the help on that question.

php and ajax: show progress for long script

I have php script which can take quite a lot of time (up to 3-5 minutes), so I would like to notify user how is it going.
I read this question and decided to use session for keeping information about work progress.
So, I have the following instructions in php:
public function longScript()
{
$generatingProgressSession = new Zend_Session_Namespace('generating_progress');
$generatingProgressSession->unsetAll();
....
$generatingProgressSession->total = $productsNumber;
...
$processedProducts = 0;
foreach($models as $model){
//Do some processing
$processedProducts++;
$generatingProgressSession->processed = $processedProducts;
}
}
And I have simple script for taking data from session (number of total and processed items) which return them in json format.
So, here is js code for calling long script:
$.ajax({
url: 'pathToLongScript',
data: {fileId: fileId, format: 'json'},
dataType: 'json',
success: function(data){
if(data.success){
if(typeof successCallback == "function")
successCallback(data);
}
}
});
//Start checking progress functionality
var checkingGenerationProgress = setInterval(function(){
$.ajax({
url: 'pathToCheckingStatusFunction',
data: {format: 'json'},
success: function(data){
console.log("Processed "+data.processed+" items of "+data.total);
if(data.processed == data.total){
clearInterval(checkingGenerationProgress);
}
}
});
}, 10000)
So, long scripted is called via ajax. Then after 10 seconds checking script is called one time, after 20 second - second time etc.
The problem is that none of requests to checking script is completed until main long script is complete. So, what does it mean? That long script consumes too many resources and server can not process any other request? Or I have some wrong ajax parameters?
See image:
-----------UPD
Here is a php function for checking status:
public function checkGenerationProgressAction()
{
$generatingProgressSession = new Zend_Session_Namespace('generating_progress');
$this->view->total = $generatingProgressSession->total;
$this->view->processed = $generatingProgressSession->processed;
}
I'm using ZF1 ActionContext helper here, so result of this function is json object {'total':'somevalue','processed':'another value'}
I'd
exec ('nohup php ...');
the file and send it to background. You can set points the long running script is inserting a single value in DB to show it's progress. Now you can go and check every ten or whatever seconds if a new value has been added and inform the user. Even might be possible to inform the user when he is on another page within your project, depending on your environment.
Yes, it's possible that the long scripts hogs the entire server and any other requests made in that time are waiting to get their turn. Also i would recommend you to not run the check script every 10 seconds no matter if the previous check has finished or not but instead let the check script trigger itself after it has been completed.
Taking for example your image with the requests pending, instead of having 3 checking request running at the same time you can chain them so that at any one time only one checking request is run.
You can do this by replacing your setInterval() function with a setTimeout() function and re-initialize the setTimeout() after the AJAX check request is completed
Most likely, the following calls are not completing due to session locking. When one thread has a session file open, no other PHP threads can open that same file, as it is read/write locked until the previous thread lets go of it.
Either that, or your Server OR Browser is limiting concurrent requests, and therefore waiting for this one to complete.
My solution would be to either fork or break the long-running script off somehow. Perhaps a call to exec to another script with the requisite parameters, or any way you think would work. Break the long-running script into a separate thread and return from the current one, notifying the user that the execution has begun.
The second part would be to log the progress of the script somewhere. A database, Memcache, or a file would work. Simply set a value in a pre-determined location that the follow-up calls can check on.
Not that "pre-determined" should not be the same for everyone. It should be a location that only the user's session and the worker know.
Can you paste the PHP of "pathToCheckingStatusFunction" here?
Also, I notice that the "pathToCheckingStatusFunction" ajax function doesn't have a dataType: "json". This could be causing a problem. Are you using the $_POST['format'] anywhere?
I also recommend chaining the checks into after the first check has completed. If you need help with that, I can post a solution.
Edit, add possible solution:
I'm not sure that using Zend_namespace is the right approach. I would recommend using session_start() and session_name(). Call the variables out of $_SESSION.
Example File 1:
session_name('test');
session_start();
$_SESSION['percent'] = 0;
...stuff...
$_SESSION['percent'] = 90;
Example File 2(get percent):
session_name('test');
session_start();
echo $_SESSION['percent'];

Have one function to wait for an AJAX call, without synchronous AJAX(SJAX)

I am creating a dynamic todo-list on a webpage. On the page you have a form for registering todo's and a table showing all the registrated todo's. The idea is that you register something you want done in a form, hit the submit button, and then the todo-list-table is automatically updated with the latest registered todo. My script manages all of this except for automatically updating the latest registered todo.
Here's my code:
$(document).ready( function() {
$('#todo_registration input[type="submit"]').click(function(evt){
evt.preventDefault();
var todo = $('#todo_registration input[name="daily_todo"]').val();
$('#todo_registration input[name="daily_todo"]').val(null);
$.when( registerTodo(todo) )
.then (
updateTodoDisplay()
);
});
});
function updateTodoDisplay() {
$.post("./daily_todo_display.php", null, replaceTbodyHTML);
}
function replaceTbodyHTML(data) {
$('#todo_display_table tbody').html(data);
}
function registerTodo(todo) {
var parameters = {
daily_todo: todo,
registration_button: 'clicked'
};
$.post("./daily_todo_registration.php", parameters); //, printRegistrationStatus);
}
I have checked that the script successfully registrates the todo in the database. The php-script that gets the updated todo-list also works. My problem, I think, is that the function updateTodoDisplay() doesn't wait for the AJAX call in registerTodo() to successfully complete before it runs. But I thought my use of #.when() was supposed to make updateTodoDisplay() wait.
I know making the AJAX call synchronous would probably fix my problem, but in my opinion that is a bad solution. I only want this one and only function to wait for the AJAX call to complete. Thus I want the rest of the webpage to function while these calls are made.
Any one know a fix for my problem? Thnx.
What you need is possible, but it looks like you have an error in your code.
Change the
.then (
updateTodoDisplay()
);
to
.then (function(){ updateTodoDisplay(); } );
or even
.then (updateTodoDisplay);
The problem is that when you are registering the callback, in your current code you are passing the result of executing updateTodoDisplay() instead of passing it as a function. That is why you get it executed right away.
You should $.post your data, AND when server-side updating is done, respond from server-side too - sending text/json/xml back to the UI. You save one (the second) request with that, you keep ajax asynchronous, you keep your code shorter/more-maintainable, and you get rid of this issue. =)
$.post("url/todo.php", params, function (data) {
// callback
// do UI update here
// "json" but you can say "xml" too
}, "json");
All you need to do is to figure out your server-side response.
jQuery.post()
Have a nice time implementing! =)

Ajax Timed Refresh / PHP / MySQL

It seems there are some very good resources on my question - at least the basics. But there are still a few dark areas for myself.
First - What I am trying to do.
I am having users "wait" at an intermediary page while I check to see if they have submitted the correct information from another resource (ie. SMS or email) to confirm their subscription to my service. The page should asynchronously check ever couple of seconds to see if they have completed the required steps and their status has been set to '1' or 'active' in the database.
Completing the ajax request seems pretty straightforward - the timing however, I am a little thick about.
Also, what is it that I am trying to do with the information I am retrieving - great, so the user has been set to 'active' and I can retrieve that value - but then what do I use to redirect them to the final or 'thank you' page? Is it a javascript variable that should hold the value then redirect?
Sorry if this seems a little discombobulated, but this is my first try at Ajax & timed refreshes/responses.
I'd do a predefined number of intervals on the ajax call - after 3 attempts, fail with a message:
var attempts = 0;
var validation_attempt = setInterval(function(){
if(attempts > 3)
{
clearInterval(validation_attempt);
$('#my_div').html('No activity, try again later');
return false;
}
$.ajax({
url: '/my_file.php',
/* other params */
success:function(data){
if(data == 'ok')
{
clearInterval(validation_attempt);
$('#my_div').html('Your account has been approved');
}
else
{
attempts++;
}
}
});
},3000); // 3 seconds
This should essentially let the user wait for 9 seconds at the most before seeing a "No activity, try again later" message.
Your "waiting" page can poll with ajax for completion. It might poll every 15-20 seconds initially, back up to once a minute after a little while and then stop polling all together after awhile. In all cases, make sure you don't keep polling forever as this will be bad for your backend if the user never completes their end of the subscription process.
If the ajax polling gets a successful answer, I would think you would then redirect the user to one of two places:
1) A page that just indicates "Success - you are now subscribed" and tells them what to do next.
or
2) The opening web page for actually using the service. This may be a login page or perhaps you can already log them in automatically so you take them to the open page for the service.
You can do this redirect either in client-side javascript (e.g. it already knows where to redirect the user in all scenarios) or you can include the redirection URL in that actual ajax response so the flow of pages can be controlled server-side. You can do it either way.
I was coding this in response to your question. I think I'll post it in case it is useful:
var active = false, i, t;
// check 'active' for truthyness, if so kill timers
function checkActive() {
if(active) {
clearInterval(i);
clearTimeout(t);
window.location.href = "success.html";
}
return false;
}
// recursively call URL to check 'active' status
function checkStatus() {
$.post("checkStatus.php", { some: "variable" }, function(resp) {
if(resp !== "1") {
t = setTimeout(checkStatus, 2000);
} else {
active = true;
}
});
}
$(document).ready(function() {
checkStatus();
i = setInterval(checkActive, 2000);
});
You can try using a server push (comet) rather than ajax for this solution.

Categories