4

I have this PHP code, but I have only written one recursive function before. I want to begin in a particular subdirectory and check for the presence of a file. If it doesn't exist in the directory I want to check the parent folder for the file and then recursively go on until I either don't find the file and return "" or find the file and return its contents. Here is my function:

/**
*    Recursive function to get sidebar from path and look up tree until we either
*    find it or return blank.
*/
class MyClass{
    public function findSidebarFromPath($path){
        if(file_exists($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html")){
            return file_get_contents($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html");
        } else {
            if(count($path) > 0){
                # Pop element off end of array.
                $discard = array_pop($path);
                $this->findSidebarFromPath($path);
            } else {
                return "";
            }
        }
    }
}

So when I call it, I do this:

$myclass = new MyClass();
$myclass->findSidebarFromPath(array("segment1", "segment2", "segment3"));

Let's say the file I am trying to find is in a directory called "segment2". The function never finds it. The array_pop function doesn't pop off the element off the end of the array and then call the findSidebarFromPath function.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bryan C.
  • 129
  • 1
  • 9
  • 1
    this isn't recursive code, since you don't call `findSidebarFromPath()` in there anywhere. you just call `getSidebar()`, which you haven't shown at all. – Marc B Apr 13 '15 at 19:21
  • 1
    The inner if branch does not return. – Don't Panic Apr 13 '15 at 19:24
  • I renamed the function for posting. Sorry about that. Now it should make more sense...$this->findSidebarFromPath($path); is what I meant to call. – Bryan C. Apr 13 '15 at 19:26
  • 1
    Whoops. I just answered my own question. Now it works. Now I feel ashamed for having asked this question :( – Bryan C. Apr 13 '15 at 19:31
  • 1
    @BryanC everyone makes mistakes, don't feel embarrassed to ask a question. Plus, you learned how to solve the same problem in a different way. This is always a plus. – maček Apr 13 '15 at 20:17

1 Answers1

2

If you write this as a standalone function, it will probably be more useful to you in other areas. After we understand how it works, we'll show you how you can add a public function to your class that can utilize it.

/**
 * @param $root string - the shallowest path to search in
 * @param $path string - the deepest path to search; this gets appended to $root
 * @param $filename string - the file to search for
 * @return mixed - string file contents or false if no file is found
 */
function findFile($root, $path, $filename) {
  // create auxiliary function that takes args in format we want
  $findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
    // resolve the complete filename
    $file = "{$cwd}/{$filename}";
    // if the file exists, return the contents
    if (file_exists($file)) return file_get_contents($file);
    // if the cwd has already reached the root, do not go up a directory; return false instead
    if ($cwd === $root) return false;
    // otherwise check the above directory
    return $findFileAux(dirname($cwd), $filename);
  };
  // starting checking for the file at the deepest segment
  return $findFileAux("{$root}/{$path}", $filename);
}

Check the example output on ideone.


So here's how to use it

findFile($_SERVER["DOCUMENT_ROOT"], "foo/bar/qux", "sidebar.html");

Here's how you would integrate it with your class. Notice that this public function has the same API as in your original code

class MyClass {
  /**
   * @param $path array - path segments to search in
   * @return mixed - string file contents or false if sidebar is not found
   */
  public function findSidebarFromPath($path) {
    // Here we call upon the function we wrote above
    // making sure to `join` the path segments into a string
    return findFile($_SERVER["DOCUMENT_ROOT"], join("/", $path), "sidebar.html";
  }
}

Additional explanation

If $_SERVER["DOCUMENT_ROOT"] is /var/www...

  1. Check /var/www/foo/bar/qux/sidebar.html, return if exists
  2. Check /var/www/foo/bar/sidebar.html, return if exists
  3. Check /var/www/foo/sidebar.html, return if exists
  4. Check /var/www/sidebar.html, return if exists
  5. Because we got to the root (/var/www) no further searches will happen
  6. return false if sidebar.html did not exist in any of the above

Here's the same function with the explanatory comments removed

/**
 * @param $root string - the shallowest path to search in
 * @param $path string - the deepest path to search; this gets appended to $root
 * @param $filename string - the file to search for
 * @return mixed - string file contents or false if no file is found
 */
function findFile($root, $path, $filename) {
  $findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
    $file = "{$cwd}/{$filename}";
    if (file_exists($file)) return file_get_contents($file);
    if ($cwd === $root) return false;
    return $findFileAux(dirname($cwd), $filename);
  };
  return $findFileAux("{$root}/{$path}", $filename);
}

You might also want to consider using DIRECTORY_SEPARATOR instead of the hard-coded "/" so that this code could be used reliably on a variety of platforms.

maček
  • 76,434
  • 37
  • 167
  • 198
  • How is the directory path: $path reduced in this function? – Bryan C. Apr 13 '15 at 19:48
  • 1
    @BryanC. You can see the `dirname($cwd)` call in the last line of the auxiliary function – maček Apr 13 '15 at 19:53
  • 1
    Feel free to use the '/' as a directory separator on 'windows' - it works fine. The only place it causes bother is the CLI unless used in quotes. This is due to 'switches' using '/'. too much information i think. – Ryan Vincent Apr 13 '15 at 19:56
  • Wow. I did not even know about PHP namespaces. I guess I have a lot to learn. – Bryan C. Apr 13 '15 at 20:03
  • 1
    @BryanC. I suspect you stumbled across namespaces because you see `use` in my code. The `use` here is not related to PHP namespaces. I'm using `use` to inherit variables from the parent scope for use with an [anonymous function](http://php.net/manual/en/functions.anonymous.php). – maček Apr 13 '15 at 20:07