1

I have the following question: how can I run a php script only once? Before people start to reply that this is indeed a similar or duplicate question, please continue reading...

The situation is as follows, I'm currently writing my own MVC Framework and I've come up with a module based system so I can easily add new functionality to my framework. In order to do so, I created a /ROOT/modules directory in which one could add the new modules.

So as you can imagine, the script needs to read the directory, read all the php files, parse them and then is able to execute the new functionality, however it has to do this for all the webbrowsers requests. This would make this task about O(nAmountOfRequests * nAmountOfModules) which is rather big on websites with a large amount of user requests every second.

Then I figured, what if I would introduce a session variable like: $_SESSION['modulesLoaded'] and then simply check if its set or not. This would reduce the load to O(nUniqueAmountOfRequests * nAmountOfModules) but this is still a large Big O if the only thing I want to do is read the directory once.

What I have now is the following:

/** Load the modules */
require_once(ROOT . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'module_bootloader.php');

Which exists of the following code:

<?php
//TODO: Make sure that the foreach only executes once for all the requests instead of every request.
if (!array_key_exists('modulesLoaded', $_SESSION)) {
    foreach (glob('*.php') as $module) {
        require_once($module);
    }
    $_SESSION['modulesLoaded'] = '1';
}

So now the question, is there a solution, like a superglobal variable, that I can access and exists for all requests, so instead of the previous Big Os, I can make a Big O thats only exists of nAmountOfModules? Or is there another way of easily reading the module files only once?

Something like:

if(isFirstRequest){
    foreach (glob('*.php') as $module) {
        require_once($module);
    }
}
Jaap Oudejans
  • 708
  • 2
  • 9
  • 25
  • Could you be more specific on why you parse files? – Ron Oct 24 '12 at 12:06
  • @Ron: Why does it matter? This question can be summed up as: "I have a very intensive script I only need to run once per installation. How?" – Madara's Ghost Oct 24 '12 at 12:08
  • Because depending on what you do, there could be a good fitting solution. – Ron Oct 24 '12 at 12:35
  • What kind of functionality are you implementing in your modules? And what kind of work does the scripts included by your bootloader do? - If your modules are object oriented (as en classes) you should use an autoloader instead: http://php.net/manual/en/language.oop5.autoload.php – EJTH Oct 24 '12 at 12:36
  • @EJTH I see, that's probably a better solution to what I want since the modules are indeed classes. If you can make your comment an answer I can probably select it as an answer :) – Jaap Oudejans Oct 24 '12 at 12:44
  • Possible duplicate of [Prevent Code or Function from Executing More Than Once](http://stackoverflow.com/questions/8116602/prevent-code-or-function-from-executing-more-than-once) – hakre Oct 29 '12 at 17:53

3 Answers3

1

At the most basic form, if you want to run it once, and only once (per installation, not per user), have your intensive script change something on the server state (add a file, change a file, change a record in a database), then check against that every time a request to run it is issued.

If you find a match, it would mean the script was already run, and you can continue with the process without having to run it again.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • That is true but I was kinda hoping instead of having to read something else, like a cache file or database entry, php would have a superglobal like system that would allow me to share data throughout the entire "Server" / "Application" – Jaap Oudejans Oct 24 '12 at 12:02
  • @Jaap: It doesn't. PHP by itself is stateless. You can try to use APC cache, but this method is easier to understand, easier to implement, and easier to reset. – Madara's Ghost Oct 24 '12 at 12:03
  • I guess I will do something like that then. Thanks. – Jaap Oudejans Oct 24 '12 at 12:07
0

when called, lock the file, at the end of the script, delete the file. only called once. and as so not needed any longer, vanished in nirvana.

This naturally works the other way round, too:

<?php

$checkfile = __DIR__ . '/.checkfile';

clearstatcache(false, $checkfile);

if (is_file($checkfile)) {
    return; // script did run already
}
touch($checkfile);

// run the rest of your script.
hakre
  • 193,403
  • 52
  • 435
  • 836
  • But then when someone else calls the file, you'll have trouble (404, file not found errors, etc). – Madara's Ghost Oct 24 '12 at 11:58
  • It would also give me the problem that if I reset the server, I would have to recreate that file to read all the modules. – Jaap Oudejans Oct 24 '12 at 12:04
  • Instead of deleting the inode, you can actually create one. It's the same principle. I added an example. When you need to be able to run again, remove the checkfile. – hakre Oct 24 '12 at 12:11
0

Just cache the array() to a file and, when you upload new modules, just delete the file. It will have to recreate itself and then you're all set again.

// If $cache file does not exist or unserialize fails, rebuild it and save it
if(!is_file($cache) or (($cached = unserialize(file_get_contents($cache))) === false)){
    // rebuild your array here into $cached
    $cached = call_user_func(function(){
        // rebuild your array here and return it
    });
    // store the $cached data into the $cache file
    file_put_contents($cache, $cached, LOCK_EX);
}
// Now you have $cached file that holds your $cached data
// Keep using the $cached variable now as it should hold your data

This should do it.

PS: I'm currently rewriting my own framework and do the same thing to store such data. You could also use a SQLite DB to store all such data your framework needs but make sure to test performance and see if it fits your needs. With proper indexes, SQLite is fast.

CodeAngry
  • 12,760
  • 3
  • 50
  • 57