2

Suppose I have a PHP file that does important things.

doAllTheThings.php:

<?php
    include '../importantThing1.php';
    include '../importantThing2.php';
    include '../importantThing3.php';
    //lots more complicated code below

Let's also suppose I type the address into the web browser, it takes half a minute but all the things get done, known good, fantastic, let's not mess with it. (There's lots of valid reasons to not mess with it. Maybe I didn't write it and don't understand it. Maybe I'm not given access to it. Maybe this is an emergency patch, and don't have time to update and test every single include path.)

Now suppose I want the user to be able to do something that can cause the code to run.

customerInterface.php:

<?php
    //lots more complicated code above
    if(doTheThings){
        include '../../things/important/doAllTheThings.php';
    }

It runs, but now the relative paths don't work. The importantThing links are broken. No important things get done.

How do I execute "doAllTheThings.php" such that it will behave the same as if I typed its address directly into the browser address bar? (Without changing the directory structure, the file location, or "doAllTheThings.php")

  • 1
    The best bet is to use absolute paths instead relative paths, you can avoid a lot of issues like this one. – Triby Dec 30 '19 at 03:52
  • I know how to do this through javascript using AJAX, but no way should the best solution trust the client computer. – Jonathon Philip Chambers Dec 30 '19 at 05:12
  • IMHO Disagree about absolute paths... hard coding is a real headache to maintain. For your question see https://stackoverflow.com/questions/9997391/php-get-name-of-current-directory – Tim Morton Dec 30 '19 at 05:26
  • @TimMorton yeah I was googling this issue before posting and found some quite fine alternatives between paths being completely absolute or completely relative. Still, I like being able to test a change from the address bar without having to hunt down every single instance to check that it behaves the same way from each of them. All solutions around this problem seemed difficult to maintain and test. – Jonathon Philip Chambers Dec 30 '19 at 06:32
  • Test from the address bar? Are you trying to run a CLI through your browser by typing in the absolute path? That’s a really cumbersome way to do it. I test directly from my editor. (Unit testing with phpunit, that is). But I can also run CLI programs directly from the editor too. – Tim Morton Dec 30 '19 at 12:48
  • @TimMorton I suspect I may be misunderstanding you, but I'm not sure that what I've written qualifies as a Command Line Interface. It's just a script that says "check the database for products, check the Etsy store, look for discrepancies, resolve". Just the act of visiting the page syncs everything and echos out a status report. But instead of me visiting the page, I want the program to visit it. If something goes wrong, I want to visit it myself, read the status report, and figure out what went wrong. – Jonathon Philip Chambers Dec 30 '19 at 12:56
  • @TimMorton however, this is getting into specific use case, which I'm trying to avoid. Rather, there's a specific tool I want. I hope it exists. If it doesn't, I already have several workarounds in place which I can do myself. Asking StackOverflow for a solution to my general situation is probably an abuse of it. The community has nothing to gain from me describing my situation in detail. It's not about escaping my situation, it's about solving this specific problem (if it has a solution at all) – Jonathon Philip Chambers Dec 30 '19 at 13:07
  • FWIW, it sounds like you’re dealing with procedural spaghetti code. OOP would simply things greatly— auto load classes and then a script could just ask the classes to do or report their respective importantThings. Sorry I couldn’t help more – Tim Morton Dec 30 '19 at 15:12
  • Client or hardcoding? No way, just create a config file where you define all needed variables and constants and include it from every script, then you can **include MY_PATH . '/things/important/another_script.php';** with no risk and no hardcoding. Is the way most frameworks and CMS works. – Triby Dec 30 '19 at 15:42

1 Answers1

6

Presuming there is no client-side code being run in doAllTheThings.php and you know the path to doAllTheThings.php.

The main issue is how include resolves paths within nested include files.

If the file isn't found in the include_path, include will finally check in the calling script's own directory and the current working directory before failing. [sic]

In this case the calling script is customInterface.php.

Being that include resolves relative paths from the current working directory of the executed script file, the simplest approach to circumvent the issue and make the calling script behave as if you executed doAllTheThings.php directly, you can use chdir in order to change the current working directory.

chdir https://3v4l.org/D0Tn6

<?php
    //...

    if (true) {
        //check to make sure doAllTheThings.php actually exists and retrieve the absolute path
        if (!$doAllThings = realpath('../../things/important/doAllTheThings.php')) {
           throw new \RuntimeException('Unable to find do all things');
        }

        //change working directory to the same as doAllTheThings.php
        chdir(dirname($doAllThings));

        //include doAllTheThings from the new current working directory
        include $doAllThings;

        //change the working directory back to this file's directory
        chdir(__DIR__);
    }

    //...
?>

include __DIR__ or dirname(__FILE__)

However, if possible, I strongly recommend using absolute paths by including the root path, by appending __DIR__ or dirname(__FILE__) in PHP < 5.3 to any relative include paths. This will absolve the need to use chdir() as a workaround, and allow PHP to resolve the correct paths to include, while also allowing the application as a whole to function on any system the scripts are executed from.

<?php
    include __DIR__ . '/../importantThing1.php';
    include __DIR__ . '/../importantThing2.php';
    include __DIR__ . '/../importantThing3.php';

set_include_path

Another more complex approach is to specify the include file directories, by using set_include_path(). However, if you are not aware of the directory the nested include scripts are in, this would require you to parse the include file to check for them. As such I do not recommend this approach, albeit viable.

<?php
if ($doAllThings = realpath('../../things/important/doAllTheThings.php') {
    //retrieve the directory names of the scripts to be included
    $basePath = dirname($doAllThings);
    $subPath = dirname($basePath);

    //add the directories to the include path
    $include_path = set_include_path(get_include_path() . PATH_SEPARATOR . $basePath . PATH_SEPARATOR . $subPath);

    include $doAllThings;

    //restore the include path
    set_include_path($include_path);
}
Will B.
  • 17,883
  • 4
  • 67
  • 69
  • Good answer. I'll wait a few more days before choosing the best one, but this is definitely the correct answer if no one else answers. (I'm assuming. I haven't tested it yet.) – Jonathon Philip Chambers Jan 16 '20 at 05:41