PHP MVC
, (*1)
The best implementation of the Model-View-Controller architectural pattern in PHP!, (*2)
Features
- Templates
- Routing
- Filters
- Cache
- Validation
- Data annotation
- Security
Requirements
Installation
$ composer require php-mvc-project/php-mvc
Server Configuration
The server must send the entire request to the ./index.php
file., (*3)
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;
}
Basic Usage
Create the following structure in the project root directory:, (*4)
.
โโโ 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
ignore('content/{*file}');
// default route
$routes->add('default', '{controller=Home}/{action=index}/{id?}');
});
// build
AppBuilder::build();
```
> **IMPORTANT**: You must use namespaces in your application.
> Be sure to specify the root namespace of your application using the `AppBuilder::useNamespace(string)` function.
### ./controllers/HomeController.php
```php
content('Hello, world!');
// create to the ./view/home/index.php
// and use view function to return this view:
// return $this->view();
}
}
```
> **IMPORTANT**: The names of all controller classes must end with the `Controller` suffix.
> For example: `HomeController`, `AccountController`, `TestController` etc.
## Structure
Your projects must implements the strict structure:
```shell
.
โโโ 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
```
And adhere to the following rules:
1. Folder names must be in lowercase.
2. The views filenames must be in lowercase.
3. The file names of the controllers must be specified in the camel style, with a capital letter.
The names must end with the `Controller` suffix. For example: `HomeController.php`.
## Models
The model is just classes. You can create any classes, with any structure.
It is recommended to adhere to the rule: the simpler, the better.
Using the static class `Model`, you can add metadata to a model in the constructor of the controller.
```php
view($model);
}
}
```
## Views
The views files contain markup.
Markup can be complete or partial.
Using the `PhpMvc\Html` class, you can create markup for HTML elements or output some views within other views.
For example:
```php
<html>
<head>
<title>=Html::getTitle('Hello, world!')?></title>
</head>
<body>
Hello, world!
=Html::actionLink('Go to second view', 'second'))?><br />
=Html::textBox('anyname')?><br />
=Html::checkbox('rememberMe')?><br /><br />
</body>
</html>
Use the helper class PhpMvc\View
to customize the behavior of the view:, (*5)
=isset($model) ? $model->anyProperty : 'empty'?>
Controllers
The controller classes names must match the controllers filenames.
For example: file name is TestController.php
, class name is TestController
., (*6)
Each controller class must inherit from the PhpMvc\Controller
class., (*7)
The controller classes must contain action methods., (*8)
The action names must match filenames of views.
For example: view file is index.php
, action name is index
., (*9)
All methods of actions must have the modifier public., (*10)
The names of the action methods must not start with the underscore (_)., (*11)
class HomeController extends PhpMvc\Controller {
public function index() {
return $this->view();
}
public function hello() {
return $this->view();
}
public function other() {
return $this->view();
}
}
Each action can take any number of parameters., (*12)
The parameters can be received from the query string, or from the POST data., (*13)
The following example shows the output of parameters retrieved from the query string:, (*14)
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
Below is an example of obtaining a model sent by the POST method:, (*15)
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
Methods of action can return different results, in addition to views., (*16)
You can use the ready-made methods of the base class of the controller to output the data in the required format:, (*17)
$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]]])
Instead of the presented methods, you can independently create instances of the desired results and return them:, (*18)
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;
}
}
All result classes implement the ActionResult
interface.
You can create your own result classes!, (*19)
Filters
Filters allow you to add handlers before and after the action.
And also handle errors of the action execution., (*20)
The filters must be in the ./Filters
folder., (*21)
Each filter must be inherited from the PhpMvc\ActionFilter
class., (*22)
Filters can be global, or work at the level of an individual controller, or action., (*23)
Filters for specific controller or action can be set in the controller's constructor:, (*24)
./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);
}
}
Routing
You can set routing rules using the AppBuilder::routes()
function, which expects the function as a parameter.
When called, an instance of RouteProvider
will be passed to the function., (*25)
The add(string $name, string $template[, array $defaults = null[, array $constraints = null]])
method allows you to add a routing rule., (*26)
The ignore(string $template[, $constraints = null])
method allows you to add an ignore rule., (*27)
AppBuilder::routes(function(RouteProvider $routes) {
$routes->add('default', '{controller=Home}/{action=index}/{id?}');
});
The names of the routing rules must be unique., (*28)
The higher the rule in the list (the earlier the rule was added), the higher the priority in the search for a match., (*29)
In templates you can use any valid characters in the URL., (*30)
Use curly braces to denote the elements of the route., (*31)
Each element must contain a name., (*32)
A name can point to a controller, action, or any parameter expected by the action method., (*33)
For example, template is: {controller}/{action}/{yyyy}-{mm}-{dd}
., (*34)
Action is:, (*35)
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
After the element name, you can specify a default value in the template.
The default value is specified using the equals sign., (*36)
For example: {controller=home}/{action=index}
- default controller is HomeController
, default action is index()
.
The default values will be used if the address does not have values for the specified path elements.
Simply put, for requests /home/index
, /home
and /
will produce the same result with this template., (*37)
If the path element is optional, then the question (?) symbol is followed by the name.
For example: {controller=home}/{action=index}/{id?}
- id is optional,
{controller=home}/{action=index}/{id}
- id is required., (*38)
Route providers must implement the RouteProvider
interface.
The default is DefaultRouteProvider
. If necessary, you can create your own route provider.
Use the AppBuilder::useRouter(RouteProvider $routeProvider)
method to change the route provider., (*39)
Caching
To use caching, you must call the AppBuilder::useCache(CacheProvider $cacheProvider)
method, which should be passed to the cache provider instance., (*40)
The cache provider must implement the CacheProvider
interface., (*41)
You can use the ready-made FileCacheProvider
, which performs caching in the file system., (*42)
AppBuilder::useCache(new FileCacheProvider());
You can access the cache via an instance of HttpContextBase
., (*43)
For example, in the controller:, (*44)
$cache = $this->getHttpContext()->getCache();
$cache->add('test', 'hello, world!');
var_dump($cache->get('test'));
In the view:, (*45)
$cache = View::getHttpContext()->getCache();
$cache->add('test', 'hello, world!');
var_dump($cache->get('test'));
For output caching, you can use the static OutputCache
class., (*46)
Caching rules can be specified for both the controller and for an each action., (*47)
Cache rules should be specified in the constructor of the controller., (*48)
<?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);
}
}
License
The MIT License (MIT), (*49)
Copyright ยฉ 2018, @meet-aleksey, (*50)