PHP 中 MVC(模型-视图-控制器)架构模式的实现。

v1.2.0 2018-05-29 19:21 UTC

This package is not auto-updated.

Last update: 2024-09-29 04:43:21 UTC


README

PHP from Packagist License GitHub release Packagist

PHP 中 模型-视图-控制器 架构模式的最佳实现!

特性

  • 模板
  • 路由
  • 过滤器
  • 缓存
  • 验证
  • 数据注释
  • 安全

需求

  • PHP 7.x

安装

$ composer require php-mvc-project/php-mvc

服务器配置

服务器必须将整个请求发送到 ./index.php 文件。

Apache

<IfModule mod_rewrite.c>
  RewriteEngine On

  # redirect /index.php to /
  RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.*index\.php
  RewriteRule ^index.php/?(.*)$ $1 [R=301,L]

  # process all requests through index.php, except for actually existing files
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php/$1?%{QUERY_STRING} [QSA,L]
</IfModule>

nginx

location / {
  try_files $uri $uri/ /index.php?$args;
}

基本用法

在项目根目录中创建以下结构

.
├── controllers           # controllers folder
├─── HomeController.php   # class of the home controller
├─── *Controller.php      # classes of others controllers
├── models                # models folder
├── views                 # views folder
├─── home                 # views folder of the home controller
├─── ...                  # folders for other controllers
├─── shared               # shared views
└── index.php             # index file

./index.php

<?php
// use aoutoload (recommended)
require_once getcwd() . '/vendor/autoload.php';
// or include the required files
// require_once getcwd() . '/vendor/php-mvc-project/php-mvc/src/index.php';

// import the AppBuilder class
use PhpMvc\AppBuilder;

// be sure to specify the namespace of your application
AppBuilder::useNamespace('RootNamespaceOfYourApp');

// use session if you need
AppBuilder::useSession();

// routes
AppBuilder::routes(function($routes) {
    // skip all the paths that point to the folder /content
    $routes->ignore('content/{*file}');

    // default route
    $routes->add('default', '{controller=Home}/{action=index}/{id?}');
});

// build
AppBuilder::build();

重要:您必须在应用程序中使用命名空间。请确保使用 AppBuilder::useNamespace(string) 函数指定应用程序的根命名空间。

./controllers/HomeController.php

<?php
// make sure you add the Controllers child name to the root namespace of your application
namespace RootNamespaceOfYourApp\Controllers;

// import the base class of the controller
use PhpMvc\Controller;

// expand your class with the base controller class
class HomeController extends Controller {

    public function index() {
        // use the content function to return text content:
        return $this->content('Hello, world!');

        // create to the ./view/home/index.php
        // and use view function to return this view:
        // return $this->view();
    }

}

重要:所有控制器类的名称必须以 Controller 后缀结尾。例如:HomeControllerAccountControllerTestController 等。

结构

您的项目必须实现严格的结构

.
├── content               # static content (images, css, scripts, etc)
├─── ...                  # any files and folders
├── controllers           # controllers folder
├─── HomeController.php   # class of the home controller
├─── *Controller.php      # classes of others controllers
├── filters               # filters folder
├─── *.php                # classes of models
├── models                # models folder
├─── *.php                # classes of models
├── views                 # views folder
├─── ...                  # views for specific controllers
├─── shared               # shared views
├─── ...                  # common view files that are not associated with specific controllers
├── index.php             # index file
└── ...                   # any of your files and folders

并遵守以下规则

  1. 文件夹名称必须为小写。

  2. 视图文件名必须为小写。

  3. 控制器文件名必须使用驼峰式命名,以大写字母开头。名称必须以 Controller 后缀结尾。例如:HomeController.php

模型

模型只是类。您可以创建任何类,任何结构。

建议遵守规则:越简单越好。

使用静态类 Model,您可以在控制器的构造函数中为模型添加元数据。

<?php
namespace RootNamespaceOfYourApp\Controllers;

use PhpMvc\Controller;
use PhpMvc\Model;

class AccountController extends Controller {

    public function __construct() {
        Model::set('join', 'join');

        Model::required('join', 'username');
        Model::required('join', 'email');
        Model::required('join', 'password');
        Model::required('join', 'confirmPassword');

        Model::display('join', 'username', 'Username');
        Model::display('join', 'email', 'Email');
        Model::display('join', 'password', 'Password');
        Model::display('join', 'confirmPassword', 'Confirm password');

        Model::compare('join', 'confirmPassword', 'password');
        Model::validation('join', 'email', function($value) {
            return filter_var($value, \FILTER_VALIDATE_EMAIL);
        });
    }
    
    public function join($model) {
      if (!isset($model)) {
        $model = new ModelForExample();
      }

      return $this->view($model);
    }

}

视图

视图文件包含标记。标记可以是完整的或部分标记。

使用 PhpMvc\Html 类,您可以为 HTML 元素创建标记或输出其他视图中的某些视图。

