>  기사  >  백엔드 개발  >  자신만의 PHP MVC 프레임워크 구축

자신만의 PHP MVC 프레임워크 구축

不言
不言원래의
2018-06-07 11:42:585283검색

이 글은 주로 자신만의 PHP MVC 프레임워크를 구축하는 방법을 소개하고, PHP에서 MVC 프레임워크를 구축하기 위한 구체적인 단계, 관련 운영 기술 및 주의 사항을 자세히 분석합니다. 자신만의 PHP MVC 프레임워크 메소드를 구축하는 방법. 참고를 위해 다음과 같이 공유합니다.

머리말PHP용 MVC 프레임워크를 작성할 때 모든 사람의 마음에 가장 먼저 떠오르는 단어는 "바퀴 만들기"입니다. 프로그래머에게는 깊은 기술이 없기 때문에 그들이 작성하는 PHP 프레임워크는 확실히 마스터가 작성하고 시간과 다양한 프로젝트를 통해 테스트된 프레임워크만큼 좋지 않습니다. 하지만 저는 여전히 준비가 되어 있었고 그 이유는 다음과 같았습니다.

PHP의 모든 측면을 이해하고 있다고 생각했지만, PHP를 배운 기간이 짧고 기초가 탄탄하지 않아 가끔 매뉴얼을 확인해야 합니다. 또한 일반적으로 사용되는 많은 함수의 매개변수에 대해서는 네임스페이스 및 리플렉션과 같은 PHP의 새로운 기능 중 일부를 간략하게 살펴봤을 뿐 실제로 적용하지는 않았습니다.

PHP에 대한 지식은 풍부하고 복잡합니다. 일반적인 프로젝트는 주로 비즈니스 로직 코드이며, 프레임워크는 이러한 지식 포인트를 통합할 수 있는 프로젝트입니다.

프레임워크를 작성할 때 TP/CI/YII 등 내가 사용한 일부 프레임워크의 소스 코드도 참조합니다. 또한 소스 코드를 보면 프레임워크를 이해하는 데 도움이 될 수 있습니다. , 앞으로 사용할 프레임을 받아들이기가 더 쉬울 것입니다.

그래서 이번에 바퀴를 만드는 목적은 바퀴를 만드는 것이 아니라, 바퀴를 만드는 과정을 익히고, 바퀴의 특징을 정리하고, 바퀴를 더 잘 활용하기 위함입니다.

완전한 PHP 프레임워크를 작성하려면 디자인 패턴, 반복자, 이벤트 및 후크 등과 같은 많은 PHP 지식 포인트는 물론 다양한 기본 지식의 유연한 적용을 마스터해야 합니다. 이것들을 완전히 제어할 수는 없을 것 같아서 먼저 뼈대를 직접 구축한 다음, 다양한 PHP 프레임워크의 특성을 참조하여 천천히 개선해 나가는 것이 좋습니다. 업무상 밤에 알고리즘, 네트워크 등 프로그래밍 기초를 따라잡아야 하기 때문에 주말에는 PHP 프레임워크 부분만 업데이트할 시간이 있을 수 있으며, 사용한 지식 포인트를 정리하고 프레임워크 업데이트 후 블로그 게시물을 업데이트하겠습니다. 기능.

먼저 프레임워크의 현재 소스 코드를 넣습니다:

GITHUB/zhenbianshu

전체 프레임워크먼저 PHP MVC 프레임워크의 작업 흐름을 요약해 보겠습니다.

간단히 말하면 다음과 같이 시작됩니다. 항목 파일 요청을 수락하려면 경로를 선택하고, 요청을 처리하고, 결과를 반환합니다.

물론 제가 몇 문장으로 요약한 내용은 실제로 많은 작업을 필요로 합니다. PHP 프레임워크는 요청을 받아들일 때마다 상수를 정의하고, 구성 파일과 기본 클래스를 로드하고, 액세스한 URL을 기반으로 논리적 판단을 내리고, 해당 (모듈) 컨트롤러와 메소드를 선택하고 해당 클래스를 자동으로 로드합니다. 요청을 처리한 후 프레임워크는 해당 템플릿 파일을 선택 및 렌더링하고 HTML 페이지 형식으로 응답을 반환합니다. 로직을 처리할 때 오류 및 예외 처리도 고려해야 합니다.

