ホームページ  >  記事  >  バックエンド開発  >  独自の 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 構造は、複数回書き込まれた場合に 1 回だけ実行されることを示します。

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 プロジェクトでは、クラスが多い場合、クラス名が重複すると混乱が生じますし、同じフォルダー内に同じ名前のファイルが存在できないため、このとき、名前空間とフォルダーペアになって登場しました。私の理解では、フォルダーは 1 つずつのボックスであり、名前空間はラベルのようなもので、ボックスはラベルに対応します。クラスを定義するときは、さまざまなクラスを異なるボックスに入れ、対応するラベルを付けます。クラスを自動的にロードする場合、ラベル (名前空間) に基づいて対応するボックス (フォルダー) を簡単に見つけ、対応するクラス ファイルを見つけることができます。

クラスの自動ロードに関しては、__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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。