基本上,我前一阵子编写了此类,以简化本地库的自动加载。

前提是,所有内容都由程序包拆分为多层子程序包。类使用CamelCasing命名。因此,一个类的名称与其包相关,如下所示:PackageSubpackageSubpackageName。现在,每个软件包都可以具有isPackageName为接口定义的特定于软件包的接口,PackageNameException所定义的异常等。我试图使其具有足够的灵活性以供重用。

/**
 * A class for lazy-loading other classes
 *
 * This class enables lazy-loading of php classes.  The benefit of this is
 * three-fold.  First, there is a memory benefit, since not all classes are
 * loaded until they are needed.  Second, there is a time benefit, since not all
 * classes are loaded.  Third, it produces cleaner code, since there is no need
     * to litter files with require_once() calls.
 *
 * @category Libraries
 * @package  Libraries
 * @author   Me
 */
abstract class Loader
{

    /**
     * @var array An array of class to path mappings
     */
    protected static $classes = array();

    /**
     * @var boolean Has the loader been initialized already
     */
    protected static $initialized = false;

    /**
     * @var array An array of auto-search paths
     */
    protected static $namedPaths = array(
        'exception',
        'interface',
        'iterator',
    );

    /**
     * @var array An array of include paths to search
     */
    protected static $paths = array(
        PATH_LIBS,
    );

    /**
     * Tell the auto-loader where to find an un-loaded class
     *
     * This can be used to "register" new classes that are unknown to the
     * system.  It can also be used to "overload" a class (redefine it
     * elsewhere)
     *
     * @param string $class The class name to overload
     * @param string $path  The path to the new class
     *
     * @throws InvalidArgumentException Upon an Invalid path submission
     * @return void
     */
    public static function _($class, $path)
    {
        $class = strtolower($class);
        if (!file_exists($path)) {
            throw new InvalidArgumentException('Invalid Path Specified');
        }
        self::$classes[$class] = $path;
    }

    /**
     * Add a path to the include path list
     *
     * This adds a path to the list of paths to search for an included file.
     * This should not be used to overload classes, since the default include
     * directory will always be searched first.  This can be used to extend
     * the search path to include new parts of the system
     *
     * @param string $path The path to add to the search list
     *
     * @throws InvalidArgumentException when an invalid path is specified
     * @return void
     */
    public static function addPath($path)
    {
        if (!is_dir($path)) {
            throw new InvalidArgumentException('Invalid Include Path Added');
        }
        $path = rtrim($path, DS);
        if (!in_array($path, self::$paths)) {
                self::$paths[] = $path;
        }
    }

    /**
     * Add a path to the auto-search paths (for trailing extensions)
     *
     * The path should end with an 's'.  Default files should not.
     *
     * @param string $path The name of the new auto-search path
     *
     * @return void
     */
    public static function addNamedPath($path)
    {
        $path = strtolower($path);
        if (substr($path, -1) == 's') {
            $path = substr($path, 0, -1);
        }
        if (!in_array($path, self::$namedPaths)) {
            self::$namedPaths[] = $path;
        }
    }

    /**
     * Initialize and register the autoloader.
     *
     * This method will setup the autoloader.  This should only be called once.
     *
     * @return void
     */
    public static function initialize()
    {
        if (!self::$initialized) {
            self::$initialized = true;
            spl_autoload_register(array('Loader', 'load'));
        }
    }

    /**
     * The actual auto-loading function.
     *
     * This is automatically called by PHP whenever a class name is used that
     * doesn't exist yet.  There should be no need to manually call this method.
     *
     * @param string $class The class name to load
     *
     * @return void
     */
    public static function load($class)
    {
        $className = strtolower($class);
        if (isset(self::$classes[$className])) {
            $file = self::$classes[$className];
        } else {
            $file = self::findFile($class);
        }
        if (file_exists($file)) {
            include_once $file;
        }
    }