1. MVC 프레임워크로서 전체 상황을 제어하려면 고유한 항목 파일이 있어야 합니다. 모든 액세스 요청은 먼저 내 프레임워크의 루트 디렉터리에 index.php와 같은 이 항목 파일을 입력합니다. 기본 폴더 경로, 현재 환경을 정의하고, 현재 환경을 기반으로 오류 보고 수준을 정의합니다.

2. PHP에서 다른 파일을 로드하려면 require와 include를 사용하세요. 둘 다 대상 파일의 내용을 현재 파일에 로드하고 require 또는 include 문을 대체하여 로드할 때 로드하고 실행합니다. 필요할 때 로드되고 실행되며, _once 구조는 여러 번 쓸 때 한 번만 실행됨을 나타냅니다.

3. 프레임워크의 구성 변수는 전용 구성 파일을 사용하여 저장됩니다. 여기서는 TP의 배열 반환 방법을 모방하고

함수를 사용하여 배열을 구문 분석하고 배열의 키를 상수로 정의했습니다. 배열.

compileConf()

if (!function_exists('compile_conf')) {
  function compileConf($conf) {
    foreach ($conf as $key => $val) {
    if(is_array($val)){
       compileConf($val);
      }else{
      define($key, $val);
      }
    }
   }
}
compileConf(require_once CONF_PATH.'config.php');

네임스페이스와 자동 로딩네임스페이스와 자동 로딩을 함께 사용하는 이유는 무엇인가요?

PHP 프로젝트에서는 클래스가 많을 때 클래스 이름이 중복되면 혼란이 생기고, 같은 이름의 파일은 같은 폴더에 존재할 수 없으므로 이때 네임스페이스와 폴더가 함께 작용하게 됩니다. 폴더는 하나하나의 상자로 이해됩니다. 네임스페이스는 레이블과 같고 상자는 레이블에 해당합니다. 클래스를 정의할 때 다양한 클래스를 서로 다른 상자에 넣고 해당 레이블을 첨부합니다. 클래스를 자동으로 로딩할 때, 라벨(네임스페이스)을 기준으로 해당 박스(폴더)를 쉽게 찾아 해당 클래스 파일을 찾을 수 있습니다.

클래스 자동 로딩에 관해서는 __autoload() 마법 함수가 있습니다. 현재 경로에서 찾을 수 없는 객체를 인스턴스화하면 전달된 클래스 이름에 따라 해당 클래스가 자동으로 호출됩니다. 함수 본문에 로드됩니다.

现在我们多用spl_autoload_register()函数,它可以注册多个函数来代替__autoload函数的功能,我们传入一个函数名为参数,spl_autoload_register会将这个函数压入栈中,在实例化一个当前路径内找不到的类时,系统将会将函数出栈依次调用,直到实例化成功。