例如

<?php 
use PhpMvc\Html;
?>
<html>
<head>
  <title><?=Html::getTitle('Hello, world!')?></title>
</head>
<body>
  <h1>Hello, world!</h1>
  <?=Html::actionLink('Go to second view', 'second'))?><br />
  <?=Html::textBox('anyname')?><br />
  <?=Html::checkbox('rememberMe')?><br /><br />

  <!--Render ./view/shared/footer.php-->
  <?php Html::render('footer'); ?>
</body>
</html>

使用辅助类 PhpMvc\View 来自定义视图的行为

<?php 
use PhpMvc\View;

// model intance (for the convenience of working with the IDE)
$model = new RootNamespaceOfYourApp\Models\AnyClass();

// set layout
View::setLayout('layout.php');

// set title
View::setTitle('Test title');

// iject model (from action)
View::injectModel($model);
?>

<h1><?=isset($model) ? $model->anyProperty : 'empty'?></h1>

<!--...-->

控制器

控制器类的名称必须与控制器文件名匹配。例如:文件名是 TestController.php,类名是 TestController

每个控制器类必须继承自 PhpMvc\Controller 类。

控制器类必须包含动作方法。

动作名称必须与视图文件名匹配。例如:视图文件是 index.php,动作名称是 index

所有动作方法都必须具有 public 修饰符。

动作方法的名称不能以下划线 (_) 开头。

class HomeController extends PhpMvc\Controller {

  public function index() {
    return $this->view();
  }

  public function hello() {
    return $this->view();
  }

  public function other() {
    return $this->view();
  }

}

每个动作可以接受任意数量的参数。

参数可以来自查询字符串,或来自 POST 数据。

以下示例显示了从查询字符串检索到的参数输出

class TestController extends PhpMvc\Controller {

  public function get($search = '', $page = 1, $limit = 10) {
    return $this->content(
      'search: ' . $search . chr(10) .
      'page: ' . $page . chr(10) .
      'limit: ' . $limit
    );
  }

}
Request:
GET /test/get?search=hello&page=123&limit=100

Response:
search: hello
page: 123
limit: 100

以下是从 POST 方法发送的模型获取示例

class AnyNameModelClass {

  public $search;

  public $page;

  public $limit;

}
class TestController extends PhpMvc\Controller {

  public function post($anyName) {
    return $this->content(
      'search: ' . $anyName->search . chr(10) .
      'page: ' . $anyName->page . chr(10) .
      'limit: ' . $anyName->limit
    );
  }

}
Request:
POST /test/post

search=hello&page=123&limit=100

Response:
search: hello
page: 123
limit: 100

动作方法可以返回不同的结果,除了视图。

您可以使用控制器基类中的现成方法以所需的格式输出数据

  • $this->view([string $viewOrModel = null[, object $model = null[, string $layout = null]]])
  • $this->json(mixed $data[, int $options = 0[, int $depth = 512]])
  • $this->file(string $path[, string $contentType = null[, string|bool $downloadName = null]])
  • $this->content(string $content[, string $contentType = 'text/plain'])
  • $this->statusCode(int $statusCode[, string $statusDescription = null])
  • $this->notFound([string $statusDescription = null])
  • $this->unauthorized([string $statusDescription = null])
  • $this->redirect(string $url)
  • $this->redirectPermanent(string $url)
  • $this->redirectPreserveMethod(string $url)
  • $this->redirectPermanentPreserveMethod(string $url)
  • 该方法重定向到指定的动作:$this->redirectToAction(string $actionName[, string $controllerName = null[, array $routeValues = null[, string $fragment = null]]])

您可以选择独立创建所需的结果实例并返回,而不是使用提供的这些方法。

class AnyController extends PhpMvc\Controller {

  public function example() {
    $view = new ViewResult();

    // set the name of an existing view file
    $view->viewFile = '~/view/abc/filename.php';
    // set the title
    $view->title = 'The view is created programmatically';
    // set the layout file name
    $view->layout = 'layout.php';

    // create model for view
    $model = new Example();

    $model->title = 'Hello, world!';
    $model->text = 'The model contains text.';
    $model->number = 42;

    // set model to the view
    $view->model = $model;

    // return view
    return $view;
  }

}

所有结果类都实现了 ActionResult 接口。您可以创建自己的结果类!

过滤器

过滤器允许您在动作之前和之后添加处理器,并处理动作执行中的错误。

过滤器必须位于 ./Filters 文件夹中。

每个过滤器都必须继承自 PhpMvc\ActionFilter 类。

过滤器可以是全局的,也可以在单个控制器或动作级别工作。

可以在控制器的构造函数中设置特定控制器或动作的过滤器。

./controllers/TestController.php

class TestController extends Controller {

  public function __construct() {
    // add filter TestFilter to all actions of the controller
    Filter::add('TestFilter');

    // add filter ExceptionToJson to error action
    Filter::add('error', 'ExceptionToJson');
  }

