PHP文件包含无法在LINUX机器上完全工作的脚本


PHP file include script not fully working on LINUX machines

我的网站中包含了很多函数和类。在stackoverflow的帮助下,我收到了一个自动包含文件夹及其子文件夹中所有文件的脚本:PHP:Automatic Include

在测试脚本时,它总是有效的,我从来没有遇到过任何问题。但最近,当从windows服务器切换到linux服务器时,会出现类扩展问题。

PHP致命错误:在第3行的路径/functions/classes/Acumulus/Acumulus ExportWorkshop.PHP中找不到类"AcumulusExportBase",referr:pagesite/?page_id=346

AcumulusExportWorkshop由Acumulus ExportBase扩展而来。这一切都可以在windows上完全工作,但拒绝在linux上工作。

我可以修复创建include_one‘AcumulusExportBase.php’的问题;但如果有更好的解决方案,这一切似乎都是不必要的,而且正在进行中。

我使用的代码如下:

load_folder(dirname(__FILE__));
function load_folder($dir, $ext = '.php') {
    if (substr($dir, -1) != '/') { $dir = "$dir/"; }
    if($dh = opendir($dir)) {
        $files = array();
        $inner_files = array();
        while($file = readdir($dh)) {
            if($file != "." and $file != ".." and $file[0] != '.') {
                if(is_dir($dir . $file)) {
                    $inner_files = load_folder($dir . $file);
                    if(is_array($inner_files)) $files = array_merge($files, $inner_files); 
                } else {
                    array_push($files, $dir . $file);
                }
            }
        }
        closedir($dh);
        foreach ($files as $file) {
            if (is_file($file) and file_exists($file)) {
                $lenght = strlen($ext);
                if (substr($file, -$lenght) == $ext && $file != 'loader.php') { require_once($file); }
            }
        } 
    }
}

有人能告诉我windows和linux在扩展类方面没有问题吗?此外,在不必手动包含基类的情况下,是否有解决问题的方法?

您是否已验证AcumulusExportBase是否包含在Linux下的Acumulus ExportWorkshop之前?PHP对导入顺序很敏感。

其他两个答案都是正确的(我都投了赞成票)。您的问题将是加载文件的顺序(请参阅Mark的响应),递归也是错误的(请参阅KIKO)。

然而,有一种更好的方法可以做你想做的事:使用自动加载器。http://php.net/manual/en/language.oop5.autoload.php

第一次是令人困惑的,但一旦你掌握了它,这是一种加载文件的好方法。

基本上,你会说"如果我需要类X,但它没有加载,那么加载文件Y.php"。

如果你非常懒惰,不想指定每个类,那么你可以说:"如果我需要类X,但它没有加载,那么在目录结构中运行,寻找一个名为X.php的文件并加载它,我的类就会在那里。"你可以混合上面的内容来做这件事。

通过这种方式,可以先加载AcumulusExportWorkshop,然后它查找Acumulus ExportBase并愉快地运行。

而且,更有益的是,你只装载你需要的东西。如果你从不需要这个类,它永远不会被加载。

我想回答你的问题,但遗憾的是,我没有安装Windows PHP服务器。但是,我可以查看并测试您的代码。我首先注意到的是格式错误的递归。要获取"inner_files",可以使用递归,但这需要函数返回一个值,即文件数组。事实并非如此。此外,尽管您使用的是"require_eonce",但在每次递归时都会调用它,这意味着您多次尝试包含"深层"文件。简而言之:是时候稍微简化一下代码了。

load_folder(dirname(__FILE__));
function load_folder($dir,$ext = '.php')
{
  if (substr($dir,-1) != '/') $dir = $dir.'/';
  if ($handle = opendir($dir))
  {
    while($file = readdir($handle))
    {
      if (($file != '.') && ($file != '..') && ($file[0] != '.'))
      {
        if (is_dir($dir.$file)) load_folder($dir.$file,$ext);
        else
        {
          if ((substr($file,-strlen($ext)) == $ext) &&
              ($file != 'loader.php') && 
              file_exists($dir.$file)) require_once($dir.$file);
        }
      }
    }
    closedir($handle);
  }
}

这在linux下工作,并执行相同的任务。我更正了内部load_folder()中缺少$ext的事实。

我的建议是永远不要盲目地复制你在互联网上找到的代码。请始终检查,然后再检查。确保你了解它是如何工作的。如果你不这样做,你的项目将充满错误,任何人都无法维护。

正如Robbie所说,其他两个答案都是正确的,自动加载器是理想的解决方案。

自动加载器(一开始)可能看起来稍微复杂一些,但它带来了真正重要的好处,非常值得使用。

