Releases: nova-framework/framework
flexible random number lenght in CSRF helper
Moved $size = 32 to make genRandomNumber() more flexible in CSRF helper
Secure CSRF helper
Changing the random number code to a genRandomNumber() method, no change required in usage.
Small fix into Database\Model
Syntax correction.
Fix Core\Template
Small change into Core\Template to fix the work on some PHP versions
New Database API
This pull request introduce an improved Database API, in a Laravel-esque style, including a QueryBuilder and a simple but powerful Model, everything living in the namespace '\Database' from isolation reasons.
To note that this new Database API doesn't replace any of existent classes, the actual Core\Model and Helpers\Database remaining untouched. Then an end-user can choose which Database API use in his application, with the only condition to not use both of them simultaneous, thing which will duplicate the Database connections.
Then new API is structured on three levels.
First one of the levels, being a Database Connection, built on top of PDO, which is available both via getting an instance:
use Database\Connection;
$db = Connnection::getInstance();
$prefix = $db->getTablePrefix();
$data = $db->select("SELECT * FROM {$prefix}users");
and via a Facade which permit commands like:
use DB;
$prefix = DB::getTablePrefix();
$data = DB::select("SELECT * FROM {$prefix}users");
Commands for insert/update/delete are available.
To note that the Database\Connection fetch the data into objects or array of objects.
The second level is represented by a very simple but powerful QueryBuilder, which permits commands like:
use DB;
$users = DB::table('users')->get();
$users = DB::table('users')->where('role', '=', 'admin')->get();
The third level is represented by a simple, yet powerful, Model, which use the Database\Connection instead of Helpers\Database. It is recommended way to use the new Database API, and can have a structure like:
namespace App\Models;
use Database\Model;
class Users extends Model
{
protected $table = 'users';
protected $primaryKey = 'userId';
public function __construct()
{
parent::__construct();
}
}
To note the two protected variables, which specify the Table name and Primary Key, which are mandatory to be configured, specially the Table name, the second one defaulting to 'id'.
A Database\Model is similar as usage with the Core\Model, but have the ability to transparently use the QueryBuilder methods, then permitting command like this:
use App\Models\Users;
$model = new Users();
$users = $model->where('role', '=', 'admin')->get();
Also, the Database\Model offer associated QueryBuilder instances, as following:
use App\Models\Users;
$model = new Users();
$query = $model->newQuery();
$users = $query->where('role', '=', 'admin')->get();
improved Response API
This Pull Request introduce an improved Response API, in a Laravel-esque style, able to simplify the Framework's Response management. Practically, is now possible to do in a Controller Method:
// Create a Response instance with string content
return Response::make(json_encode($user));
// Create a Response instance with a given status
return Response::make('Not Found', 404);
// Create a Response with some custom headers
return Response::make(json_encode($user), 200, array('header' => 'value'));
// Create a response instance with JSON
return Response::json($data, 200, array('header' => 'value'));
// Create a 404 response with data (will be directly obtained a shiny themed Error Page)
return Response::error('404', array('message' => 'Not Found'));
It is also possible to use those Response instance in the Route Filters, case when the given Response will be send to browser and the further processing will be skipped.
To note that Response class was moved to Core considering as being now a vital part of Framework, for its properly work, and not an (optional) Helper.
Also, there is introduced a new class, called Core\Redirect, built on top of Core\Response, which permit in Route Filters or Controller Methods commands like:
// Create a Redirect Response to a location within the application
return Redirect::to('user/profile');
// Create a Redirect Response with a 301 status code
return Redirect::to('user/profile', 301);
// Create a Redirect Response and flash to the Session
return Redirect::to('profile')->with('message', 'Welcome Back!');
Someone can ask: what is wrong with Url::redirect() to need to return those Redirect instances from the Controller Methods and Route Filters?
The response is simple: while with a simple and clear API, yet powerful, the Routing and/or Controllers execution on Nova Framework is a complex thing, including even two stacked Middlewares.
Suddenly stopping the Framework Execution Flow in a middle of some Controller Method or Route Filter, is not the luckiest idea in the town, in the Nova's context. And exactly that Url::redirect() do, sending a redirection HTTP Header and unceremonious quitting the current execution.
In other hand, the new Response API permit further simplifications on Routing. For example, the command Response::error() is quite capable to display a themed/templated Error Page, without the help of an additional "Error Controller" or other "callbacks".
View Patches
It is supposed that View::nest() and Template::nest() always will nest a local generated View instance.
Adjust Core\Controller@after to correct handle the standalone View/Template instance handling.
Templates Class
Rationale
In almost any of Big Houses of frameworks, the View Class deal with a single VIEWS PATH.
Under Nova, behind Views, we have also the concept of Templates, which have their own design and concept.
Dealing with Template files in the same View class make us to complicate its design an can be confusing for end-users some of them looking to have problems to understand the inherited difference between a View file associated to a Controller and its Method, and a Template file, who give you (parts of) Layouts, even when using the ol'good Triplet Style Rendering.
I consider that is not better to continue this confusion maker situation even into the New Style Rendering API and it better to have a dedicated class who deal with the Template Files.
Enter the Core\Template, a View specialized to handle Template files; basically identical with (its parent) View, but with its own make() builder and no Compatibility Layer, being destined to be used New Style API only.
Usage
The usage of Core\Template is very simple. Instead to execute:
$template = View::makeTemplate('default', $data, 'Admin');
You can simply do:
$template = Template::make('default', $data, 'Admin');
From there, you can process it as usual, but the code will be much more clear and non confusional, Template dealing with the files from app/Templates, while the View deal only with the files from app/Views, and Modules based Views.
Alternative view/layout options
Disclaimer - NO API breaks included!
While someone (I look to you, Jim) can have a shock looking on the source code of the Core\View proposed by this pull-request, as in being completely rewritten, the API compatibility to actual Core\View is complete preserved. To be read:
This pull request does NOT introduce API breaks and the existent code will continue to work with NO changes.
Rationale
Everyone love the Views rendering triplets. Me too. You know, that < template header >< view >< template footer >, but when you need a more complex structure, containing blocks, diverse partials and fragments, that all by-hand rendering can become complicated, even with heavy use of recent introduced View::fetch().
And that complicated thing should be repeated every Method, from every Controller from your Frontend or Backend.
Sure, one can ask: but, while having at our disposition two stacked Middleware, on Routing and Controllers, why we can't compose our Views and instruct the Middleware to do the final job?
Response: yes, we can do that in some measure, but almost View rendering methods (excluding fetch) have an immediate effect, being static functions, arriving to the need of building and passing over complex data arrays, or doing another Code-Fu...
I'm of opinion that offering to end-user the ability to manipulate Views using a High Level API could simplify heavily the work with complex Page structures, like my own experience demonstrated me.
This is the reason of this pull-request and the proposed Core\View, to offer a fluent and powerful API to manipulate the Views and their data, with a strong Laravel-esque taste.
Basic Commands
While the actual Core\View methods are static and they should called independently, the new API works with View instances, then we should build them. We have two methods at disposition, for standard Views and Templates one. An combined usage example is presented bellow:
View::make('Welcome/SubPage')
->shares('title', $title)
->with($data)
->render();
// OR
$page = View::make('Welcome/SubPage') ->with($data);
View::makeTemplate('default')
->shares('title', $title)
->withContent($page)
->display();
Probably you'll notice that we passed the first View instance ($page) as is to second one. That will make it to be automatically fetched to $content variable, on final rendering. Also, you will notice that the View Methods can be chained.
The data can be passed to a View instance in diverse ways. The following commands are equvalent:
$page = View::make('Welcome/SubPage');
$page->with('shinyInfo', $info);
$page->withShinyInfo($info);
$page->shinyInfo = $info;
$page['shinyInfo'] = $info;
To note the variable name transformation by dynamic withX methods.
Also, the View instances can be nested. The following command are equivalent:
// Add a View instance to a View's data
$view = View::make('foo')->nest('footer', 'Partials/Footer');
// Equivalent functionality using the "with" method
$view = View::make('foo')->with('footer', View::make('Partials/Footer'));
To note that nesting assume that the nested View instance is a Standard View, not a Template one.
There is also a new shares() method, similar with actual share() but working for instances.
As rendering commands, we have:
fetch() : will render the View and return the output
render() : will render and output the View and display the output
display() : same as display() but will send also the Headers.
Simple Usage
NOTE: the Triplet Style Rendering works as always, being full supported.
Then you do not need to change your already written code. The following example is from an untouched Welcome's Controller method:
View::renderTemplate('header', $data);
View::render('Welcome/Welcome', $data);
View::renderTemplate('footer', $data);
This pull request introduce also a simple method of auto-rendering, which enter automatically in action when you return from a Controller Method execution a View instance.
Then is enough for an usual template-based rendering to have a command like:
return View::make('Welcome/SubPage', $data)->shares('title', $title);
Practically, that rendering style will execute also in Controller's after() the command:
View::makeTemplate($this->layout, array(), $this->template)
->withContent($result)
->display();
Every Controller have now the ability to specify its Template and Layout, as following:
class Welcome extends Controller
{
protected $template = 'Admin';
protected $layout = 'custom';
/**
* Call the parent construct
*/
public function __construct()
{
parent::__construct();
$this->language->load('Welcome');
}
...
}
WHERE the 'default' Layout is a simple composition of your header/footer files. For details, see:
app/Templates/Default/default.php
That file contains only:
require dirname(__FILE__) .'/header.php';
echo $content;
require dirname(__FILE__) .'/footer.php';
Then, any changes in your header and footer files will be propagated.
Advanced Usage
Rendering with partials (i.e. header/footer) from the standard Views location
If you need to work with partials, for example blocks, header and footer files located in app/Views directory, it is very simple to do that. You have just to compose your views as following:
The following examples use a very simple Template Layout file, called:
app/Templates/Default/custom.php
which contains:
echo $header;
echo $content;
echo $footer;
Rendering with a complete custom Template living on Views folder
return View::makeTemplate('custom')
->shares('title', $title)
->with('header', View::make('Partials/Header'))
->with('content', View::make('Page/Index', $data))
->with('footer', View::make('Partials/Footer'));
OR
return View::makeTemplate('custom')
->shares('title', $title)
->withHeader(View::make('Partials/Header'))
->withContent(View::make('Page/Index', $data))
->withFooter(View::make('Partials/Footer'));
OR
return View::makeTemplate('custom')
->shares('title', $title)
->nest('header', 'Partials/Header')
->nest('content', 'Page/Index', $data)
->nest('footer', 'Partials/Footer');
OR
return View::makeTemplate('custom')
->shares('title', $title)
->with('content', View::make('Partials/Layout')
->nest('content', 'Page/Index', $data));
Rendering in the Triplet Style, but using the new API
return View::makeTemplate('custom')
->shares('title', $title)
->with('header', View::makeTemplate('header'))
->with('content', View::make('Page/Index', $data))
->with('footer', View::makeTemplate('footer'))
Rendering in the Triplet Style, but using some Views as blocks
// Views instances automatically rendered into base View rendering.
$data['latestNewsBlock'] = View::make('Articles/LatestNewsBlock')->withNews($latestNews);
$data['topVisitedBlock'] = View::make('Articles/TopVisitedBlock')->withNews($topNews);
View::share('title', $title);
View::renderTemplate('header');
View::render('Welcome/Welcome', $data); // <-- there the View instances from $data will be fetched automatically
View::renderTemplate('footer');
FAQ
How is possible to have 'View::render($path, $data, $module)' while '$view->render()' accept NO parameters?
The new Core\View speculate its Design to create a Facade which literally transform your (classic) static calls into New Style commands. The process is transparent and well optimized, but also with the result that (compat) View::render($path, $data, $module) and (native) $view->render() behave different.
What are the real commands executed when I do 'View::render($path, $data, $module)' ?
$view = View::make($path, $data, $module);
Response::sendHeaders();
$view->render();
How is possible to execute 'View::addHeader($header)' while there is no such method, statically or not?
The Core\View Facade transparently execute the 'right' command:
Response::adHeader($header);
Why you moved the Header associated methods from Core\View to Helpers\Response?
In my opinion, a View is just an abstraction of the Page Content, NOT being even complete without additional Template header and footer, or layout, which decides the overall Theme. Why it should send HTTP Headers? That's the job of a Response.
In other hand, the Headers support into Helpers\Response exists since its creation, being a Class imported from NovaIgniter (the 3.0-branch), together with the Routing.
Then, I just preserved the existing API while calling the 'right' commands, in my opinion, from the (HTTP) Response.
How is possible to execute 'echo $view' and render it, where $view is a View instance?
If you do as following you obtain an rendering output:
$view = View::make($path, $data, $module);
echo $view;
That's a expected behavior, a Core\View instance having the ability to automatically execute a fetch while casting it as string.
Implementation of Route Filters support into Routing and other improvements
Rationale
After the implementation of Controller's Middleware, the current pull request introduce the Routing Middleware, implemented using the concept of the Route Filters.
For what they are good for, comparative with Controller's before() and after() ?
The Controller's Middleware, aka Controller Execution Flow represents a High Level processing API, executed by the requested Controller, when it is instantiated, its requested Method is know as being valid and callable, and working in the same flow as your wanted Method.
The graph of The Controller Execution Flow is as follow:
before() -> action() -> after()
While very efficient and accurate, sometimes this design is not the best. For example, to instantiate a Controller and start its Execution Flow, only to obtain an redirect from a CSRF fault, can be costly as resources and response speed.
Better is to have a solution for Routing to handle the CSRF fault, before to even instantiate the requested Controller, right after the Route was identified; this was the used resources will be lower and response speed will be better.
Enter the Route Filters: a method to execute specified callbacks right after the correct Route was identified and before starting the execution of associated Controller.
Route Filters
How they works? Let's say that we want a CSRF filter. In the (new) file app/Filters.php we define it as following:
Route::filter('csrf', function($route) {
if (! Csrf::isTokenValid()) {
Url::redirect();
}
});
We'll see that a Route Filter definition have a name as first parameter and secondly, an callback which receive a Core\Route instance, which is just current matched Route, from where being available into callback information about HTTP method, URI, captured parameters, Route callback, etc.
ATTENTION: WHEN one of Filters return boolean FALSE, the Routing will generate an "404 Error" for the matched Route even it is a valid matched one.
This is useful to "hide" parts of your website for non authenticated use or to redirect to a custom "404 Error" page, for example.
Note that Route Filters are defined using "Route::filter()"
How to use this Filter? We use a new style of defining Routes:
Router::post('contact', array(
'filters' => 'csrf',
'uses' => 'App\Controllers\Contact@store'
));
WHERE the Route definition accept an array as second parameter and where the keys name are obviously. The key 'filters' assign to the value of a '|' separated string of used Route Filters, and the key 'uses' assign the associated Callback for the Route.
Running this Route definition, the Routing will known to apply the the Filter with the name 'csrf' before the Controller execution, then on CSRF fault, the Filter's callback will be executed and we go very fast into redirect.
Like I said, it is possible to apply multiple Filters to a Route, using a string containing their name separated by character '|' (pipe).
Usually we will want to add another two Route Filters and there is a more complex example:
Route::filter('csrf', function($route) {
if (($route->method() == 'POST') && ! Csrf::isTokenValid()) {
Url::redirect();
}
});
Route::filter('auth', function($route) {
if (Session::get('loggedIn') == false) {
Url::redirect('login');
}
});
Route::filter('guest', function($route) {
if (Session::get('loggedIn') != false) {
Url::redirect();
}
});
And an example of their usage can be:
Router::any('contact', array(
'filters' => 'guest|csrf',
'uses' => 'App\Controllers\Contact@index'
));
Router::any('login', array(
'filters' => 'guest|csrf',
'uses' => 'App\Controllers\Auth@login'
));
Router::get('logout', array(
'filters' => 'auth',
'uses' => 'App\Controllers\Auth@logout'
));
WHERE only the only Guest Users can access the Contact and Login page, with CSRF validation, while only the Authenticated Users can access the Logout action.
The alternative usage of Route Filters registering is to use a Class instead of callback, where the called method will receive the matched Route instance as parameter. For example:
Route::filter('auth', 'App\Helpers\Filters\User@isLoggedIn');
Route::filter('guest', 'App\Helpers\Filters\User@isGuest');
Improvements
In other hand, this pull-request introduce a improved Method handling when the Routes are registered and a new Router command called share(), which permit to register multiple Routes all pointing to the same Controller. For example:
Router::share(array(
array('GET', '/'),
array('POST', '/home')
), 'App\Controllers\Home@index');