spl_autoload_register('Sqier\Loader::autoLoad');
class Loader {
public static function autoLoad($class) {
  //如果有的话,去除类最左侧的\
  $class = ltrim($class, '\\');
  //获取类的路径全名
  $class_path = str_replace('\\', '/', $class) . EXT;
  if (file_exists(SYS_PATH . $class_path)) {
    include SYS_PATH . $class_path;
    return;
  }
  if (file_exists(APP_PATH . $class_path)) {
    include APP_PATH . $class_path;
    return;
  }
}

现在Loader类还是一个简单的类,待以后慢慢完善。

路由选择

接下来就是路由选择了,其本质是根据当前定义的全局URL模式选择合适的方法来分析传入的URI,加载对应的类,并实现对应的方法。

class Router {
public static $uri;
public static function bootstrap() {
  self::$uri = $_SERVER['REQUEST_URI'];
  switch (URL_MODE) {
    case 1: {
      self::rBoot();
      break;
    }
    default: {
      self::rBoot();
    }
  }
}
public static function rBoot() {
  $router = isset($_GET['r']) ? explode('/', $_GET['r']) : [
    'index',
    'index'
  ];
  $cName = 'Controller\\' . ucfirst($router[0]);
  $aName = isset($router[1]) ? strtolower($router[1]) . 'Action' : 'indexAction';
  $controller = new $cName();
  $controller->$aName();
  }
}

这样,我在地址栏输入 zbs.com/index.php?r=index/login 后,系统会自动调用/app/Controller/Index.php下的login方法。完成了这么一个简单的路由。

阶段总结:

接下来我会优化现有的工具类,添加显示层,添加数据库类,还会将一些别的框架里非常cool的功能移植进来~

接上文(代码有所更新),继续完善框架(二):

对于本次更新,我想说:

① 本框架由本人挑时间完善,而我还不是PHP大神级的人物,所以框架漏洞难免,求大神们指出。
② 本框架的知识点应用都会写在博客里,大家有什么异议的可以一起讨论,也希望看博客的也能学习到它们。
③ 本次更新,更新了函数规范上的一些问题,如将函数尽量的独立化,每一个函数尽量只单独做好一件事情,尽量减少函数依赖。还对框架的整体优化了一下,添加了SQ全局类,用以处理全局函数,变量。

回调函数

替换了很low的类名拼装实例化,然后拼装方法名的用法,使用PHP的回调函数方式:

原代码:

$controller_name = 'Controller\\' . self::$c_name;
$action_name = self::$a_name . 'Action';
$controller = new $controller_name();
$controller->$action_name();

修改后代码

$controller_name = 'Controller\\' . self::$c_name;
$controller = new $controller_name();
call_user_func([
  $controller,
  self::$a_name . 'Action'
]);

这里介绍一下PHP的函数回调应用方式:call_user_func和call_user_func_array:

call_user_func ( callback $function [, mixed $parameter [, mixed $... ]] )

调用第一个参数所提供的用户自定义的函数。

返回值:返回调用函数的结果,或FALSE。

call_user_func_array()的用法跟call_user_func类似,只不过传入的参数params整体为一个数组。

另外,call_user_func系列函数还可以传入在第一个参数里传入匿名参数,可以很方便的回调某些事件,这些特性在复杂的框架里应用也十分广泛,如yii2的事件机制里回调函数的使用就是基于此。

VIEW层和ob函数

框架在controller的基类中定义了render方法来渲染页面,它会调用类VIEW的静态函数来分析加载对应页面的模板。

public static function display($data, $view_file) {
  if(is_array($data)) {
    extract($data);//extract函数解析$data数组中的变量
  }else {
    //抛出变量类型异常
  }
  ob_start();
  ob_implicit_flush(0);
  include self::checkTemplate($view_file);//自定义checkTemplate函数,分析检查对应的函数模板,正常返回路径
  $content = ob_get_clean();
  echo $content;
}

这里重点说一下ob(output buffering)系列函数,其作用引用简明代魔法的ob作用介绍:

① 防止在浏览器有输出之后再使用setcookie,或者header,session_start函数造成的错误。其实这样的用法少用为好,养成良好的代码习惯。
② 捕捉对一些不可获取的函数的输出,比如phpinfo会输出一大堆的HTML,但是我们无法用一个变量例如$info=phpinfo();来捕捉,这时候ob就管用了。
③ 对输出的内容进行处理,例如进行gzip压缩,例如进行简繁转换,例如进行一些字符串替换。
④ 生成静态文件,其实就是捕捉整页的输出,然后存成文件,经常在生成HTML,或者整页缓存中使用。

它在ob_start()函数执行后,打开缓冲区,将后面的输出内容装进系统的缓冲区,ob_implicit_flush(0)函数来关闭绝对刷送(echo等),最后使用ob_get_clean()函数将缓冲区的内容取出来。

类__URL__常量和全局类

TP里的__URL__等全局常量用着很方便,可以很简单的实现跳转等操作,而定义它的函数createUrl函数我又想重用,于是借鉴YII的全局类定义方法:

定义基类及详细方法(以后的全局方法会写在这里)

class BaseSqier{
  //方法根据传入的$info信息,和当前URL_MODE解析返回URL字符串
  public static function createUrl($info = '') {
    $url_info = explode('/', strtolower($info));
    $controller = isset($url_info[1]) ? $url_info[0] : strtolower(CONTROLLER);
    $action = isset($url_info[1]) ? $url_info[1] : $url_info[0];
    switch(URL_MODE){
      case URL_COMMON:
        return "/index.php?r=" . $controller . '/' . $action;
      case URL_REWRITE:
        return '/' .$controller . '/' . $action;
    }
  }
}

在启动文件中定义类并继承基类;

require_once SQ_PATH.'BaseSqier.php';
class SQ extends BaseSqier{
}

在全局内都可以直接使用SQ::createUrl()方法来创建URL了。这样,定义__URL__常量就很轻松了。

用单例模式定义数据库连接基类

class Db {
  protected static $_instance;
  public static function getInstance() {
    if(!(self::$_instance instanceof self)) {
      self::$_instance = new self();
    }
    return self::$_instance;
  }
  private function __construct() {
    $link = new \mysqli(DB_HOST, DB_USER, DB_PWD, DB_NAME) or die("连接数据库失败,请检查数据库配置信息!");
    $link->query('set names utf8');
  }
  public function __clone() {
    return self::getInstance();
  }
}

使用单例模式的核心是:

① 私有化构造函数,使无法用new来创建对象,也防止子类继承它并改写其构造函数;
② 用静态变量存放当前对象,定义静态方法来返回对象,如对象还未实例化,实例化一个,存入静态变量并返回。
③ 构造其__clone魔术方法,防止clone出一个新的对象;

DB类的sql查询函数

DB查询函数是一个很复杂的部分,它是一个自成体系的东西,像TP和YII的查询方法都有其独特的地方。我这里暂时先借用TP的MODEL基类,有时间再慢慢补这个。

嗯,介绍一下像TP的查询里的方法联查的实现,其诀窍在于,在每个联查方法的最后都用 return this 来返回已处理过的查询对象。

阶段总结:

yii2里的数据表和model类属性之间的映射很酷(虽然被深坑过), 前面一直避开的模块(module,我可以想像得到把它也添加到URI时解析的麻烦)有时间考虑一下。

接上文,继续完善框架(三)

本次更新的主要内容有:

① 介绍了异常处理机制
② 完善了异常和错误处理
③ 数据表跟Model类的映射

异常处理

异常处理:异常处理是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)