    /**
     * Find the file to include based upon its name
     *    
     * This splits the class name by uppercase letter, and then rejoins them
     * to attain the file system path.  So FooBarBaz will be turned into
     * foo/bar/baz.  It then searches the include paths for that chain.  If baz
     * is a directory, it searches that directory for a file called baz.php.
     * Otherwise, it looks for baz.php under the bar directory.
     *
     * @param string $class The name of the class to find
     *
     * @return string The path to the file defining that class
     */
    protected static function findFile($class)
    {
        $regex = '#([A-Z]{1}[a-z0-9_]+)#';
        $options = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
        $parts = preg_split($regex, $class, null, $options);

        $subpath = '';
        $file = strtolower(end($parts));
        $test = strtolower(reset($parts));
        if ($test == 'is') {
            array_shift($parts);
            return self::findNamedFile($class, $parts, 'interface');
        }
        foreach ($parts as $part) {
            $subpath .= DS . strtolower($part);
        }    
        foreach (self::$paths as $path) {
            $newpath = $path . $subpath;
            if (is_file($newpath . '.php')) {
                return $newpath . '.php';
            } elseif (is_file($newpath . DS . $file . '.php')) {
                return $newpath . DS . $file . '.php';
            }
        }
        if (in_array($file, self::$namedPaths)) {
            //Get rid of the trailing part
            array_pop($parts);
            return self::findNamedFile($class, $parts, $file);
        }    
        return '';
    }

    /**
     * Find a file for named directories (interfaces, exceptions, iterators, etc)
     *
     * @param string $class The class name of the exception to find
     * @param array  $parts The parts of the class name pre-split
     * @param string $name  The name of the named directory to search in
     *    
     * @return string The found path, or '' if not found
     */
    protected static function findNamedFile($class, array $parts, $name)
    {    
        if (empty($parts)) {
            return '';
        }
        $name = strtolower($name);
        //Add a trailing s, since individual files are not plural
        $filename = $name;
        $name .= 's';
        //Try the global path first
        $subpath = DS . $name . DS . strtolower(implode('', $parts)) . '.php';
        foreach (self::$paths as $path) {
            $newpath = $path . $subpath;
            if (is_file($newpath)) {
                return $newpath;
            }
        }
        //Try to build a full sub path for package specific named files
        $package = array_shift($parts);
        $subpath = DS . strtolower($package) . DS . $name . DS;
        if (!empty($parts)) {
            $subpath .= strtolower(implode('', $parts)) . '.php';
        } else {
            $subpath .= $filename . '.php';
        }
        foreach (self::$paths as $path) {
            $newpath = $path . $subpath;
            if (is_file($newpath)) {
                return $newpath;
            }
        }
        return '';
    }
}


完全经过单元测试。

您有什么想法?太复杂了吗?

#1 楼

我看到的第一个问题是,在很多情况下,有人会创建一个名称(DataMapper)中带有多个单词的类,而您提供的自动加载器不允许这样做。我建议使用另一个字符来分隔软件包名称。 Zend框架使用Package_SubPackage_SubPackage_Class,并且效果很好。

顺便说一句,我不确定您编写自动装载机的具体原因是什么(无论是用于生产,教育还是其他)。 ,但是如果您打算将其用于生产,则建议您从Zend Framework中推荐Zend_Loader类,因为该类已得到支持,经过全面测试并正在不断开发中。您可以在此处阅读快速入门指南

评论


\ $ \ begingroup \ $
嗯,这是个人喜好,但我不能忍受在类名中使用_。我发现很难打字,并且把它分开得太多了。至于名称中的多个单词,是的,这很有效。我为什么不使用Zend?好吧,这是个人喜好。我过去曾尝试过,但并不在意。我可以使用它,但它以错误的方式给我带来了麻烦...不过,感谢您的见解...
\ $ \ endgroup \ $
–ircmaxell
2011年1月19日21:37

\ $ \ begingroup \ $
要澄清一下,当我谈论Zend时,我并没有推荐整个框架,而只是推荐了autoloader类,该类可以与zf中的所有其他组件完全脱钩使用。
\ $ \ endgroup \ $
–韦德·坦迪(Wade Tandy)
2011年1月19日在21:50

