This is a very thin framework for PSR-15 PHP application using MVC.
Currently, it offers the following functionality only:
- Application shell
- View
- basic exception handling
It requires the following additional modules to work:
- PSR-7 request / response implementation
- PSR-17 factory implementation
Moreover, you are suggested to add various libraries to improve the development experience.
- Dependency injector
- Session handling
- Cookie handling
In order to create a PHP application using the framework, you need to do the following:
-
Subclass the provided
Application
class to handle your application logic for each entry point. The dependencies should be injected to the constructor of your concrete Application, and it receives aServerRequestInterface
and returns aResponseInterface
when run. -
Create views by writing templates and subclassing the provided abstract
View
(for non-template views),Template
(for templates in any language) ,PhpTemplate
(for PHP templates) orStaticTemplate
(for static templates) class. The view data for PHP templates should be declared as protected members in your concrete view, which is accessible in the template by$this->member
.Helper functions are provided for easy output escaping:
html
xml
js
json
css
url
If you are providing JSON API only, you (obviously) don't need to write any views.
-
Create the entry point to start the application, which is accessible under the document root. The entry point should:
require
the composer autoloader.- Set up error and exception handlers (optional).
- Create the
Application
andServerRequestInterface
objects. - Run the application.
- Output the response.
Please refer to miklcct/thin_php_app_demo
repository for how this framework works.
In this framework, application and controller refer to the same thing - something that accepts a request and returns a response.
Just implement your controller code in the provided Application
class and you are good to go.
No routing functionality is built-in ecause I don't want to make everything go through a single entry point. This would make the entry controller bloated because all dependencies used in every route have to be injected into the main controller. I don't want to make a container mandatory and injecting container is also a bad idea.
Instead, create a PHP file on each entry point of the application, subclass the provided Application
class and do the
work specific to that route.
If you need to use SEO friendly URL, please use path info and web server rewrite. For example, if you want a specific
blog page accessible under http://example.com/article/2018/01/24/hello-world
, please create article.php
under your
document root, set up your web server to rewrite /article to /article.php
Alternatively, you can install a router as a middleware in the application.
It is very easy to install PSR-15 middlewares in the framework.
If you are using the provided Application
class, just register it in getMiddlewares()
method.
Remember to call parent::getMiddlewares()
inside the overridden method to inherit the previous middlewares.
It is also easy to install global middlewares. Just extend the provided Application
class, register your global
middlewares, and implement your controllers on top of it.
It is very easy to install middlewares on top of an existing PSR-15 application from another framework
even if you don't use the provided Application
class. MiddlewareApplication
class is provided for such purpose.
Views are defined as content with a content type. You need to implement __toString()
(for string), render()
(for StreamInterface
) and getContentType()
. If you implement __toString
you can use the provided StringToStream
trait to fill in render()
, conversely, if you implement render()
you can use the provided StreamToString
to fill in
__toString()
.
You can use the built-in StaticTemplate
(for static page) or PhpTemplate
(for PHP templates). Just
implement getPathToTemplate()
method.
You need to install the template engine (e.g. Smarty, Twig, etc.), extend the provided Template
class
and implement render()
and __toString()
methods to call the template engine.
PHP templates are run in the context of your View object. You are recommend to access view data using protected
fields and members (private
will not work because it is rendered in a base class method). and add a docblock
/* @var YourConcreteView $this */
near the beginning of your PHP templates such that IDE analysis will work.
Other template engines may have their own method to pass data into templates. You are recommended to declare exactly what data is expected in the view constructor, or use a factory object to produce views with correct data.
There are two helper classes provided: ExceptionErrorHandler
which will convert PHP error into ErrorException
depending on your error_reporting
setting, and ResponseFactoryExceptionHandler
which will output responses from a
factory when an exception is uncaught. Objects of these classes can be passed directly to set_error_handler()
and set_exception_handler()
respectively.
You can also register a middleware to handle exceptions from your application.
All library codes are organised into the appropriate subfolder under src
folder, and properly namespaced using PSR-4, even including constants and functions.
You MUST adhere to the following rules when making pull requests:
-
Follow the existing code style for existing files, but feel free to use any style for new files.
-
Place all code into a subfolder under
src
folder and namespace them. No global code, including global constants and functions are allowed. -
All PHP code MUST have strict type checking (
declare(strict_types = 1);
) enabled. -
No
eval
is allowed. This prohibition includes functions witheval
-behaviour e.g.preg_replace()
withe
modifier,create_function()
and the backtick operator. -
No
goto
is allowed. -
No undeclared properties or methods are allowed. This prohibition includes the use of magic, i.e. the following magic methods are not allowed:
__call()
__callStatic()
__get()
__set()
__isset()
__unset()
The use of undeclared fields in
stdClass
object to represent a generic data object is allowed as an exception. -
Use of a string for indirect variable, property and method access is not allowed. For example, the use of following syntaxes are not allowed, where
$foo
is a string:$$foo
(indirect variable)$object->$foo
(indirect property)$object->$foo()
(indirect method)
However, the use of a string for indirect function call is allowed, provided that the string comes from an expression declared, annotated or deduced as a
callable
, for example:function test(string $foo, callable $bar) { $foo(); // not allowed $bar(); // allowed }
Only proven callables are allowed to be used in
callable
expression, which include:- anonymous functions
- string literal (representing a free standing function or static class method)
- array containing an object and a string literal (representing a method inside an object)
- objects with
__invoke()
method
-
Alternative control syntax is only allowed in
.phtml
templates and not allowed in regular.php
files containing application logic.
If existing code are found to violate the above rules, please open a bug report.