[web] PHP: Including files messes up relative paths

Started by
10 comments, last by konForce 17 years, 11 months ago
I have a file structure that looks like this:

/
  login.php
  db.php
  /agentadmin
    getagentid.php
getagentid.php has the following line: include('../db.php'); login.php has the following line: include('agentadmin/getagentid.php'); The problem here is that login.php includes getagentid.php, which tries to include ../db.php, which doesn't exist relative to login.php (but does exist relative to getagentid.php). Absolute paths are unfortunately out of the question. How do I make include()'s work from the current file's path?
Advertisement
I simply set a $root_path variable at the top of my entry files (that is, any file that can be called directly in a browser). E.g:

if (!isset($root_path))  $root_path = './';


./ would be for your login.php. For your getagentid.php it would be ../ instead (if you can call that file directly. If it's only ever included the you don't set it at all - it will be inherited from the entry file). Then, simply include everything relative to that:

include($root_path . 'db.php');


Ofcourse, if all your entry files are in the top directory then you can simply set all the includes manually. E.g. include('db.php'); without the ../ in getagentid.php.

<hr />
Sander Marechal<small>[Lone Wolves][Hearts for GNOME][E-mail][Forum FAQ]</small>

Yeah, this annoys me too...I don't know if this is considered to be "by design" or not, but I would prefer that it's relative to the file containing the reference.
Rule #1: Create an include folder for all utility files.
Rule #2: Adjust the php include_path variable to include that first.

Result: No need to use relative paths anymore.

Suggestion: Have a master "common.inc.php" file that loads the framework, initializes the db, etc.

Using such a mix of relative paths is really bad design. It's okay (although I don't like it) to include a file that's in a subfolder, but going back up the chain is not good.

So:

.htaccess:
php_value include_path /var/www/html/include:/usr/share/pear: ... etc

Structure:
/login.php
/include/common.inc.php
/include/db.inc.php
/include/agentadmin/getagentid.php

login.php:
<?php  require_once('common.inc.php');  require_once('agentadmin/getagentid.php');  // profit?>


common.inc.php:
<?php  require_once('db.inc.php');  // whatever needs to be set up 80%+ of the time?>


I'm not sure what "agentadmin/getagentid.php" is. If it's a content page, then I wouldn't necessarily put it into the include file. That is, if your login.php acts as some sort of dispatcher (which I don't quite follow) then I'd not consider the "getagentid.php" as a utility include. If the "getagentbyid.php" is simply a set of functions, then I'd put it in an include folder.

Lastly, I highly recommend using PHP classes as namespaces around similar sets of function and enabling autoloading of classes. For instance:

<?php  require_once('common.inc.php');  Agent::getId(); // automatically loads class/Agent.class.php upon first access  // this is assuming that Agent::getId() is a static function, etc.?>


Agent.class.php
<?php  class Agent  {    public static function getId()    {       return "Whatever";    }  }?>


The small performance hit is well worth your sanity as you no longer have to specify any include files, except for your global one. (And that can be eliminated if you use the auto include directive...)
You can actually get around this entirely in PHP. What I do is have a function which modifies the include path - put this in an include which *DOES* have a relative path.

// Set our include path to add the document root.function FixIncludePath(){ 	$include_path = ini_get("include_path");	$docroot = @ $_SERVER['DOCUMENT_ROOT'];	if ($docroot) {		ini_set("include_path", $include_path . ":" . $docroot);	} else {		// Doc root not set, use .. instead (This assumes that the CWD is here when run from CLI)		ini_set("include_path", $include_path . ":..");	}}


Then after that's done, all further includes can be relative to the web root.

Hence, the app contains no absolute paths, but includes still work as expected.

Mark
The best way to solve the problem is to use __FILE__ constant.
The BeanDog's example can be written like this:

In getagentid.php write:
require_once dirname(dirname(__FILE__)).'/db.php';

In login.php:
require_once dirname(__FILE__).'/agentadmin/getagentid.php';

The $_SERVER['DOCUMENT_ROOT'] aproach also works fine in web enviroment, but fails when running php file as console script.
And it also has the drawback that you should know all paths relative to DocumentRoot, which is a problem for redistributable code since user probably would like to place it under his own folder.
Quote:require_once dirname(dirname(__FILE__)).'/db.php';

That is horrible. PHP has "include_path" for a reason.
No, using __FILE__ can be a very neat solution.

Specifically, you could use a mixture of my method and pash_ka's one, where you put dirname(__FILE__) into the include_path anyway.

You'd only want to do that once.

Mark
Quote:That is horrible. PHP has "include_path" for a reason.

Yes, and changing include_path may be usable for some tasks, like including PEAR libraries.
(By the way, there is one more way to do it. I usualy have line like this:
Quote:set_include_path(dirname(__FILE__).'/pear'.PATH_SEPARATOR.get_include_path());

)
But there is also method used for Smarty (SMARTY_DIR): define a constant for your application root directory and use it as prefix for relative paths.
Quote:Specifically, you could use a mixture of my method and pash_ka's one, where you put dirname(__FILE__) into the include_path anyway.

I can live with an include_path that gets dynamically set, but there's a better way: include('settings.inc.php');. Inside there, set the include path to a hard coded path, set a few variables, define whatever constants you need, etc.

It's really quite pointless to take the performance hit* of dirname(__FILE__) on every page, when you can just take 15 seconds and set it forever. With the settings file, you can easily upload files from development to production without worrying about changing important things. (Just remember never to upload the settings file!)

* I realize it's quite small in the grand scheme of things, but it's pointless nonetheless. ;) It's really more of something like: if you cannot come up with a non-hacky way of including a file, what's the rest of the code look like?

There is absolutely no excuse, however, of sticking it in every include() directive. That just shows ignorance of the PHP language. If one is indifferent after knowning the proper way, then I'm sure the rest of his code is a pile of spaghetti that no one cares to touch.

Quote:But there is also method used for Smarty (SMARTY_DIR): define a constant for your application root directory and use it as prefix for relative paths.

That is almost as bad as the dirname(__FILE__) hack. When PHP offers standard functionality to do something, don't hack around it for no reason (other than ignorance). It's no harder to tell someone to adjust ini_set(), then it is to adjust the SMARTY_DIR constant.

This topic is closed to new replies.

Advertisement