异常处理用于处理程序中的异常状况,虽说是“异常状态”,但仍然还是在程序编写人员的预料之中,其实程序的异常处理完全可以用‘if else'语句来代替,但异常处理自然有其优势之处。

个人总结其优点如下:

① 可以快速终止流程,重置系统状态,清理变量和内存占用,在普通WEB应用中,一次请求结束后,FAST CGI会自动清理变量和上下文,但如果在PHP的命令行模式执行守护脚本时,它的效果就会很方便了。

② 大量的if else语句会使代码变得繁杂难懂,使用异常处理可以使程序逻辑更清晰易懂,毕竟处理异常的入口只有catch语句一处。

③ 一量程序中的函数出现异常结果或状况,如果使用函数的return方式返回异常信息,层层向上,每一次都要进行return判断。使用异常处理我们可以假设所有的返回信息都是正常的,避免了大量的代码重复。

虽然将代码放在try catch块中会有微微的效率差,但是跟这些优点一比,这点消耗就不算什么了。那么PHP的异常处理怎么使用呢?

PHP内置有Exception类,使得我们可以通过实例化异常类来抛出异常。我们将代码放在try语句中执行,并在其后用catch试图捕捉到在try代码块中抛出的异常,并对异常进行处理。我们还可以在catch代码段后使用finally语句块,无论是否有异常都会执行finally代码块的代码,try catch语句形如下面代码:

