#> RESTKHZ _

休止千鹤 | 我依旧是一名平凡的学生

自写PHP MVC框架(1)

  休止千鹤  |    17/10/2020

缘起

((这里嫌啰嗦可以不看
起初我想自己写一个博客系统。讲道理博客系统不算复杂,但是我还是打算使用框架,并且,自己编写。
那么,为什么不用现成框架?PHP的La它不好用吗?
首先,框架的概念,框架的意义,解决的痛点,我并不了解。甚至一些php的功能,OOP的思想对于我来说都很模糊。直接使用框架恐怕只是学习一种工具,而不是思想。工具每年都在更迭翻新,花费大量时间学习框架只是被工具绑架而已。而我目前并不是程序员,没有那么多急迫的业务需要,学一个框架很快也会因为工具更迭,被淘汰而失去价值。
所以不如动手中学习一下思想。

单做一个框架,收获不大。一定要让它做点实用的事情。再通过实际的业务回首反观框架,才能了解到我需要什么,哪里存在问题。
所以《从零开始自己动手写PHP MVC框架》只是一个开头。以后还是要用自己框架做点什么,否则依旧不知道框架应该是什么样子。(虽然以后这类文章我未必会写)
多学习一点没坏处(正好闲着)
琢磨起了这个框架的设计。毕竟是我第一个写了能用起来的框架,设计并不周到,很多地方写写改改,功能也有点缺。目前还想整一个ORM出来。整个技术上来说也并没有什么黑科技,中规中矩,代码丑陋,补丁遍地。这玩意我就叫它Ugly了。(阿格里!)

本文会围绕着这个框架,跟着一个请求处理的顺序观察框架,并且精简一部分代码。

概览

MVC是什么我就不再多介绍了。不理解这是什么你也不会到这里来。

设计上来说,我希望:

每个模块之间清晰,配置灵活.方便日后扩展和替换. 因此存在命名空间Ugly( core namespace )和Controller, Model, Widget(app namespace)

核心命名空间在以后框架使用中以后不再进行编辑, 只编辑应用命名空间.

M:

目前还是通过模型基类的函数进行sql拼接,pdo预处理(为了安全)。但是这里用了一个”驱动”概念:

模型基类只接收pdo对象,不在乎你用的什么数据库. 所以可以自己编写不同数据库的pdo,初始化并提供给模型基类, 以后可以可以在配置中进行替换。

每次初始运行都会获取被调用的模型表名。没有ORM多多少少还是麻烦。
比如目前根据用户id读取用户名:

$r = $this  ->select('name')
                 ->where('id', $id)
                 ->exec()
                 ->fetch();

目前用的是sqlite

V:

偷懒,用了Twig。后面Xdebug性能分析发现它开销是大头。这没啥好说的了。

C:

中规中矩嘛……加了一些读json,发送json的功能而已。我试着增加一些插件功能,在每次控制器运行/销毁的时候运行,可以hook一些东西。但是后来因为觉得鸡肋,有时候还出问题,就删了。

路由:

自动的,通过配置文件的classmap,不同命名空间对应不同路径,方便自动加载器寻找路径。
个人认为路由是我写的很失败的地方。

性能:

这种小框架讨论性能没有意义……
只能告诉你不慢。

安全:

SQLi:设计时必须考虑到的问题,使用PDO预处理解决SQL注入问题。

XSS: 对于用户输入做了转义,考虑到有时候可能会对正常业务有影响,没有全局使用。这有存在漏网之鱼的可能。

htmlspecialchars($value,ENT_QUOTES);

这个函数不用ENT_QUOTES会翻车。可以bypass。
并且在输出的时候开启twig的autoescape,转义特殊字符。尽可能消灭漏网之鱼。

目录结构

.
├── data 数据库存放
│ └── sqlite.db
├── public 对外公开的web目录
│ ├── assets 静态资源
│ └── index.php 入口文件
└── ugpf 框架核心
├── cache twig缓存
├── config 配置文件目录
│ └── config.php 配置文件
├── controller 应用的控制器都在这
│ └── Index.class.php (应用首页控制器)
├── core 框架的核心
│ ├── Controller.class.php
│ ├── ErrorHandler.class.php
│ ├── Model.class.php
│ ├── Router.class.php
│ ├── SqliteDriver.class.php
│ └── Ugly.class.php
├── model 应用的模型层都在这
│ └── IndexModel.class.php(应用首页的模型)
├── vendor 第三方玩意
│ ├── autoload.php
│ ├── composer
│ ├── symfony
│ └── twig
└── view 应用的视图模板
├── 404.html
├── dbdebug.html
└── error.html

这是这个框架早期的目录结构。

入口文件

<?php
/**
* Ugly PHP blog 0.1.0
* This is the Entrance File.
* We defined consts and included all the things we need.
* check, and run.
*/

//Please set DEV to 0 when it's running in production environment
define('DEV', 1);

define('DIR',__DIR__.'/');

define('UGLY_PATH',DIR.'../ugpf/');

$config = (file_exists(UGLY_PATH.'config/config.php')) ? require UGLY_PATH.'config/config.php' : die('Configuration file is missing. x_x');

require UGLY_PATH.'core/Ugly.class.php';

Ugly\Ugly::run($config);

在这里我们定义了它运行的环境,根目录,包含配置文件并且加载Ugly.class.php。
并且启动整个引导过程(Ugly::run)
如此定义开发环境生产环境其实并不是个好习惯。但是早期版本作为学习也就这样用了。

配置文件

<?php

return [
  'DEFAULT_ROUTE' => [
    'CONTROLLER' => 'Index' ,
    'ACTION'    =>  'index',
  ],

  'CLASS_MAP' => [
    //Here is core-classes path
    'Ugly\\' => UGLY_PATH.'core/',
    'ErrorHandler\\'  => UGLY_PATH.'core/',
    //APP classes path
    'Controller\\' => UGLY_PATH.'controller/',
    'Model\\' => UGLY_PATH.'model/',
    'Widget\\' => UGLY_PATH.'widget/',

  ],

  'CACHE_PATH' => UGLY_PATH.'cache/',
  'VIEW'  => UGLY_PATH.'view/',
  //DB_config
  'DB' => [
      'DRIVER'  => 'SqliteDriver',
      'PATH' => UGLY_PATH.'../data/',
      'NAME' =>  'sqlite.db',
  ]

];

配置文件定义了命名空间和路径的映射,数据库配置,twig配置,默认的控制器和行为。
这块CLASS_MAP是给UGLY自动加载器使用的,待会就能看到。

Ugly.class.php

<?php
namespace Ugly;

 /**
  * Ugly core class
  */
 class Ugly
 {

   public static $controller;
   public static $action;
   public static $param;
   public static $config;
   public static $view;
   public static $ErrorHandler;
   /**
   * Run() is a bootloader 它只是一个启动引导器
   */
   public static function run($config)
   {
       //load Configurations 加载配置
       self::$config = $config;

       //Register classloader 注册autoload函数
       spl_autoload_register('self::loadClass');

       //Load View engine 加载视图引擎twig
       self::_initView();

       //Register Error Handler.注册错误处理
       self::$ErrorHandler = new \ErrorHandler\ErrorHandler;
       self::$ErrorHandler->run();

       //Run the Router, and LOS! 开始运行路由
       Router::start();

   }

   /**
    * For auto loading
    * @param  [type] $dirtyClassName [description]
    * @return [type]                 [description]
    */
   public static function loadClass($dirtyClassName)
   {
       //分离出类名
       $class = substr(strrchr($dirtyClassName, '\\'), 1); 
       //分离出调用的命名空间
       $nameSpace =  substr($dirtyClassName, 0, strrpos($dirtyClassName, $class));
       //通过配置文件,找到路径并require。没有找到直接调用错误处理显示404
       $file = self::$config['CLASS_MAP'][$nameSpace]. '/'. $class .'.class.php';
       if(!file_exists($file)){self::$ErrorHandler->E404($class);}
       require $file;
   }

   /**
    * This loading method is awful. 加载视图。
    * Twig used in ErrorHandler and controllers
    */
   protected static function _initView()
   {
       require UGLY_PATH.'/vendor/autoload.php';
       $loader = new \Twig_Loader_Filesystem(self::$config['VIEW']);
       self::$view = new \Twig_Environment($loader, array(
          'cache' => self::$config['CACHE_PATH'],
          'debug' => true
        ));

   }
 }

这里我们把一个框架的引导过程全部放进了run(),使整个运行过程清晰起来。
另外的一些重要全局内容,比如请求的控制器,行为,参数,错误处理器,全部成为Ugly的一个属性。


到此为止,一个框架的引导部分就完成了,接下来在路由中,一个请求的处理才刚刚开始。

下一篇我们会讲路由分发和控制器基类的设计: 自写PHP MVC框架(2)


Views:

 Comments


(no comments...maybe you can be the first?)