php-mvc-project / php-mvc
PHP 中 MVC(模型-视图-控制器)架构模式的实现。
Requires
- php: >=7.0
Requires (Dev)
- phpunit/phpunit: ^7
This package is not auto-updated.
Last update: 2024-09-29 04:43:21 UTC
README
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
后缀结尾。例如:HomeController
、AccountController
、TestController
等。
结构
您的项目必须实现严格的结构
. ├── 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
并遵守以下规则
-
文件夹名称必须为小写。
-
视图文件名必须为小写。
-
控制器文件名必须使用驼峰式命名,以大写字母开头。名称必须以
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