paket / fram
Minimal view framework
Installs: 81
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 2
Forks: 0
Open Issues: 1
pkg:composer/paket/fram
Requires
- php: >=7.2
- psr/container: ^1.0 || ^2.0
Requires (Dev)
- ext-pdo: *
- nikic/fast-route: ^1.3
- paket/bero: ^0.3.0
Suggests
- nikic/fast-route: For FastRouter
- paket/bero: For ContainerInterface
README
Fram (Swedish in front) is a view framework for PHP. Fram's goal is to fit everywhere, new projects, existing projects, side by side with other frameworks, thus Fram is a flexible framework that can fit into many scenarios.
Usage
final class HelloWorldView implements HtmlView
{
    public function render(Route $route)
    {
        ?>
        <!DOCTYPE html>
        <html>
        <head>
            <title>Hello, World!</title>
        </head>
        <body>
        <h1>Hello, World!</h1>
        </body>
        </html>
        <?php
    }
}
$container = new BeroContainer(new StrictBero()); // any PSR-11 ContainerInterface
$router = new FastRouter(simpleDispatcher(function (RouteCollector $r) {
    $r->addRoute('GET', '/', HelloWorldView::class);
}));
$fram = new Fram($container, $router, HtmlViewHandler::class);
$fram->run(function (Route $route, ?Throwable $throwable) {
    if (isset($throwable)) {
        return $route->withViewClass(ExceptionView::class, $throwable);
    }
    if ($route->hasEmptyView()) {
        return $route->withViewClass(NotFoundView::class);
    }
    return $route;
});
Installation
composer require paket/fram
Requirements
Requires PHP 7.2 or higher.
Examples
See /examples
Note: examples should be used as examples only and not as library code.
Running examples
php -S localhost:8888 -t examples/www
General
Fram is a view framework only, based on a route a view can be rendered. Other things necessary for a project like database handling, authentication, template rendering or logging, is not within the scope of Fram.
Fram's design is based around a few core interfaces, Fram ships with a few implementations of these interfaces, but it is up to the user of Fram to customize it based on project needs. Each core interface is designed to be as small as possible making the implementation trivial, thus extending Fram for projects needs should be possible within minutes.
Core parts
View
In Fram a View acts as both the controller and the view. To create a view you implement a specific View interface, thus one class is only one View. Grouping similar views should be done using namespaces.
View interfaces included in the framework
- DefaultView
- HtmlView
- JsonView
All view types implements an empty View interface.
By using different view types the framework can adapt its execution per view type. Any number of View interfaces can be created by the project for different scenarios.
How the View is rendered, the method name and it's parameters, is decided per view type, thus different view types can provide different input, e.g. like request and response objects.
EmptyView
EmptyView is a special view that represents the null View, i.e.
when the is no matching View for the current request.
ViewHandler
View handlers executes view types. Typical things that happens in a view handler is setting headers, specialized logging, handling buffers or executing a middleware pipe. It is the ViewHandler responsibility to provide the View with the input enforced by the view type. By switching to another ViewHandler you can change the behaviour of a View.
View handlers included in the framework
- DefaultViewHandler
- HtmlViewHandler
- JsonViewHandler
Route
Route is passed thru each part of Fram. Route knows the HTTP method, URI and what is the current View class. A Route is immutable and the only way to change the current View class is by cloning Route, this is done by calling withViewClass(). By changing View class a new Route is returned and can then be passed back upstreams to achieve internal redirects within Fram. Fram will redirect until the Route is not changed. Note that Route keeps track of the current View class name, not the class instance itself.
Router
Router matches HTTP method and HTTP uri with a View class and returns a Route with that View class set.
Routers included in the framework
- SimpleRouter- simple string matching against method and uri
- FastRouter- uses FastRoute for routing
Container
To instantiate a classes Fram uses PSR-11 ContainerInterface.
Suggested implementation and used in all documentation and examples is BeroContainer, BeroContainer is part of Bero project.
Fram
Fram is the engine that basically pumps the Route thru the framework.
By calling run() on Fram the framework initiates a request.
run() is called by providing a callback, this callback gives the project the necessary control over each Route change and can either approve or change Route.
The run() callback is  powerful mechanism the enables Fram to integrate better with existing code or other frameworks. By having the possibility to inspect each Route change, the callback can redirect or abort if needed. Typical scenarios are debugging, different execution path depending on environment, fallback to legacy code and authorization checks.
It is thru the run() callback how 404 pages are managed. Fram has no knowledge about how to handle the EmptyView case, but the run() callback can instruct Fram how to handle it by doing an internal redirect to the correct View.
Fram also catches exceptions that can happen either in Router or in the ViewHandler and thus the View. Exceptions are passed to the run() callback as an optional Throwable second parameter, callback can then redirect with a new Route if needed. Fram does not catch exceptions happening in the run() callback, those exceptions should be caught outside of run(). LogicExcpeption will always stop Fram.
If the run() callback returns null the execution of Fram stops.
Flow of Fram
- executes the Routerwith method and uri
- Routerreturns a new- Routewith a- Viewclass set
- calls run()callback withRouteand optionalThrowableif an exception happened
- run()callback returns the same- Route, a new- Routeor- nullto cancel
- matches the Route'sViewclass with registered view handlers
- instantiates ViewfromViewclass name by usingContainerInterface
- executes matched ViewHandlerwithRouteandView
- calls 3 if returning RoutefromViewHandlerhas changed or on exception
Error handling
Fram does not register any exception, error or shutdown handlers. That is something that needs to be configured outside of Fram.
Customization
Most customization should be done by implementing your own ViewHandler. The ViewHandler is a good place for doing user authentication/authorization, buffered output, response header management, custom logging, PSR-7 request & response objects or calling PSR-15 middlewares.
FAQ
- Should my view class directly implement the Viewinterface?- No, the top Viewinterface is an abstract interface and should never be implemented, theViewinterface is used as type to represent all view interfaces. You should implement a view interface that in turn extends the topViewinterface.
 
