I have a PHP script that can take a few minutes to be done. It's some search engine which executes a bunch of regex commands and retrieve the results to the user.
I start by displaying a "loading page" which does an AJAX call to the big processing method in my controller (let's call it 'P'). This method then returns a partial view and I just replace my "loading page" content with that partial view. It works fine.
Now what I would like to do is give the user some information about the process (and later on, some control over it), like how many results the script has already found. To achieve that, I do another AJAX call every 5 seconds which is supposed to retrieve the current number of results and display it in a simple html element. This call uses a method 'R' in the same controller as method 'P'.
Now the problem I have is that I'm not able to retrieve the correct current number of results. I tried 2 things :
Session variable ('file' driver) : in 'P' I first set a session variable 'v' to 0 and then update 'v' every time a new result is found. 'R' simply returns response()->json(session('v'))
Controller variable : same principle as above but I use a variable declared at the top of my controller.
The AJAX call to 'P' works in both cases, but everytime and in both cases it returns 0. If I send back 'v' at the end of the 'P' script, it has the correct value.
So to me it looks like 'R' can't access the actual current value of 'v', it only access some 'cached' version of it.
Does anyone have an idea about how I'm going to be able to achieve what I'd like to do? Is there another "cleaner" approach and/or what is wrong with mine?
Thank you, have a nice day!
__
Some pseudo-code to hopefully make it a bit more precise.
SearchController.php
function P() {
$i = 0;
session(['count' => $i]); // set session variable
$results = sqlQuery(); // get rows from DB
foreach ($results as $result) {
if (regexFunction($result))
$i++
session(['count' => $i]); // update session variable
}
return response()->json('a bunch of stuff');
}
function R() {
return response()->json(session('count')); // always returns 0
}
I would recommend a different approach here.
Read a bit more about flushing content here http://php.net/manual/en/ref.outcontrol.php and then use it.
Long story short in order to display the numbers of row processed with flushing you could just make a loop result and flush from time to time or at an exact number or rows, the need for the 5 seconds AJAX is gone. Small untested example :
$cnt = 0;
foreach($result as $key => $val) {
//do your processing here
if ($cnt % 100 == 0) {
//here echo smth for flushing, you can echo some javascript, tough not nice
echo "<script>showProcess({$cnt});</script>";
ob_flush();
}
}
// now render the proccessed full result
And in the showProcess javascript function make what you want... some jquery replace in a text or some graphical stuff...
Hopefully u are not using fast_cgi, beacause in order to activate output buffering you need to disable some important features.
I believe you have hit a wall with PHP limitations. PHP doesn't multithread, well. To achieve the level of interaction you are probably required to edit the session files directly, the path of which can be found in your session.save_path global through php_info(), and you can edit this path with session_save_path(String). Though this isn't recommended usage, do so at your own risk.
Alternatively use a JSON TXT file stored somewhere on your computer/server, identifying them in a similar manner to the session files.
You should store the current progress of the query to a file and also if the transaction has been interrupted by the user. a check should be performed on the status of the interrupt bit/boolean before continuing to iterate over the result set.
The issue arises when you consider concurrency, what if the boolean is edited just slightly before, or at the same time, as the count array? Perhaps you just keep updating the file with interrupts until the other script gets the message. This however is not an elegant solution.
Nor does this solution allow for concurrent queries being run by the same user. to counter this an additional check should be performed on the session file to determine if something is already running. An error should be flagged to notify the user.
Given the option, I would personally, rewrite the code in either JSP or ASP.NET
All in all this is a lot of work for an unreliable feature.
Related
I am working on a TYPO3 project where I have to dynamically disable caching based on a condition. It is a very specific usecase, that will not happen a lot.
I planned to use a USER_INT function, where I would perform the check and disable the cache if necessary. The USER_INT function works flawlessly, it is being called on every page load.
The thing is, I can not disable the cache, or at least I do not know how.
The code, I have right now:
page = PAGE
page {
typeNum = 0
adminPanelStyles = 0
11 = USER_INT
11.userFunc = [COMPANY_NAMESPACE]\PageHandler->checkCache
And in the function I perform the check:
public function checkCache($content,$conf){
global $TSFE;
$id = $TSFE->id;
if($this->checkIfDisableCache($id)){
//$TSFE->set_no_cache(); // <---- first I tried this one
$TSFE->no_cache=true; // <-----after a while I got despoerate and tried to disable it directly
}
}
I also tried to play with the config, it did not work.
The funny thing is, if I set it directly in typoscript:
config.no_cache = 1
it works, but since the check is rather complex, I want to use PHP to determine, if the cache should be disabled.
I know I am doing something wrong, I just don't know what. Any help would be appretiated :)
I don't think either of the previous answers really explain the situation. You have sort of a catch-22 here, in that your USER_INT is executed after the page cache entry has been generated. The way it works internally is everything that can be cached gets rendered first, and every USER_INT then outputs a marker in the HTML source which gets replaced afterwards. This way the cache can contain the version with markers and those can be rendered without having to render the whole page.
So what you need to do in this case if you want the page cache to be disabled only in some conditions, is to use a custom TypoScript condition that is capable of setting config.no_cache = 1 only under special circumstances. That way you prevent generating a cache entry if the condition is met, but preserve full caching and cached output for every other request.
https://docs.typo3.org/typo3cms/TyposcriptSyntaxReference/TypoScriptParserApi/CustomConditions/Index.html
Note that it is still recommended that you instead create the parts of your page that must not be cached, as USER_INT objects. Having a use case where you in some cases need to disable the entire page cache indicates a possible misunderstanding of how the caching framework and/or USER_INT works. Hopefully the above explains those parts a bit.
if you look at the pibase (AbstractPlugin) code you will see that probably setting $conf['useCacheHash']and $conf['no_cache'] should be done.
https://api.typo3.org/typo3cms/current/html/_abstract_plugin_8php_source.html#l00190
If you create this object as USER_INT, it will be rendered non-cached, outside the main page-rendering.
https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/UserAndUserInt/Index.html
I'm trying to display some content in real time to my visitors using a loop. Problem is that the content is added to the output instead of being replaced with the new one. Here's an example of code which counts from 10 to 0. The output shows the whole set of results instead of counting backwards. I mean, I don't want to see all numbers shown just each number as the counter goes.
ob_end_flush ();
//start buffering
ob_start ();
echo str_pad ( '', 1024 ); // minimum start for Safari
for ( $i = 10; $i > 0; $i --) {
echo str_pad ( "$i<br>\n", 8 );
ob_flush ();
flush ();
sleep ( 1 );
}
die ();
This can not be achieved with PHP alone, as to alter what is rendered in the browser requires some client side intervention.
How you go about this division of labour depends upon what is needed.
If your code is all of it, then you would be far better off writing a JS routine to count down the numbers in a timed fashion.
If PHP is required in some other fashion to generate the numbers then the decision needs to be taken as to whether you can split the generation of the update in to multiple PHP calls or if it has to be from within the single PHP call due to actions taking forth within that call.
If the former, you can separate it, which is the ideal situation - then the procedure is to setup AJAX calls to retrieve updated information periodically.
If the latter and it must be from a single page, then the connection must be kept open with new content generated.
In this case, to get new content to replace old, JS code must handle this. Either code running periodically upon the page can check for new content and update accordingly, or a little trick can be played injecting code that performs the update.
Sending inline <script> content that performs the update instead of new display elements works well, if a little untidy, as the browser receives the new <script> element it is run. This is the only way I am aware of that a push-update can be made to a web page without plugins.
There might be a trick or something (just thought of one or two) but the most appropriate way to do it is with an ajax autoupdate from the browser. Like this pattern description.
We didnt do this in PHP, in fact using js. ( with Stacker help ) you can reverse the count
But am sure you could adopt, sorry if this doesnt answer your question.
Demo here on our dev site < link >
You can also ( again may not be pertinent, use jquery to refresh a div element )
I am working on my personal site, where I want to store my customers recent search result limited to that particular session.
I am using PHP platform and Javascripts.
Here is an example of what I am exactly looking at :
It stores your previously searched domain name for that particular session so that user can make decision by comparing those results.
Thanks.
EDIT- Well Thanks for all of your answers and suggestions.
But If you have noticed
above example
It looks like some kind of script loading a new content on the same page without refreshing it and keeping previous search content <div> as it is.
How to achieve this using javascripts or some sort of div layer ????
UPDATE START
This example uses page reload. If you want to do it without page reload, you can but you'll have to use AJAX to load new search results. But then, it's not a PHP question. I suggest looking at jquery library, as it makes it easy. Tutorials: http://docs.jquery.com/Tutorials and e.g. this one ( http://docs.jquery.com/Tutorials:Getting_Started_with_jQuery#Rate_me:_Using_Ajax ).
When loading data via AJAX, the page rendering result (in my example search.php) should return only HTML for results part, not whole HTML page. This is generally a first part of my tutorial (without session).
But I really think that AJAX in here is not really needed. Session is more reliable and allows access to your page from older / mobile browsers where not always JS works correctly.
UPDATE END
Ok then. Let's try the simple tutorial then. Sorry if too simple, but I don't know your exact level.
PHP has mechanism called sessions. In reality they are just bytes stored on server. Server knows which session is for each client by reading session cookie from client browser.
Not every page uses sessions (not every page needs it, and session uses server space, even if only temporarily), session is not enabled by default. To turn on session you use command
<?php session_start(); ?>
In most cases this is either run by PHP framework you use, or put near the top of your site. Session is definitely needed if you want to authenticate user somehow. Or in your case :)
To access session you can use superglobal $_SESSION variable (superglobal means that you can access it anywhere). It's an array, so session element will be e.g. $_SESSION['search'] etc.
As example, let's assume that your page looks like that
<html>
...
<form action="search.php" method="post">
Search: <input type="text" name="searchQuery" />
<input type="submit" value="Search" />
</form>
...
</html>
this very form will send user search to file named search.php. It can be the same file where the form resides - in simplest case when you put both your code and HTML in one file. Beginners often use this schema, although it's not advisable as result is a mess and hard to further change.
In search.php then, you'll use similar code:
<?php
if (!empty($_POST['searchQuery'])) //we have a new search
{
$result = do_search($_POST['searchQuery']);
}
?>
Then, somewhere below you'll display your search result ($result variable). do_search() function is your search mechanism, I guess you have it somewhere. You may have it not 'wrapped' in a function, then I advise to create it like that, it's much more useful.
function do_search($searchQuery)
{
...
return $result;
}
mind it, the above code doesn't use sessions yet. Let's add saving previous search results in session. The code may then look like that:
<?php
session_start(); //Starting session
//let's create session variable used to store results
if (!isset($_SESSION['searches']))
$_SESSION['searches'] = array();
if (!empty($_POST['searchQuery'])) //we have a new search
{
if (isset($_SESSION['searches'][$_POST['searchQuery']]) //User already searched on this value, delete previous result from sesion
{
unset($_SESSION['searches'][$_POST['searchQuery']]);
}
$result = do_search($_POST['searchQuery']);
//Let's add new search on the begining of session array to make iterations easier.
$result = array($_POST['searchQuery'] => $result); //convert result to same format as session table
$_SESSION['searches'] = array_merge($result, $_SESSION['searches']);
}
?>
In display you'll now not iterate on $result variable as before, but instead you will do something like
foreach ($_SESSION['searches'] as $query => $result)
{
...//display of single result
}
I haven't tested following code and it's not a full program. Parts to display result and to do actual search are not described but I guess you have them already prepared. Also, this is only one possible approach of countless possibilities. But I hope this helps :)
Possible modification - now I always perform search, even if user already searched on this term. You may want to receive the result from cache without second search. Then the code will look like
if (isset($_SESSION['searches'][$_POST['searchQuery']]) //User already searched on this value
{
$result = $_SESSION['searches'][$_POST['searchQuery']];
unset($_SESSION['searches'][$_POST['searchQuery']]);
}
else
{
$result = do_search($_POST['searchQuery']);
}
For more in-depth information about sessions and some other constructs used in my example I suggest PHP manual
http://pl.php.net/manual/en/book.session.php
and various tutorials over the network. Or you can add a comment here :)
Put this code near the beginning of your script(s):
if (!isset($_SESSION['previous_searches']) || !is_array($_SESSION['previous_searches'])) {
$_SESSION['previous_searches'] = array();
}
[edit]
This code snippet checks if if there is already an array with prevous searches and if not it will be created.
[/edit]
Then when the user hits the search page put this code in the receiving script of the search:
$_SESSION['previous_searches'][] = $_GET['what_ever_your_search_value_might_be'];
[edit]
This code snippet adds the current search value to the and of the array with previous search values
[/edit]
Now you have all previous search values in $_SESSION['previous_searches']
If your website is a web application where you never reload the page nor change the page, you can keep it JavaScript in a global store (declare at top level something like var StoredSearch = []; and use it). If not, then use $_SESSION to store this and AJAX to save/load searches from JavaScript to PHP.
This may be a silly question, but how do I save variables that are not specific to a particular session. An simple example of why you might want to do this would be a visitor counter - a number that increases by one each time someone visits a web page (note - I'm not actually doing that, my application is different, but that is the functionality I need). The only ways I can think of doing this are either writing the variables to a file, or putting the variables into a database. Both seem a bit inelegant. Is there a better way to to this kind of thing?
If you need to save global state, you need to save global state. This is typically done in either a file or a database as you already noted.
It's not "inelegant" at all. If you need to save something (semi-)permanently, you put it in a database. That's what databases are for.
Have a look at the serialize() function in PHP http://uk3.php.net/serialize where you'll be able to write an array or such to a file and re-retrieve:
<?php
// Save contents
$var = array('pageCounter' => 1);
file_put_contents('counter.txt', serialize($var));
// Retrieve it
$var = unserialize(file_get_contents('counter.txt'));
?>
Otherwise save the value to a database.
Given that PHP is stateless and that each pageload is essentially re-running your page anew, if you're going to be saving variables that will increment over multiple pageloads (e.g., number of distinct users), you'll have to use some form of server-end storage - file-based, database, whatever - to save the variable.
You could try installing APC (Alternative PHP Cache) which has cool features for sharing data between all PHP scripts, you could try using shared memory too or like you said, use a file or database
I think I've found the answer - session_name('whatever') can be used to have a fixed name for a session, I can refer to that data as well as the session specific session.
If you want it to be permanent, database and files are really your only two choices.
If you only want to temporarily store these values in memory, if APC is installed, you can do this:
// Fetch counter value back from memory
$success = false;
$counter = apc_fetch('counter', &$success);
if ($success) {
// fetch succeeded
} else {
// fetch failed
$counter = 0;
}
// Increment the counter and store again
// Note that nothing stops another request/page from changing this value
// between the fetch and store calls.
$counter++;
apc_store('counter', $counter);
That was just an example.
For a counter, you're better off using apc_inc('counter') / apc_dec('counter').
Presumably other opcode caches have similar methods. If you're not running an opcode cache... really? You want PHP to recompile a page every time its requested?
Elegant, no database and no file ?
Store it in your server memory with shmop and hope your server does not reboot !
At the end of a page, if something occurs, it needs to be cleared, then the entire page needs to be re-parsed before serving to the client. I was going to echo out a javascript to refresh the page, but that will make them load the page and then reload it...I was wondering if there was a way to just tell the php engine to go back to the beginning and re-parse the entire page?
Thanks!
I will try to explain the problem more clearly but it is complicated and I am a terrible communicator. I on the page that lists products I am giving users the option to select fields to narrow the results. The system remembers this so they don't have to keep selected them. If they narrow a category like metal color and then go to a category that metal color is irrelevant like crystal figurines it will not show any results because none will match the metal color chosen. To generate the query to pull the products from the data-base is very complicated because different categories have different requirements to find the correct products. so once the query is generated I want to test it against mysql_num_rows() and if there is no results clear out the users choices and start over.
You're being a little vague, but if you're merely talking about reparsing the output, you could do that using output buffering.
I'm not entirely clear what the issue is, but couldn't you decide what is to be shown before creating the HTML, and then send the right thing the first time?
To generate the query to pull the products from the data-base is very complicated because different categories have different requirements to find the correct products. so once the query is generated I want to test it against mysql_num_rows() and if there is no results clear out the users choices and start over.
In that case, just put the query inside a function that returns the result, check the row count, and if it's zero clear the filters and call that function a second time.
Output buffering (ob_start and ob_clean), combined with separating the functionality at hand into a separate file and eval()'ing that should do the trick.
Oh, and recent PHP versions actually have a goto statement... although I'll always deny mentioning anything about it. :-)
I think you're going about it a little bit off.
What you should do to reparse the page is to redirect the user to the page again, using
header('Location: thepagefile.php');
however if you actually would like to reparse the file without creating a new page, you could also just include the file again:
include thepagefile.php
But you'd probably get the same result. If you want to actually parse the output of the page you'd do something like:
ob_start(); // this is at the very top of the code/page
// all the code goes here
$output = ob_get_clean();
eval($output); // WTF?
but that actually makes no sense, but I hope it helps.
I'd actually like to know what the real problem you're trying to solve really is.
I think your looking for something like this:
<?php
ob_start(); //we start output buffering, this means nothing is send to the browser
//We do some code stuff
$time = microtime();
echo "Hai \n"; //Note taht mixing logic and output in real life
echo $time; // is terribly practice
echo "\n bai"; //I do it here purely for the example
if(/*some condition */){
$anErrorHappened = true;
}
if($anEroorHappened === true){
//Load the output in a var if you need it
//Otherwise don't
$output = ob_get_clean();
//Do other code stuff
//I.E. send an error page
include('errorPage.html');
}
else{
ob_end_flush(); //Send everything the script has echo()'d, print()'ed and send to the browser in any other way (I.E. readfile(), header() etc.)
}
?>