以下是其中的一些:

  • 您不需要手动包含或要求文件。

  • 您不需要担心文件是否按正确的顺序加载——解释器会自动加载任何依赖项。(在您的案例中,Windows和Linux操作系统的差异暴露了现有代码中的这一弱点)

  • 可以避免加载不需要的文件。

  • 解释器不需要解析不必要的类和代码。

以下是关于自动加载器你应该知道的一些事情:

  • 您可以拥有任意数量的自动加载器——它们存储在堆栈中并按顺序执行。如果第一个没有加载该类,则使用下一个,依此类推,直到加载该类或没有更多的自动加载器可供尝试。

  • 自动加载器是可调用的——要么是类的方法,要么是函数。

  • 具体如何实现自动加载器取决于您自己——因此,如果您的项目有一个与类类型或层次结构相关的特定目录结构,您可以指示它在特定目录中查找,从而提高效率。

我们大多数人都喜欢把我们的类放在单独的文件中。这样可以更容易地找到我们感兴趣的类,并使文件更小,从而更容易理解。

当涉及到我们使用的文件名时,PHP没有强制执行任何类型的命名约定,但大多数开发人员更喜欢将Classes保存在文件名与类名相关的文件中。

自动加载器功能假定有一种方法可以在显示文件名时加载正确的文件。因此,一个好的做法是使用一种简单的方法从类名生成文件名——最简单的方法是使用类名作为文件名。

