mudge/engine

在一个银行假日周末编写的微型PHP网络框架。

dev-master 2018-01-01 15:59 UTC

This package is not auto-updated.

Last update: 2024-09-29 04:48:55 UTC


README

在一个银行假日周末编写的微型PHP网络框架。

当前版本:未发布
支持的PHP版本 7.1, 7.2

安装

$ composer create-project mudge/engine-skeleton:dev-master my-project
$ cd my-project
$ php -S localhost:8080 -t public

设计

很容易把HTTP请求响应周期想象成一个纯函数 f,它接受一些请求(可能是原始HTTP字符串或某些包装对象),并产生一个单独的响应(再次,可能是原始字符串或包装对象)

+-------------------+     +---+     +----------------------------------------+
| GET / HTTP/2      | --> | f | --> | HTTP/2 200                             |
| Host: example.com |     +---+     | Content-Type: text/html; charset=utf-8 |
+-------------------+               |                                        |
       Request                      | <!DOCTYPE html>...                     |
                                    +----------------------------------------+
                                                     Response

然而,这种思维模型并不完全正确,因为并不是所有的请求都会一次性产生一个单独的响应。例如,响应可以分块发送:可能是一个请求立即接收到一些响应头,然后以部分的形式返回正文。还有可能响应永远不会结束,通过连续流式传输(例如,想想Twitter流式API)。

因此,Engine采用了另一种方法:将典型的请求响应周期建模为一个函数,该函数接受两个请求和响应对象,后者是一种开放文件句柄,允许用户在处理过程中的任何时刻发送头部信息等。这意味着响应完全通过副作用创建,这是一种更混乱的思考方式,但更接近现实。

+-------------------+     +---+
| GET / HTTP/2      | --> |   |
| Host: example.com |     |   |
+-------------------+     |   |
       Request            | f |
                          |   |
+-------------------+     |   |
|                   | --> |   |
+-------------------+     +---+
       Response

Engine使用MVC模式,但控制器是初始化了RequestResponse和日志记录器的对象。

Request是一个值对象,提供对请求URI、方法、任何GETPOST参数以及任何Cookies或会话数据的访问。

Response对象允许您通过各种方法向请求发送数据

  • redirect(string $location): void:发送重定向头部;
  • header(string $header): void:发送任意头部;
  • render(string $template, array $variables = []): void:使用给定的变量渲染Twig模板;
  • notFound(): void:返回一个名为404.html的模板的404 Not Found响应;
  • forbidden(): void:返回一个名为403.html的模板的403 Forbidden响应;
  • methodNotAllowed(): void:返回一个名为405.html的模板的405 Method Not Allowed响应。

请注意,所有方法都返回void,因为它们完全通过副作用工作:即向客户端发送数据。

因此,控制器操作是控制器实例上的典型方法,可以访问requestresponselogger(有关示例,请参阅用法)。由于这些都是普通的PHP对象,因此可以在控制器之间共享行为,所有常规技术(如组合和继承)都可用。

至于控制器的实例化和动作调用:这是由Router负责的,它包含HTTP方法和路径到控制器类和动作的映射,例如

+-------------+     +----------------------+
| GET /       | --> | HomeController#index |
+-------------+     +----------------------+

+-------------+     +---------------------------+
| POST /login | --> | SessionsController#create |
+-------------+     +---------------------------+

Engine Skeleton项目将创建一个public/index.php,它设置一个新的Router,用默认路由填充它,创建一个Request和一个空的Response,并根据需要进行路由。

            +-------------------+     +-----------------------------+     +------------------+
            | GET / HTTP/2      | --> | GET / -> HomepageController | --> |                  |
 __O__  --> | Host: example.com |     |          index              |     |      index       |
   |        +-------------------+     +-----------------------------+     |                  |
   |              Request                        Router                   |                  |
   |                                                                      |                  |
   |                                                       +--------+     |                  |
   |    <------------------------------------------------- |        | --> |                  |
  / \                                                      |        | <-- |                  |
 Client                                                    +--------+     +------------------+
                                                            Response       HomepageController

用法

使用引擎骨架来创建一个新的引擎Web应用程序

$ composer create-project mudge/engine-skeleton:dev-master my-project

这将生成一个在my-project中的项目布局(不显示vendor目录的内容)

.
├── README.md
├── composer.json
├── composer.lock
├── log
├── public
│   ├── css
│   │   └── app.css
│   └── index.php
├── src
│   └── HomepageController.php
├── templates
│   ├── 404.html
│   ├── base.html
│   └── index.html
├── tests
│   └── HomepageControllerTest.php
├── tmp
└── vendor

然后您可以启动开发服务器

$ cd my-project
$ php -S localhost:8080 -t public

您可以在Web浏览器中访问https://:8080并看到来自Engine的欢迎页面

您可以运行自动化的测试

$ ./vendor/bin/phpunit

控制器

默认情况下,您的项目将期望src目录中的所有内容都在App命名空间中。您可以通过从Engine\Controller继承来实现自己的控制器

<?php
declare(strict_types=1);

namespace App;

use Engine\Controller;

/* Controllers are just plain classes that will be instantiated with a Request, Response and logger. */
final class HomepageController extends Controller
{
    public function index(): void
    {
        /* Prevent CSRF attacks using tokens in form submissions. */
        $this->verifyCsrfToken();

        /* Query arguments or post data can be accessed through Parameters. */
        $this->params->fetch('name');

        /* The response object can be used to render templates and send them to the user. */
        $this->response->render('index.html', ['csrf_token' => $this->session->crsfToken()]);

        /* Or, use the convenience methods on the controller itself. */
        $this->renderForm('index.html');

        /* Use convenience methods for common responses. */
        $this->response->notFound();
        $this->response->forbidden();
        $this->response->redirect('http://www.example.com');
    }
}

路由

您Web应用程序的主要入口是public/index.php,它会在每次请求时执行。项目骨架已经为您生成,但您可以按需编辑。

您几乎肯定想要编辑路由,将请求路由到您自己的控制器操作,但您也可以在这里配置日志和模板缓存。

<?php
declare(strict_types=1);

/**
 * Ensure all encoding is in UTF-8.
 */
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');

require_once __DIR__ . '/../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Engine\{Router, Request, Response};

/**
 * Set up logging.
 *
 * By default, this will log INFO-level messages to ../log/app.log
 */
$logger = new Logger('app');
$logger->pushHandler(new StreamHandler(__DIR__ . '/../log/app.log', Logger::INFO));

/**
 * Set up templating.
 *
 * By default, this will load templates from ../templates and cache them
 * to ../tmp (you may want to disable the cache during development)
 */
$loader = new \Twig_Loader_Filesystem(__DIR__ . '/../templates');
$twig = new \Twig_Environment($loader, ['cache' => __DIR__ . '/../tmp']);

/**
 * Set up the request and response.
 */
$request = Request::fromGlobals();
$response = new Response($twig, $logger);

/**
 * Set up the router.
 *
 * Add any routes of your own here, e.g.
 *
 *     $router->get('/login', 'App\SessionsController', 'new');
 *     $router->post('/login', 'App\SessionsController', 'create');
 */
$router = new Router($logger);
$router->root('App\HomepageController', 'index');

/**
 * Serve the request with the response.
 */
$router->route($request, $response);

为什么是"Engine?"

因为我15年前编写过的各种博客系统都不可避免地被称为"Engine"。

许可证

版权所有 © 2017 Paul Mucur

在MIT许可证下分发。