>  기사  >  백엔드 개발  >  파일 로딩---프로젝트를 이해하는 첫 번째 단계

파일 로딩---프로젝트를 이해하는 첫 번째 단계

WBOY
WBOY원래의
2016-08-08 09:29:51893검색

처음 PHP 작성을 시작했을 때 나는 항상 다음 문제에 대해 걱정했습니다. 여기에서 새 클래스를 해당 클래스 파일에 로드할 수 있습니까? 결국 치명적인 오류는 실행되자마자 보고되고 일부 ** 파일을 찾을 수 없으며 클래스를 인스턴스화할 수 없는 등의 문제가 발생합니다. 이는 매우 "낮은 수준"의 오류이므로 다른 사람들이 읽을까 두렵습니다. 농담으로요. 그래서 새로운 작업을 맡을 때마다 로딩 과정을 항상 파악하고 싶어합니다. (저는 이전에 몇 가지 HTML 태그와 스타일만 알았고, 이것이 웹 개발로 간주되는지는 모르겠습니다.) 가끔 상사가 살펴보곤 합니다. 그리고 아직 읽을 시간이 있다고 해서 빨리 Logic을 작성하고 이렇게 하세요... 당신의 여동생은 물론 D:라고 확신하지만 나중에 프로세스가 거의 완료되었다는 것을 알게 되었습니다. 동일합니다.

C++/Java와 같은 IDE에서 개발할 때 일반적으로 새 프로젝트를 만들고 IDE를 통해 지정된 디렉터리에 새 파일을 추가한 다음 #include/Import를 사용합니다. PHP는 이 단계를 보다 절차적으로 만듭니다. , 파일 로딩 프로세스는 기본적으로 이 프로젝트(프레임워크 또는 자체 구축 프로젝트)의 디렉토리 구조와 파일 분류를 결정합니다.

프레임워크이든 자체 빌드된 프로젝트이든 항목 파일이 있어야 합니다. 이때 구성 파일, 일반 방법 등 일부 기본 정보를 미리 로드해야 합니다. 기본 방법 단일 파일을 직접 수동으로 로드하는 것입니다. 1:

 include, require, include_once, require_once

    <span>include</span>('config.php'<span>);
    </span><span>require</span>('database.php');

클래스 파일을 로드할 때 일부는 직접 로드됩니다. 예를 들어 일반적인 메소드는 나중에 사용할 메소드가 많기 때문에 유틸리티 클래스에 정적 메소드로 작성됩니다(예: 오류 출력, 컬 요청, 임의의 문자열 생성...)이므로 클래스로 캡슐화되며 일반적으로 구성 파일을 로드할 때 로드됩니다

include('Utilities.php');

더 일반적인 상황은 클래스의 동적 로딩입니다. 먼저 로딩 방법에 대해 이야기하지 말고 클래스와 인스턴스가 언제 사용되는지 살펴 보겠습니다.

1. 가장 확실한 것은 $obj = new A; $className = 'A' $obj = $className;

 2. 클래스의 정적 메서드, 정적 변수 및 상수 호출, 즉 Utilities::httpRequest(), Utilities::$instance, Utilities::HOST

 3. PHP 함수에서는 콜백 함수가 사용됩니다. 가장 일반적인 것은 call_user_func_array()(call_user_func)입니다. 배열에 array_walk 및 array_map과 같이 콜백 함수가 사용됩니다. .

콜백 함수는 매우 유연합니다. 단순한 함수일 뿐만 아니라 정적 클래스 메서드를 포함한 객체 메서드일 수도 있습니다. 객체 메소드나 정적 메소드를 사용할 수 있으므로 이때 해당 클래스 파일을 로드해야 합니다. php5.3부터는 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));
엄밀히 말하면 위 예제의 call_user_func_array는 이미 객체를 인스턴스화했지만 그런 사용법도 있고 클래스 정적 메서드를 사용할 수도 있습니다.

가장 먼저 이해해야 할 것은 동적 로딩이 필요한 이유입니다. PHP는 스크립팅 언어입니다. 액세스할 때 스크립트가 사용 가능한 리소스로 사용됩니다. 예를 들어, 루트 디렉터리에 index.php 파일이 있으며, localhost/index.php로 직접 액세스할 경우에는 다른 파일이 포함되지 않습니다. index.php에 있는 모든 리소스 공통 클래스 A가 index.php에 정의되어 있으면 A의 객체가 스크립트에서 인스턴스화될 때 프로그램은 다음과 같이 반응합니다. A를 정의하고 직접 인스턴스화할 수 있습니다(다른 파일을 로드할 필요 없음). B, C, D 등의 클래스가 있는 경우 index.php에 모두 작성하고 다른 파일에 작성한 다음 포함하면(include가 이미 로딩 작업을 수행하고 있음) 작동하지 않습니다. 프로그램의 경우에도 "표시"됩니다.

그러나 시스템 기능이 증가함에 따라 클래스가 점점 더 많아지고 각 클래스의 기능도 다릅니다. 일부는 데이터베이스 작업을 직접 정의하고 데이터베이스 데이터를 읽으며, 일부는 스크립트에 액세스할 때 실행할 메소드를 제어하고, some 표시될 페이지이며, 그 중 일부는 우리가 참조하는 타사 핵심 라이브러리입니다. 따라서 모든 파일을 디렉토리에 넣을 때 include를 통해 직접 로드할 수 있지만 이러한 파일의 배치는 지저분해 보입니다. 유지관리 비용도 여전히 높습니다. 좋습니다. 루트 디렉터리에 디렉터리를 몇 개 더 만들어 보겠습니다. 디렉터리 A는 데이터베이스를 처리하는 스크립트를 저장하는 전용이고 디렉터리 B는 시스템의 다양한 구성 정보 파일입니다. 프로그램.스크립트, 디렉토리 D는 브라우저에 표시되는 페이지입니다...

  于是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教程有兴趣的朋友有所帮助。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:PHP 대기열다음 기사:PHP 대기열