这是我喜欢的自动加载器——我根据Jess Telford的代码改编而成,这是我在学习PHPUnit时在网上找到的(http://jes.st/2011/phpunit-bootstrap-and-autoloading-classes/)

class ClassDirectoryAutoLoader {
    static private $classNamesDirectory = array();
    public static function crawlDirectory($directory) {
        $dir = new DirectoryIterator($directory);
        foreach ($dir as $file) {
            self::addClassesAndCrawlDirectories($file);
        }
    }
        private static function addClassesAndCrawlDirectories($file){
            if (self::isRealDirectory($file)) {
                self::crawlDirectory($file->getPathname());
            } elseif (self::isAPhpFile($file)) {
                self::saveClassFilename($file);
            }
        }
            private static function isRealDirectory($file){
                // ignore links, self and parent
                return $file->isDir() && !$file->isLink() && !$file->isDot();
            }
            private static function isAPhpFile($file){
                //ends in .php
                return substr($file->getFilename(), -4) === '.php';
            }
            private static function saveClassFilename($file){
                //assumes that the filename is the same as the classname
                $className = substr($file->getFilename(), 0, -4);
                self::registerClass($className, $file->getPathname());
            }
    public static function registerClass($className, $fileName) {
        self::$classNamesDirectory[$className] = $fileName;
    }
    public static function loadClass($className) {
        if (isset(self::$classNamesDirectory[$className])) {
            require_once(self::$classNamesDirectory[$className]);
        }
    }
}
$classDir = dirname(__FILE__) . '/../classes'; // replace with the root directory for your class files
ClassDirectoryAutoLoader::crawlDirectory($classDir);
spl_autoload_register(array('ClassDirectoryAutoLoader', 'loadClass'));

这个代码的作用是

  1. 重复浏览目录(从classDir),查找.php文件。

  2. 生成一个关联数组,该数组将类名映射到完整的文件名。

  3. 注册自动加载器(loadClass方法)。

当解释器试图实例化一个未定义的类时,它将运行这个自动加载器,它将:

  1. 检查文件是否存储在关联阵列中。

  2. 如果找到该文件,则需要该文件。

我喜欢这个自动加载器,因为:

  • 这很简单。

  • 它是通用的——你几乎可以在任何遵循一些简单约定的项目中使用它(见下文)。

  • 它只对目录树进行一次爬网,而不是每次实例化一个新类。

  • 它只需要所需的文件。

  • loadClass方法非常高效,可以简单地执行查找和require。

这个代码做了一些假设:

  • 所有的类都存储在一个特定的目录中。

  • 该目录中的所有文件都包含类。

  • 文件名与类名完全匹配。

  • 需要一个文件没有任何副作用(即该文件只包含一个类定义,没有过程代码)。

打破这些假设将打破这个自动加载器。

以下是您需要遵守的惯例来使用这个自动加载器:

  1. 将所有类放在一个目录下。

  2. 仅类目录下的类定义文件。

  3. 为每个公共类制作一个单独的文件。

  4. 以每个文件所包含的类命名。

  5. 在这些类定义文件中没有带副作用的过程代码。

好吧,我正在构建一个使用自动加载器的系统,下面是我所做的:

function endswith($string,$tidbit){
// Length of string
$strlen = strlen($string);
// Length of ending
$tidlen = strlen($tidbit);
// If the tidbit is of the right length (less than or equal to the length of the    string)
if($tidlen <= $strlen){
    // Substring requires a place to start the copying
    $tidstart = $strlen - $tidlen;
    // Get $tidlen characters off the end of $string
    $endofstring = substr($string, $tidstart, $tidlen);
    // If the $tidbit matches the end of the string
    $ret = ($endofstring == $tidbit);
    return $ret;
} else {
    // Failure
    return -1;
}
}
// Working
function ush_load_path($path) {
if (is_dir($path)) {
    if (is_file($path . '/' . (explode('/', $path)[count(explode('/', $path)) - 1]) . '.inc')) {
        require_once $path . '/' . (explode('/', $path)[count(explode('/', $path)) - 1]) . '.inc';
    }
    ush_load_path_recursive($path);
    // If it is a file   
} else if (is_file($path)) {
    require_once $path;
    // Failure
} else {
    return false;
}
}
function ush_load_path_recursive($path) {
// Directory RESOURCE
$path_dir = opendir($path);
// Go through the entries of the specified directory
while (false != ($entry = readdir($path_dir))) {
    if ($entry != '.' && $entry != '..') {
        // Create Full Path
        $path_ext = $path . '/' . $entry;
        // Development
        if (is_dir($path_ext)) {
            ush_load_path_recursive($path_ext);
        } else if (is_file($path_ext)) {
            if (ush_is_phplib($path_ext)) {
                print $path_ext . '<br />';
                require_once $path_ext;
            } else {
                // Do nothing
            }
        }
    }
}
}
// Working
function ush_is_phplib($path) {
   return endswith($path, '.inc');
}

你能在closedir($dh)之后打印_r($files)吗;在foreach之前,这样我们就可以看到哪些文件实际上是被加载的,按哪个顺序加载?

load_folder(dirname(__FILE__));
function load_folder($dir, $ext = '.php') {
if (substr($dir, -1) != '/') { $dir = "$dir/"; }
clearstatcache(); // added to clear path cache
if($dh = opendir($dir)) {
    $files = array();
    $inner_files = array();
    while($file = readdir($dh)) {
        if($file != "." and $file != ".." and $file[0] != '.') {
            if(is_dir($dir . $file)) {
                $inner_files = load_folder($dir . $file);
                if(is_array($inner_files)) $files = array_merge($files, $inner_files); 
            } else {
                array_push($files, $dir . $file);
            }
        }
    }
    closedir($dh);

    clearstatcache($dir); // added to clear path cache

    foreach ($files as $file) {
        if (is_file($file) and file_exists($file)) {
            $lenght = strlen($ext);
            if (substr($file, -$lenght) == $ext && $file != 'loader.php') { require_once($file); }
        }
    } 
}

}

似乎必须先调用clearstatcache($path),然后才能对symlink'd目录执行任何文件处理函数。

我可能在这里遗漏了一些内容,但看到那个脚本会让我感到毛骨悚然。。。我假设您使用一个文件夹来调用该函数,在该文件夹中保存所有php函数文件和其他文件,这些文件都包含在内,即使实际脚本只需要其中一个文件即可工作。

我是在这里错过了什么,还是它就是这样工作的?如果没有,我被描述和功能代码误导了。

如果这真的是你正在做的,那么有更好的方法来包含所需的文件,而不需要所有不必要的包含。

我有一个类来处理我所有的脚本加载。我所要做的就是注册加载功能:

spl_autoload_register('Modulehandler::Autoloader'); 

然后,每当需要一个新文件时,PHP都会使用我的函数来查找该文件。

这是函数本身:

static function Autoloader($className) { 
    $files = array($className); 
    $lowerClass = strtolower($className); 
    if (strcmp($className, $lowerClass) != 0) $files[] = $lowerClass; 
    foreach (self::$modules as $moduleName => $module) { 
        foreach ($files as $className) { 
            $file = "{$module}/classes/{$className}.php"; 
            if (file_exists($file)) { 
                require $file; 
                break; 
            } 
        } 
    } 
}

这个类本身不仅仅是加载,因为我还可以将模块添加到加载程序中,所以我只从包含的模块中搜索文件夹,与搜索所有模块相比,可以获得一些性能提升。除此之外,只包含必要的文件还有一个明显的好处。

我希望这符合你的需要,并能帮助你。

您是否检查过AcumulusExportBase是否正确包含在Acumulus ExportWorkshop中?请记住,Linux非常区分大小写,因此文件名应该使用正确的大小写。

LIKE a.JPG在windows中可以称为.JPG,但在LINUX中,我们需要维护正确的情况。

主要区别在于文件系统的类型。当您使用Mac或Windows时,它们都不区分大小写,但Linux实际上将文件名"Capitalized.php"视为"Capitalized.php"

这就是为什么大多数流行的框架都有小写的文件名。