When I first started writing PHP, I was always worried about this problem: Can a new class I create here be loaded into the corresponding class file? After all, a Fatal Error is reported as soon as it is run, some ** file is not found, the class cannot be instantiated, etc. It is a very "low-level" error, and I am afraid that others will read it as a joke. So every time I take on a new task, I always want to figure out its loading process (I only knew a few HTML tags and styles before, and I don’t know if it counts as web development). Sometimes the boss looks at it and says that he still has time to read this, so he quickly writes Logic, just do this... Your sister, you know, of course you are sure of it D:, but later I found out that the processes are similar.
When developing in an IDE, such as C++/Java, you usually create a new project, add a new file to the specified directory through the IDE, and then #include/Import it in. PHP makes this step more procedural, and the file The loading process basically determines the directory structure and file classification of this project (framework or self-built project).
Regardless of the framework or the self-built project, there must be an entry file. At this time, some basic information must be loaded in advance, such as configuration files, general methods, etc. The basic method is to manually load a single file directly, using one of the following four methods:
include, require, include_once, require_once
<span>include</span>('config.php'<span>); </span><span>require</span>('database.php');
When it comes to the loading of class files, a small number of them are loaded directly. For example, common methods are written as static methods in a class Utilities, because they are many methods that will be used later (such as error output, curl request, random string generation. ..), so it is encapsulated with a class and is usually loaded when loading the configuration file
include('Utilities.php');
The more common situation is: dynamic loading of classes. Let’s not talk about the loading method first, let’s take a look at when a class and instance will be used:
1. The most obvious, $obj = new A; its variants $className = 'A'; $obj = $className; are all the same;
2. Calling static methods, static variables and constants of the class, namely Utilities::httpRequest(), Utilities::$instance, Utilities::HOST;
3. In PHP functions, when callback functions are used, the most typical one is call_user_func_array() (call_user_func). There are other places where callback is used, such as array_walk and array_map in arrays, which require a callback function as a parameter.
The callback function is very flexible. It can not only be a simple function, but also an object method, including static class methods. Because you can use object methods or static methods, you have to load the corresponding class file at this time. Since php5.3, callback functions can also be implemented using anonymous functions like in js.
<span>class</span><span> A{ </span><span>public</span> <span>static</span> <span>function</span> cube(<span>$var</span><span>){ </span><span>return</span> <span>pow</span>(<span>$var</span>, 3<span>); } </span><span>public</span> <span>function</span> twice(<span>$var</span><span>){ </span><span>return</span> 2*<span>$var</span><span>; } } </span><span>//</span><span> 使用类的静态方法</span> <span>$num</span> = <span>call_user_func</span>('A::cube', 5<span>); </span><span>//</span><span> 使用对象</span> <span>$obj</span> = <span>new</span><span> A; </span><span>$num</span> = <span>call_user_func_array</span>(<span>array</span>(<span>$obj</span>, 'twice'), <span>array</span>(7));
Strictly speaking, the call_user_func_array in the above example has already instantiated the object before, but there is such a usage, it can also use the class static method.
The first thing to understand is why dynamic loading is needed. PHP is a scripting language. When we access, scripts are used as available resources. For example, there is an index.php file in the root directory. It does not include any other files. When we access it directly as localhost/index.php, we can access it. All resources in index.php. If a common class A is defined in index.php, when an object of A is instantiated in the script, the program will react like this: Oh, I have seen the definition of A and can instantiate it directly. ize it (no need to load other files). If there are many classes such as classes B, C, and D, it will obviously not work if you write them all in index.php. Then write them in other files and include them (include is already doing the loading work). This way, for the program , is also "visible".
However, as the system functions increase, there are more and more classes, and the functions of each class are also different. Some directly define the operation of the database and read the data of the database. Some control the methods to be run when accessing the script, and some are Some of the pages that will be displayed are third-party core libraries that we reference. Therefore, when we put all the files in a directory, although they can be loaded directly by include, the placement of these files seems messy and difficult to find, which reduces the maintenance cost. Still high. Okay, let’s create a few more directories in the root directory. Directory A is dedicated to storing scripts that deal with the database. Directory B is the various configuration information files of the system. Directory C is to control the entry control method when we enter the program. Script, directory D is the page that will be displayed to the browser...
于是MVC架构慢慢就演化出来了,我们不能再像以前那样直接include,脚本都放在特定的目录下,如Controller目录下存放的是各种控制器,加载控制器时,我们得这样include('root/Controller/indexController.php'),每次都在文件前面弄一大串的include不仅看着头疼,简直让人累觉不爱。既然有了获取当前文件路径和类名的现成方法,为何不将类名与文件名对应起来,而只要是控制器类的脚本就全放在根目录的Controller子目录下边,就可以写一个方法,只要是控制器类,在这个方法中运行include(ROOT.'Controller/'.$className.'.php');这一句,ROOT为根目录常量,$className为传入的类名,只要是模型类,就这样include(ROOT.'Model/'.$className.'.php');,全凭这个函数来动态控制到哪个目录里边去找,这个project可能就是这样的:
无形中,就建立起了类名和文件名的对应规则,文件和所在的目录的对应规则,该project下有哪些这样的目录和文件呢?啊原来是放控制器的Controller、放配置信息的Config等等,再次于无形中得知了这个project的结构,而上面说的,利用函数根据一定条件(传入参数)可知自动到哪个目录下去加载该文件,而不是一个个写死的include,就是所谓的文件的动态加载了。
因此,当你要新建一个**类文件时,也就知道,哦在这个project中,我应该放在这个目录下,文件的命名应该与类名相同,这样就一定能加载到了~~~接下来就是写业务逻辑的一个“愉快的过程”。
知道什么时候会动态加载及为什么要动态加载后,接下来就是来实现了,也就是上面说到的利用函数来加载某个文件,就是要写好这个“函数”来实现这个过程。常用的有三种方式:
1. __autoload
我第一次学的时候就是用的就是这个,魔术函数,只要定义了php程序就会在要用到一个类时自动调用它进行文件动态加载,一样,既然它是个函数,就要让程序对__autoload的定义可见,不然从哪儿调用它呢?一般来说,作为后边程序大部分地方要用到的方法,我们都会放在一个单独的文件中,在程序的入口处加载进来,一个project总得有几个文件是手动include的,完全可以在开头单独include进来,或者放在配置信息中,加载配置信息时就加载进来了。它的原型:
void __autoload ( string $class )
参数当前加载的类名名称(注意如果有命名空间,则包含命名空间前缀),下面是一个针对上面的图片结构的简单示例:
<span>//</span><span> file: autoload.php // ROOT为已经定义的根目录常量</span> <span>function</span> __autoload(<span>$className</span><span>){ </span><span>try</span><span>{ </span><span>if</span>(<span>file_exists</span>(ROOT.'Controller/'.<span>$className</span>.'.php')){<span>//</span><span> 检查Controller</span> <span>include</span>(ROOT.'Controller/'.<span>$className</span>.'.php'<span>); } </span><span>else</span> <span>if</span>(<span>file_exists</span>(ROOT.'Model/'.<span>$className</span>.'.php')){<span>//</span><span> 检查Model</span> <span>include</span>(ROOT.'Model/'.<span>$className</span>.'.php'<span>); } </span><span>else</span> <span>if</span>(<span>file_exists</span>(ROOT.'Lib/'.<span>$className</span>.'.php')){<span>//</span><span> 检查Lib</span> <span>include</span>(ROOT.'Lib/'.<span>$className</span>.'.php'<span>); } </span><span>else</span>{ <span>//</span><span> 找不到该文件</span> <span>throw</span> <span>new</span> <span>Exception</span>("ERROR: can't find file {<span>$className</span>}.php"<span>); } } </span><span>catch</span>(<span>Exception</span> <span>$e</span><span>){ </span><span>echo</span> <span>$e</span>.<span>getMessage(); </span><span>exit</span><span>; } }</span>
2. spl_autoload_register
__autoload实际上也差不多了,但它是php定义的,如果现在有个东西写了并调用之后,就告诉程序说,我不用__autoload来加载文件了,我已经定义了一个专门加载文件的方法(比如名称是loadClass),以后需要加载一个类文件时,你就用它吧。spl_autoload_register就是这样一个能告诉程序这样去做的方法,而且自定义加载方法将会更灵活,可以指定多个加载函数,spl_autoload_register函数会将这些函数放在一个队列中,并激活它们,在调用时逐个激活:“If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. ”,php.net上(http://php.net/manual/en/function.spl-autoload-register.php)也确实如此解释,spl_autoload_unregister则是从加载函数队列中注销。
另外spl_autoload_functions()函数,可以获取我们注册了哪些函数;spl_autoload_call($class)函数,尝试调用所有已注册的加载函数来加载$class的类文件。
对于spl_autoload_register的解释,我的理解是,如果用spl_autoload_register注册了n个函数在加载队列中,因为它自动激活它们嘛,现在我要实例化一个类,在第1个加载函数中加载失败了,然后尝试第2个函数,第二个失败则尝试第3个,''',直到第n个函数走完,若还没加载成功,就报错,只要中间一个加载成功就成功了,but事实好像有点出入。
还是用上一个图片中的目录结构,
1、在Controller目下创建indexController.php文件,包含类indexController;
2、在Model目录下创建userModel.php文件,包含类userModel;
3、首页写个类加载脚本Autoload.php,代码如下:
<span>//</span><span> file: Autoload.php</span> <span>define</span>('DS',<span> DIRECTORY_SEPARATOR); </span><span>define</span>('ROOT', <span>rtrim</span>(<span>dirname</span>(<span>__FILE__</span>), '/\\').<span>DS); </span><span>class</span><span> Autoload{ </span><span>public</span> <span>static</span> <span>function</span> autoloadRegister(<span>$loadFunc</span> = 'Autoload::loadControllerClass', <span>$enable</span> = <span>true</span><span>){ </span><span>return</span> <span>$enable</span> ? spl_autoload_register(<span>$loadFunc</span>) : spl_autoload_unregister(<span>$loadFunc</span><span>); } </span><span>//</span><span> 加载控制器类</span> <span>public</span> <span>static</span> <span>function</span> loadControllerClass(<span>$className</span><span>){ </span><span>if</span>(<span>file_exists</span>(ROOT.'Controller'.DS.<span>$className</span>.'.php')){<span>//</span><span> 检查Controller</span> <span>include</span>(ROOT.'Controller'.DS.<span>$className</span>.'.php'<span>); </span><span>echo</span> ROOT.'Controller'.DS.<span>$className</span>.'.php'.'<br/>'<span>; } </span><span>else</span><span>{ </span><span>echo</span> "ERROR: can't find file {<span>$className</span>}.php in ".ROOT."Controller"<span>; </span><span>exit</span><span>; } } </span><span>//</span><span> 加载模型类</span> <span>public</span> <span>static</span> <span>function</span> loadModelClass(<span>$className</span><span>){ </span><span>if</span>(<span>file_exists</span>(ROOT.'Model'.DS.<span>$className</span>.'.php')){<span>//</span><span> 检查Model</span> <span>include</span>(ROOT.'Model'.DS.<span>$className</span>.'.php'<span>); </span><span>echo</span> ROOT.'Model'.DS.<span>$className</span>.'.php'.'<br/>'<span>; } </span><span>else</span><span>{ </span><span>echo</span> "ERROR: can't find file {<span>$className</span>}.php in ".ROOT."Model"<span>; </span><span>exit</span><span>; } } }</span>
4、测试脚本,测试类是否能加载
<span>//</span><span> 注册两个加载函数</span> Autoload::autoloadRegister('Autoload::loadControllerClass'<span>); Autoload</span>::autoloadRegister('Autoload::loadModelClass'<span>);</span><span> // 查看总共注册了哪些加载函数</span> <span>echo</span> 'register functions=> <pre class="brush:php;toolbar:false">'<span>; </span><span>print_r</span><span>(spl_autoload_functions()); </span><span>//</span><span> 分别实例化一个Controller类和Model类</span> <span>$indexCon</span> = <span>new</span><span> indexController; </span><span>$userMod</span> = <span>new</span> userModel;
结果是这样
这不科学啊,spl_autoload_functions数组显示两个函数都注册了,但是当实例化userModel类时它还是跑到Controller目录中去找,两个类的实例化调用的自动加载方法都是Autoload::loadControllerClass,所以userModel类文件加载报错......注意到spl_autoload_register方法的第三个参数, 是添加一个加载函数时放在栈中的位置,于是我另写一个类似的类otherLoad,只是为了将loadModelClass方法放到队列首部:
<span>class</span><span> otherLoad{ </span><span>public</span> <span>static</span> <span>function</span> autoloadRegister(<span>$loadFunc</span> = 'otherLoad::loadModelClass', <span>$enable</span> = <span>true</span><span>){ </span><span>//</span><span> 默认将loadModelClass放在队首</span> <span>return</span> <span>$enable</span> ? spl_autoload_register(<span>$loadFunc</span>, <span>true</span>, <span>true</span>) : spl_autoload_unregister(<span>$loadFunc</span><span>); } </span><span>//</span><span> 加载模型类</span> <span>public</span> <span>static</span> <span>function</span> loadModelClass(<span>$className</span><span>){ </span><span>if</span>(<span>file_exists</span>(ROOT.'Model'.DS.<span>$className</span>.'.php')){<span>//</span><span> 检查Model</span> <span>include</span>(ROOT.'Model'.DS.<span>$className</span>.'.php'<span>); </span><span>echo</span> ROOT.'Model'.DS.<span>$className</span>.'.php'.'<br/>'<span>; } </span><span>else</span><span>{ </span><span>echo</span> "ERROR: can't find file {<span>$className</span>}.php in ".ROOT."Model"<span>; </span><span>exit</span><span>; } } } </span>
测试是这样
<span>//</span><span> 注册三个加载函数</span> Autoload::autoloadRegister('Autoload::loadControllerClass'<span>); Autoload</span>::autoloadRegister('Autoload::loadModelClass'<span>); otherLoad</span>::autoloadRegister('otherLoad::loadModelClass'<span>); </span><span>//</span><span> 查看总共注册了哪些加载函数</span> <span>echo</span> 'register functions=> <pre class="brush:php;toolbar:false">'<span>; </span><span>print_r</span><span>(spl_autoload_functions()); </span><span>//</span><span> 分别实例化一个Controller类和Model类</span> <span>$indexCon</span> = <span>new</span><span> indexController; </span><span>$userMod</span> = <span>new</span> userModel;
这次的结果是这样:
可以看到,这次是在加载indexController类时不成功,因为它只调用了loadModelClass方法,再看看spl_autoload_functions返回的数组,otherLoad类的loadModelClass方法在最前面,难道说,只有在加载函数队列最前面的函数才被用于自动加载,其他无效?这是什么状况?
使用spl_autoload_call('indexController')来“尝试调用所有已注册的函数来装载请求类”,还是报这个错。
翻了下别人的文章,包括github上的博客,也就是列举了下手册上说的“可以一次注册多个加载函数 bala bala......”,难道没有人试过,还是我的理解有问题>3<...(win下测试,php版本5.4.10)真是这样的话spl_autoload_register方法就没多大意义嘛╮(╯▽╰)╭......
关于spl_autoload_register还有几个有意思的地方:
1、 一个函数只会加载到函数队列中一次,重复加载也是如此;
2、 spl_autoload_register如果不指定加载函数(第一个参数),则默认使用加载函数spl_autoload(功能类似于__autoload,是它的默认实现形式)
3、 spl_autoload_register指定了__autoload为加载函数,则一定要实现__autoload;
4、 同时实现了spl_autoload_register和__autoload,优先使用spl_autoload_register注册的加载函数。
以上几种情况几乎都可从php.net的note中找到测试例子,老外写得挺有意思,可供参考。上面第2点还需要注意,比如现在在根目录创建一个目录,使用默认函数来加载:
<span>//</span><span> 设置加载文件的扩展名,将只加载*.php的文件</span> spl_autoload_extensions('.php'<span>); </span><span>//</span><span> 默认使用spl_autoload加载文件,只能加载当前目录下文件:小写类名.php</span> <span> spl_autoload_register(); </span><span>//</span><span> 测试 // $obj = new A;</span>
spl_autoload_extensions设置加载时只认哪些扩展类型的文件,默认是.php或者.inc文件,这里设置成.php,然后就是调用注册函数。在根目录下创建一个A.php文件,新建一个类A,加载成功,再将文件名改成a.php,照样加载成功。需要留意spl_autoload默认将类名转小写,但是A.php照样加载成功,因为Windows的文件是大小写不敏感的(在同一目录下创建一个d.txt,再创建D.txt会认为是同一个文件),对于Mac OS X也是这样,但Linux就是大小写敏感了,测试时要注意这点。
也不是全要自动加载,如CI,它将加载文件封装为一个核心类CI_Loader,程序启动时先include必要的脚本(其他要用的核心类),然后再等需要使用时,CI_Loader实例作为当前控制器类或模型类等的一个属性成员,通过调用它的方法来include各种model(模型)、view(视图)、database(数据库对象)、helper(辅助函数)等等。
无论用不用动态加载,必须保证的是,文件分门别类的放好,文件按一定规则命名,这是一个健壮、高扩展、高易用的project必备的,写起代码来也方便。当然加载文件的多少,占内存的多少,各有不同,也是评判一个框架的若干标准。弄清楚加载方式,熟悉一个框架结构不就是很容易的事了=_=...
以上就介绍了文件加载---理解一个project的第一步,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。