I know PHP > 5.3 added the Magic constants : __DIR__ to get the father dir path,
But how can I get the grandfather dir path in a smarter way?
1.__DIR__.'/../'
2.dirname(__DIR__)
Use the second parameter levels for the dirname call
echo dirname(__DIR__,2); // 1 Level up = Father. 2 Levels up = Grandfather
However, it is not any better than what you already have. It's just smarter like you asked for :)
You can simply move up a folder by using ...
For example:
$path = dirname(__DIR__);
$grandfatherPath = $path.'/../../';
Related
All my PHP include files are in a single directory:
https://www.mywebsite.com/includes
Inserting these files in top level pages is easy enough:
<?php include 'includes/logo.php'; ?>
<?php include 'includes/main_nav.php'; ?>
<?php include 'includes/news.php'; ?>
etc.
For sub-directory pages I've been doing this:
<?php include '../includes/logo.php'; ?>
<?php include '../includes/main_nav.php'; ?>
<?php include '../includes/news.php'; ?>
and this:
<?php include '../../includes/logo.php'; ?>
<?php include '../../includes/main_nav.php'; ?>
<?php include '../../includes/news.php'; ?>
So far so good, but I suspected it wasn't going to continuing being this easy.
Now I need to include this file:
top_five_news_stories.php
in this:
news.php
At this point, my relative path strategy fails because the include in the include can have only one path structure.
I've read several posts recommending absolute paths using:
dirname(__FILE__)
realpath(dirname(__FILE__)
$_SERVER["DOCUMENT_ROOT"]
However, they all come with some kind of caveat relating to PHP configuration, server configuration or operating system. In other words, there's often a comment by somebody saying it didn't work in their case, or doesn't work in IIS, or doesn't work in UNIX, or something else.
A solution I haven't seen is one I thought would be most simple: Just set a variable:
$base = "https://www.mywebsite.com/includes";
then:
<?php include $base . "logo.php" ?>
Considering that I already use the HTML base element, which works in a similar way, this method strikes me as simple and efficient.
But since it wasn't mentioned in any of the posts I read, I'm wondering if I'm overlooking a potential problem.
Frankly, if I had to go to production to today, I would use this:
<?php include $_SERVER['DOCUMENT_ROOT'] . '/logo.php" ?>
which works for me and is commonly mentioned.
But I'm wondering if using a variable is a reliable, efficient method?
Don't
I would advise against using anything that needs something outside of PHP, like the $_SERVER variable.
$_SERVER['DOCUMENT_ROOT'] is usually set by the webserver, which makes it unusable for scripts running from the command line. So don't use this.
Also don't use url's. The path-part in a url is not the same as the path of the file on disk. In fact, that path can not even exist on disk (think Apache rewrites).
Including url's also needs you to turn allow_url_include on, which introduces (severe) security risks if used improperly.
Do
If your minimal supported PHP version is 5.3 (I hope it is!), you can use the magic constant __DIR__. 2 examples:
define(ROOT_DIR, __DIR__);
define(ROOT_DIR, realpath(__DIR__ . '/..'));
If you need to support lower versions, use dirname(__FILE__). 2 examples:
define(ROOT_DIR, dirname(__FILE__));
define(ROOT_DIR, realpath(dirname(__FILE__) . '/..'));
Make sure ROOT_DIR points to the root of you project, not some subdirectory inside it.
You can then safely use ROOT_DIR to include other files:
include ROOT_DIR . '/some/other/file.php';
Note that I'm defining a constant (ROOT_DIR), not a variable. Variables can change, but the root directory of you project doesn't, so a constant fits better.
realpath()
realpath() will resolve any relative parts and symlinks to the canonicalized absolute pathname.
So given the following files and symlink:
/path/to/some/file.php
/path/to/another/file.php
/path/to/symlink => /path/to/another
and /path/to/file.php contains:
define(ROOT_DIR, realpath(__DIR__ . '/../symlink'));
then ROOT_DIR would become /path/to/another, because:
__DIR__ equals to /path/to/some (so we get /path/to/some/../symlink)
.. is 1 directory up (so we get /path/to/symlink)
symlink points to /path/to/another
You don't really need to use realpath(), but it does tidy up the path if you're relying on relative parts or symlinks. It's also easier to debug.
Autoloading
If you need to include files that contain classes, you'd best use autoloading. That way you won't need include statements at all.
Use a framework
One last pease of advise: This problem has been solved many many times over. I suggest you go look into a framework like Symfony, Zend Framework, Laravel, etc. If you don't want a "full stack" solution, look into micro-frameworks like Silex, Slim, Lumen, etc.
Jasper makes some good points, and a further reason for not using DOCUMENT_ROOT is that content accessible via a URL does not have to be within this directory (consider Apache's alias, scriptalias and mod_user_dir, for example).
As Barmar points out PHP explicitly provides functionality for declaring a base directory for includes. While this is typically set in the config it can be overridden/added to at runtime in your code. You never want to see a variable in your include/require directives. It breaks automatic tools and hides vulnerabilities. Nor should you ever include using the file wrappers.
There is an argument in OO programming for never using include/require explicitly but just autoloading class definitions. However the problem of locating the code remains.
The short answer is that there is no best solution for the problem you describe. Each method has its drawbacks - the best solution is completely dependent on the context. For an enterprise application, setting the include_path simplifies development processes and, if not directly accessible from the webserver enhances security. It also allows for selectively overlaying functionality by manipulating the order of multiple entries in the path.
On the other hand this is not a good model for software you intend to distribute to less technical users likely to be confused about multiple paths whom may not have access to directories outside the document root or to change the default config.
Using relative paths is a robust and portable solution. I don't understand your problem with including top_five_news_stories.php.
A solution which gives you the benefits of both the enterprise and low-end hosting is shown below. This however has the drawback that it needs code added to each entry point in the site (and requires the app to be installed in a named sub directory):
define('APP_NAME', 'mikesdemo');
$base=substr(__DIR__, 0, strrpos(__DIR__, APP_NAME))
. APP_NAME . '/include';
set_include_path(get_include_path() . PATH_SEPARATOR . $base);
The more sophisticated user can then simply....
mv /var/www/html/mikesdemo/include/* /usr/local/php/include/
File Arquitecture Rework:
define a path for every tipe of file like that:
+root
|
+------app(D)(all php script MVC)
|
+------conf(D)(all php Config file)
|
+------assets(D)(all file js and css static image)
|
+------fileup(D)(all file Uploades)
|
+------index.php(F)(Procesor of petition http)
in your index you need include all File of config Like style C++:
Example:
require_once ('conf/config.security.php'); #Configuration around Security in PHP
require_once ('conf/config.conpro.php'); #Configuration around Constantent
require_once ('conf/config.classlib.php'); #Lib class around Generic DB Conection ETC
require_once ('conf/config.classlibmvc.php'); #Lib class around MVC specific class
And example of config file:
Declare Root Directory + Path that shared the file
$APP_DIR_CLASS = $_SERVER['DOCUMENT_ROOT'] . '/app/classgeneric/';
Define library file:
if (!defined('DBMANAGER_CLASS')) define('DBMANAGER_CLASS' ,'class.managerdb.php' );
Include or require the class
require_once $APP_DIR_CLASS . DBMANAGER_CLASS;
when you are in a class and need use DB class you can call it easy:
class Class_Exmaple{
public function __construct(){
$this -> DBMANAGER = new Class_BDManager();
}
public function __destruct(){
$this -> DBMANAGER = new Class_BDManager();
}
public function ConsultDB(){
$query ='Selec * From Tablename';
$result = $this -> DBMANAGER -> ExecuteQ($query);
print_r(result);
}
}
is a easy way to implement but you need learn more about injection and Class Loaders.
There is no "correct way" to require/include an internal script in your project. A lot (most) MVC frameworks use similar best practices to global file access in a router object.
Let's take an example, here is our directory infrastructure:
App/
Controllers/
Controller.php
Models/
Model.php
Views/
View.php
404/
index.php
index.php
.htaccess
Inside our .htaccess we would have a rewrite rule to the index.php in the root directory of your server.
Inside this file, is where we actually run the whole of your Software. For example, this is a great router I use AltoRouter.
First things first, we need to add a way to stop direct browser access and an error path to any controllers, models and views:
define( 'ERROR_PATH', strtolower(explode( '/', $_SERVER['SERVER_PROTOCOL'][0]) . '://' . $_SERVER['SERVER_NAME'] . '/404' );
define( 'IN_APP', 0 );
Which is then used in your controllers, models and views like:
if( !defined( 'IN_APP' ) ) {
header('Location: ' . ERROR_PATH);
exit();
}
Your file path will be the root file path if you declare __FILE__ in this instance (index.php) so we can use it any way (best practice is global defines).
define( '_DIR_', dirname( __FILE__ ) );
Then start requiring your files:
$includeFiles = [
glob( _DIR_ . '/Controllers/*.php' ),
glob( _DIR_ . '/Models/*.php' )
];
foreach( $includeFiles as $dir ):
foreach( $dir as $file ):
require_once( $file );
endforeach;
endforeach;
In CodeIgniter, there are available defined filepath constants inside the /public/index.php.
There are following constants available:
BASEPATH returns C:/wamp/www/testci2_2.dev/system/
SELF returns index.php
SYSDIR returns system
FCPATH returns C:\wamp\www\testci2_2.dev\public\
Inside my controller /application/controllers/configurations.php within a function backup() method, I would like to make backup files write directly to the /sql/ folder which is located here: C:\wamp\www\testci2_2.dev\sql\
I tried to use var_dump(dirname(BASEPATH) . '/sql/'); which produces what I want, but I am not quite sure if it's the best way to do that, maybe there is smarter steps to get that path ?
How can I target that folder by using some of this constants ?
If it is not possible, is there any other CLEVER method so when one day I decide to move controllers into the subfolders or so, they will not loose the track from specified folders ?
You need to use pathinfo to extract the directory name from FCPATH prior to \public\
$path = pathinfo(FCPATH, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR;
echo $path; // C:\wamp\www\testci2_2.dev\sql\
I want to loop all the sub dirs in the main dirs, where I keeps all my classes, for instance,
core/
model/
page/
class_1.php
class_2.php
menu/
class_3.php
and so on...
So this is my autoload function that I place it in the init.php,
function autoload_multiple_directory($class_name){
// List all the class directories in the array.
$array_directories = array(
'core/controller/',
'core/model/',
'core/helper/'
);
// When you use namespace in a class, you get something like this when you auto load that class \foo\tidy.
// So use explode to split the string and then get the last item in the exloded array.
$parts = explode('\\', $class_name);
//print_r($parts);
// Set the class file name.
$file_name = strtolower(end($parts)).'.php';
// $file_name = 'class_'.strtolower($class_name).'.php';
// Loop the array.
foreach($array_directories as $path_directory){
$recursive_directory = new RecursiveDirectoryIterator($path_directory);
foreach (new RecursiveIteratorIterator($recursive_directory) as $filename => $file) {
if(file_exists(WEBSITE_DOCROOT.$file->getPath().'/'.$file_name)){
include WEBSITE_DOCROOT.$file->getPath().'/'.$file_name;
}
}
/* no problem with this, but I cannot loop the sub dirs...
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name)){
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
*
*/
}
}
spl_autoload_register('autoload_multiple_directory');
But then I get this error message,
Fatal error: Cannot redeclare class Common in C:\wamp\www\xxx\core\helper\Common.php on line 6
There is only one Common class in my project. Why does it say more than once or redeclare?
But if you look at the if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name)) that I comment out - it has no problem to load the classes. The problem of this initial loop is that it does not loop the sub dirs in a main dir, for instance, core/model/
Any ideas why and what should I do to loop the sub dirs of a main dir?
EDIT:
The problem comes from RecursiveDirectoryIterator - it loops the directories and lists all files. But what I want is only the sub directories.
Is there any chance that there are more than one copy of Common.php file exists in those folders?
Because your code does not break after including a class file the autoloader will continue seeking other files with the same name in the folder tree, and would lead to Fatal error: Cannot redeclare class XXX error. Adding break could fix the problem.
// Loop the array.
$isClassFound = false;
foreach($array_directories as $path_directory){
$recursive_directory = new RecursiveDirectoryIterator($path_directory);
foreach (new RecursiveIteratorIterator($recursive_directory) as $filename => $file) {
if(file_exists(WEBSITE_DOCROOT.$file->getPath().'/'.$file_name)){
include WEBSITE_DOCROOT.$file->getPath().'/'.$file_name;
$isClassFound = true;
}
if ($isClassFound) break;
}
if ($isClassFound) break;
}
However, the nature of your autoloader seems like it should not allow any duplicated class file names. Maybe you can write a name duplication checker to guarantee uniqueness.
EDIT:
I removed the class_exists() part from my answer because using it doesn't make much sense. Anyway, since you saw that version of my answer, and you asked me where to put class_exists() via the comment, I'll revive the code sample. you can add the following code at the beginning of the autoloader.
if (class_exists($class_name,false)) // give false to avoid automatic loading
return;
Open the start menu. In the text box write cmd, wait for the cmd program to pop up and then hit Enter.
Once the terminal window opens navigate to your root folder and then do a recursive search (through all files and folders) for the class' name:
cd C:\wamp\www\xxx
findstr /SNIP /C:"class common" *.php
There should be more than one declaration of the class.
Let's say I'm writing a global logData method that wants to write to a log file that has the same name as the php that's running it, but with a .log extension.
I'm including this logging in a parent php with the intention of having it always write to log files that are whatever the *parent file name is (not the tools.php lib in which it's sitting).
So, I have
/some/arbitrary/directory/parent.php
which calls
include ("/path/to/my/php/libs/tools.php");
but when I run my logging method that's in tools.php it logs to a file called
/path/to/my/php/libs/tools.php.log
rather than
/some/arbitrary/directory/parent.php.log (which is what I'd like).
I'm using __FILE__ which is behaving this way (probably as its intended to). Is there a command for getting the parent's file name so that I can get this to work as I intend? Or will I have to pass FILE as a param into my method from the parent php to get it to write to the correct output file?
TIA
You could probably use $_SERVER["SCRIPT_FILENAME"]
debug_backtrace() will give you what you need.
http://php.net/manual/en/function.debug-backtrace.php
You need to pass __FILE__ to the log class.
Something like:
// file:/some/arbitrary/directory/parent.php
$logger = new Logger(__FILE__);
// file:/path/to/my/php/libs/tools.ph
public function __construct($file) {
// But it is not good idea to save log file same with php files.
$this->log_path = $file.'.log';
}
this is my third question so far on stackoverflow :D
i am defining files and their location on my first_run.php files,
the files that i define here is those files containing classes, helper functions
and any other files required
at early development, this first_run.php contains only a few lines of codes
but the line is increasing gradually as i add some new classes or new files to be included
and since i group the file's location inside a particular folder, i figure that maybe i can scan the folder, put the name of the files retrieved into an array and then loop the require_once, so that i dont have to edit first_run.php every time i add a new file inside the folder.
my fisrt approach is using scandir()
before:
defined('INC_PATH') ? null : define('INC_PATH', SITE_ROOT.DS.'includes');
defined('MEMBERS') ? null : define('MEMBERS', INC_PATH.DS.'models'.DS.'members');
require_once(MEMBERS.DS.'member.php');
require_once(MEMBERS.DS.'phone.php');
require_once(MEMBERS.DS.'profile.php');
require_once(MEMBERS.DS.'talent.php');
require_once(MEMBERS.DS.'profile_picture.php');
require_once(MEMBERS.DS.'audio.php');
require_once(MEMBERS.DS.'video.php');
require_once(MEMBERS.DS.'gallery.php');
require_once(MEMBERS.DS.'statistik.php');
require_once(MEMBERS.DS.'inbox.php');
require_once(MEMBERS.DS.'comment.php');
require_once(MEMBERS.DS.'picked_stat.php');
require_once(MEMBERS.DS.'log.php');
after is something like:
$member_files = scandir(MEMBERS);
foreach($member_files as $member_file):
require_once(MEMBERS.DS.$member_file);
endforeach;
i havent try the 'after' code though.
is this possible?? or is there any other approach?? or should i just leave it that way (keep adding the lines without scanning the files)
thanks in advance
Consider using Autoloading instead.
With autoloading, you do not have to bother with including files at all. Whenever you instantiate a new class that is not known to PHP at that point, PHP will trigger the registered autoload function. The function includes the required files then. This way, you only load what you need when you need it, which should increase performance.
Simple example with PHP5.3
spl_autoload_register(function($className) {
include "/path/to/lib/and/$className.php";
});
$foo = new Foo;
When you call new Foo, the registered autoload function will try to include the class from /path/to/lib/and/Foo.php. It is advisable to use a classname convention, like f.i. PEAR, to make finding files easier and to cut down on the amount of include_paths.
For additional security and speed, you can provide a more sophisticated Autoloader that uses an array to map from classname to filename, thus making sure only files that actually are part of your application can get included.
Further reading:
http://weierophinney.net/matthew/archives/245-Autoloading-Benchmarks.html
It's possible, but not recommended, like what if somebody could create a php file on that directory, you'll end up including it, besides, you can't predict the inclusion order.
Try this instead:
$includes=array(
'member',
'phone',
'profile',
'talent',
);
foreach($includes as $fname) {
require_once(MEMBERS.DS.$fname. '.php');
}
If you were using classes, consider using autoloading, as #Gordon suggested. And if you werent using classes, consider using them :)
At a first glance your code could work, although you have to ignore "." and ".." in the foreach loop. Plus I'd check, if the file ends with ".php":
$member_files = scandir(MEMBERS.DS);
foreach($member_files as $member_file) {
// Ignore non php files and thus ".." & "."
if (!preg_match('/\.php$/', $member_file) {
continue;
}
require_once(MEMBERS.DS.$member_file);
}
create 2 functions
function GetFiles($directory,$exempt = array('.','..','.ds_store','.svn'),&$files = array()) {
$handle = opendir($directory);
while(false !== ($resource = readdir($handle))){
if(!in_array(strtolower($resource),$exempt)){
if(is_dir($directory.$resource.'/'))
array_merge($files, self::GetFiles($directory.$resource.'/',$exempt,$files));
else
$files[] = $directory.$resource;
}
}
closedir($handle);
return $files;
}
function Autoload($includes = array()){
$files = array();
foreach($includes as $directory)
$files[] = self::GetFiles($directory);
foreach($files as $k=>$v){
foreach($v as $k1=>$v1)
require_once($v1);
}
}
to use it:
$includes = array(
'C:WWW/project/helpers/',
'C:WWW/project/lang/',
'C:WWW/project/lib/',
'C:WWW/project/mod/',
);
Autoload($includes);