try{
  throw new Exeption('msg'[,'code',$previous_exeception]);
}catch(Exeption $var) {
  process($var);
}catch(MyException $e){
  process($e)
}finally{
  dosomething();
}

使用try catch语句,需要注意:

① 当我们抛出异常时,会实例化一个异常类,此异常类可以自己定义,但在catch语句中,我们需要规定要捕获的异常对象的类名,并且只能捕获到特定类的异常对象,当然我们可以在最后捕获一个异常基类(PHP内置异常类)来确保异常一定能被捕获。

② 在抛出异常时,程序会被终止,并回溯代码找到第一个能捕获到它的catch语句,try catch语句是可以嵌套的,并且如上面代码所示 cacth语句是可以多次定义的。

finally块会在try catch块结束后执行,即使在try catch块中使用return返回,程序没有执行到最后。

框架里的异常处理

说了那么多异常相关(当然解释这些也是为了能理解和使用框架),那么框架里要怎么实现呢?

重写异常类

我们可以重写异常类,完善其内部方法:

<?php
class Exception
{
  protected $message = &#39;Unknown exception&#39;;  // 异常信息
  protected $code = 0;            // 异常代码
  protected $file;              // 发生异常的文件名
  protected $line;              // 发生异常的代码行号
  function __construct($message = null, $code = null,$previous_exeception = null);
  final function getMessage();        // 返回异常信息
  final function getCode();          // 返回异常代码
  final function getFile();          // 返回发生异常的文件名
  final function getLine();          // 返回发生异常的代码行号
  final function getTrace();         // 返回异常trace数组
  final function getTraceAsString();     // 返回异常trace信息
  /**
   * 记录错误日志
   */
  protected function log(){
    Logger::debug();
  }
}

如上,final方法是不可以重写的,除此之外,我们可以定义自己的方法,如记录异常日志,像我自定义的log方法,在catch代码块中,就可以直接使用$e->log来记录一个异常日志了。

注册全局异常方法

我们可以使用set_exception_handler('exceptionHandler')来全局捕获没有被catch块捕获到的异常,此异常处理函数需要传入一个异常处理对象,这样可以分析此异常处理信息,避免系统出现不人性化的提示,增强框架的健壮性。

function exceptionHandler($e) {
  echo &#39;有未被捕获的异常,在&#39; . $e->getFile() . "的" . $e->getLine() . "行!";
}

其他全局函数

顺便再说一下其他的全局处理函数:

set_shutdown_function('shutDownHandler')来执行脚本结束时的函数,此函数即使是在ERROR结束后,也会自动调用。

set_error_handler('errorHandler')在PHP发生错误时自动调用,注意,必须在已注册错误函数后才发出的错误才会调用。函数参数形式应为($errno, $errstr, $errfile, $errline);

但是要注意这些全局函数需要在代码段的前面已经定义过再注册。

数据表和Model类的ActiveRecord映射

初次使用yii2的ActivceRecord类觉得好方便,只需要定义其字段同名属性再调用save方法就OK了(好神奇啊),它是怎么实现的呢,看了下源码,明白了其大致实现过程(基类)。

1. 使用‘describe table_name' 查询语句;
2. 分析查询结果:对每一个字段,有Field(字段名)、Type(数据类型)、Null(是否为空)、Key(索引信息,‘PRI'表示为主键)、Default(默认值)、Extra(附加信息,如auto_increment)
3. 通过判断其主键($row['KEY'] == 'PRI')信息,保存时看是否有主键信息,若存在,则为更新;不存在,则插入。
4. 另外,解析出来的字段信息还有更多妙用~~

相关推荐:

thinkPHP框架实现图像裁剪、缩放、加水印的方法

위 내용은 자신만의 PHP MVC 프레임워크 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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