  public function index() {
      return $this->content('Hello, world!');
  }

  public function other() {
      return $this->view('~/views/home/other.php');
  }

  public function anyname() {
      return $this->view();
  }

  public function error() {
      throw new \Exception('Any error here!');
  }

}

./filters/TestFilter.php

<?php
namespace RootNamespaceOfYourApp\Filters;

use PhpMvc\ActionFilter;
use PhpMvc\ContentResult;

class TestFilter extends ActionFilter {

  // action executed handler
  public function actionExecuted($actionExecutedContext) {
    // check exception result
    if (($ex = $actionExecutedContext->getException()) === null) {
      // no exception, replace result
      $actionExecutedContext->setResult(new ContentResult('test'));
    }
    else {
      // set exception error message to result
      $actionExecutedContext->setResult(new ContentResult($ex->getMessage()));
    }
  }

}

./filters/ExceptionToJson.php

<?php
namespace RootNamespaceOfYourApp\Filters;

use PhpMvc\ActionFilter;
use PhpMvc\JsonResult;

class ExceptionToJson extends ActionFilter {

  // error handler
  public function exception($exceptionContext) {
    // set JsonResult to action result
    $exceptionContext->setResult(
      new JsonResult(
        array('message' => $exceptionContext->getException()->getMessage())
      )
    );
    // exception is handled
    $exceptionContext->setExceptionHandled(true);
  }

}

路由

您可以使用 AppBuilder::routes() 函数设置路由规则,该函数期望一个函数作为参数。当调用时,将向该函数传递一个 RouteProvider 的实例。

add(string $name, string $template[, array $defaults = null[, array $constraints = null]]) 方法允许您添加一个路由规则。

ignore(string $template[, $constraints = null]) 方法允许您添加一个忽略规则。

AppBuilder::routes(function(RouteProvider $routes) {
  $routes->add('default', '{controller=Home}/{action=index}/{id?}');
});

路由规则的名称必须是唯一的。

列表中规则的位置越高(即规则添加得越早),其优先级在搜索匹配项时也越高。

在模板中,您可以使用 URL 中有效的任何字符。

使用花括号表示路由的元素。

每个元素必须包含一个名称。

名称可以指向控制器、动作或动作方法期望的任何参数。

例如,模板是:{controller}/{action}/{yyyy}-{mm}-{dd}

动作是

public function index($yyyy, $mm, $dd) {
  return $this->content('Date: ' . $yyyy . '-' . $mm . '-' . $dd);
}
Request:
GET /home/index/2018-05-26

Response:
Date: 2018-05-26

在元素名称之后,您可以在模板中指定默认值。默认值使用等号指定。

例如:{controller=home}/{action=index} - 默认控制器是 HomeController,默认动作是 index()。如果地址中未指定路径元素值,将使用默认值。简单来说,对于请求 /home/index/home/,此模板将产生相同的结果。

如果路径元素是可选的,则问号 (?) 符号后跟名称。例如:{controller=home}/{action=index}/{id?} - id 是可选的,{controller=home}/{action=index}/{id} - id 是必需的。

路由提供者必须实现 RouteProvider 接口。默认是 DefaultRouteProvider。如果需要,您可以创建自己的路由提供者。使用 AppBuilder::useRouter(RouteProvider $routeProvider) 方法来更改路由提供者。

缓存

要使用缓存,您必须调用 AppBuilder::useCache(CacheProvider $cacheProvider) 方法,并将缓存提供者实例传递给它。

缓存提供者必须实现 CacheProvider 接口。

您可以使用现成的 FileCacheProvider,它在文件系统中执行缓存。

AppBuilder::useCache(new FileCacheProvider());

您可以通过 HttpContextBase 的实例访问缓存。

例如,在控制器中

$cache = $this->getHttpContext()->getCache();
$cache->add('test', 'hello, world!');
var_dump($cache->get('test'));

在视图中

$cache = View::getHttpContext()->getCache();
$cache->add('test', 'hello, world!');
var_dump($cache->get('test'));

对于输出缓存,您可以使用静态的 OutputCache 类。

可以为控制器和每个动作指定缓存规则。

应在控制器的构造函数中指定缓存规则。

<?php
namespace RootNamespaceOfYourApp\Controllers;

use PhpMvc\OutputCache;
use PhpMvc\OutputCacheLocation;
use PhpMvc\Controller;

class OutputCacheController extends Controller {

    public function __construct() {
        // caching for 10 seconds of the results of all actions of this controller
        OutputCache::setDuration('.', 10);
        
        // caching for 30 seconds of the results of the action of thirty
        OutputCache::setDuration('thirty', 30);
    }

    public function index($time) {
        return $this->content('time => ' . $time);
    }

    public function thirty($time) {
        return $this->content('time => ' . $time);
    }
 
}

许可

MIT 许可证 (MIT)

版权所有 © 2018,@meet-aleksey