- No, the top 
- Can a view interface extend another view interface?
- Only if the other interface is an abstract interface like the top Viewinterface. Assume we have view interfaceAthat extends theViewinterface, thenBinterface extendsAinterface. BothAandBhas their own view handlers so view implementations ofAandBcan be rendered, however a view class ofBcan then match both the view handler that is registered forAandB, this can lead to unexpected results and Fram does not handle this case.
 
- Only if the other interface is an abstract interface like the top 
- How to implement CSRF token validation?
- Implement a view handler that inspects all incoming forms and validate the token supplied in the form data. Look in the examples folder for a working example, specifically the FormBackendHandlerclass.
 
- Implement a view handler that inspects all incoming forms and validate the token supplied in the form data. Look in the examples folder for a working example, specifically the 
- How to implement output buffering?
- Implement a view handler that starts output buffering before rendering view and then either flushes (sends) or cleans (drops) output buffer based on condition, e.g. if we return a new route from our view we clean our output buffer, if not we flush it.
final class BufferedHtmlHandler implements ViewHandler { public function handle(Route $route, View $view): Route { ob_start(); /** @var $view HtmlView */ $newRoute = $view->render($route); if ($newRoute !== null && $newRoute !== $route) { ob_end_clean(); return $newRoute; } ob_end_flush(); return $route; } public function getViewInterface(): string { return HtmlView::class; } }
 
- Implement a view handler that starts output buffering before rendering view and then either flushes (sends) or cleans (drops) output buffer based on condition, e.g. if we return a new route from our view we clean our output buffer, if not we flush it.
- How to do request header based routing?
- Based on a request header, like Acceptor a custom one likeX-Versionyou could pick different routers by implementing a wrapperRouter, e.g.final class RequestHeaderRouter implements Router { /** @var Router */ private $first; /** @var Router */ private $second; public function __construct(Router $first, Router $second) { $this->first = $first; $this->second = $second; } public function route(string $method, string $uri): Route { if (/* check request header for condition to be true */) { return $this->first->route($method, $uri); } return $this->second->route($method, $uri); } }
 
- Based on a request header, like 
License
Fram is released under the MIT License. See the bundled file LICENSE.txt.