\ $ \ begingroup \ $
HiMyNameIsRobertAndIWorkLongHoursSadFace和Hi_My_Name_Is_Robert_And_I_Work_Long_Hours_Sad_Face,我相信这是_的原因
\ $ \ endgroup \ $
– RobertPitt
2011年1月19日23:36

\ $ \ begingroup \ $
@RobertPitt:我认为如果您的类名真的那么长,您需要重构或重新考虑您的包装系统...
\ $ \ endgroup \ $
–ircmaxell
2011年1月20日14:40

#2 楼

我发现了一些要点:

该类被标记为abstract,让我感到奇怪,因为我发现它仅具有静态方法调用,并且由于它对静态调用使用“ self ::”,所以我猜有无论如何,没有有意义的方法来扩展该类。 (关于LSB的问题)。

我认为该类“ all static”没有什么大问题,我认为它适合您的项目。 (您没有明确的引导程序,并且不需要/不需要该类的多个实例)


include_once $file;让我感到有些奇怪,因为“ _once”部分应该不需要。但是,如果您在项目的稍后阶段编写了加载程序,我会发现可能需要在哪里避免遇到两次加载类的问题。

通常,我会说您没有让php记住它是否已经接触过文件(并在其上进行磁盘昂贵的realpath()),因为对于每个以前未知的类,只会调用一次加载函数。


总而言之,我认为代码/使用率很好,而且也不太复杂。

替代品

提到了即将到来的“标准”和库,所以我我只会指出另一种自动加载的方式,“性能更好”并且侵入性较小(在您的应用程序中需要更少的代码)。您只需要包含在引导程序中的所有类(接口等)。可以再次运行它以拾取新的类,或者可以手动编辑结果文件。 (有些人围绕它构建工具,因此如果找不到类,它会在开发中自动重新创建自己)。

#3 楼

当涉及到自动加载器时,我倾向于使其与我的任何项目兼容,因此,我将始终保持最佳编码标准,例如Zend。
有一项建议规定了类的布局,目录结构,自动加载程序可以很好地工作的名称空间。
以下描述了自动加载程序互操作性必须遵守的强制性要求。


必填项:


/>完全合格的名称空间和类必须具有以下结构:
     \<Vendor Name>\(<Namespace>\)*<Class Name>



每个名称空间必须具有顶级名称空间(“供应商名称”)。 />

每个名称空间可以根据需要拥有任意数量的子名称空间。


从文件系统加载时,每个名称空间分隔符都会转换为DIRECTORY_SEPARATOR


类名中的每个_字符都转换为DIRECTORY_SEPARATOR_字符在名称空间中没有特殊含义。


从文件系统加载时,标准名称空间和类的后缀为“ .php”。


供应商名称,名称空间和类名称中的字母字符可以是大小写的任意组合。


示例:


\Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php


\Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php

\Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php


\Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php


使用以上内容构造您的自动加载器肯定可以在您的项目中迁移有关是否具有名称空间的天气。 >我在大约6个项目中使用了一个非常好的类,我发现这是完美的,您应该学习并了解如何使用它。
类链接
示例用法是像这样:
$classLoader = new SplClassLoader('Doctrine\Common', '/libs/doctrine');
$classLoader->register();

$classLoader = new SplClassLoader('Ircmexell\MyApplication', 'libs/internal');
$classLoader->register();


#4 楼

大约一年以来,我使用了一个非常简单的自动加载程序,该工程的通用PHP文件的根目录用于我的项目以及所有包含的库(Zend,Rediska等)。目录。

/ external中的所有库都已从svn / git中完全检出,然后我在/ app中为其PHP代码建立了符号链接。

PHPExcel

pwd 
/var/www/project/app
ls -lah PHPE*
PHPExcel -> ../external/PHPExcel/PHPExcel
PHPExcel.php -> ../external/PHPExcel/PHPExcel.php


然后我在index.php文件中放入以下内容:

set_include_path (PATH . 'app');
require 'somepath/Autoloader.php';
Autoloader::registerAutoload ();


它使我可以通常使用一种(与Zend,Rediska,PHPExcel和许多其他库完全兼容),并且是所有库的非常简单的自动加载器。