-
-
Save zofe/e9ef3fdc7ebf9824c8dc to your computer and use it in GitHub Desktop.
<?php namespace Zofe\Rapyd; | |
/** | |
* Class Router | |
* the rapyd router, works "before" laravel router to check uri/query string | |
* it set widgets status / actions. | |
* | |
* @package Zofe\Rapyd | |
* | |
* @method public static get($uri=null, $query=null, Array $route) | |
* @method public static post($uri=null, $query=null, Array $route) | |
* @method public static patch($uri=null, $query=null, Array $route) | |
* @method public static put($uri=null, $query=null, Array $route) | |
* @method public static delete($uri=null, $query=null, Array $route) | |
*/ | |
class Router | |
{ | |
public static $routes = array(); | |
public static $qs = array(); | |
public static $remove = array(); | |
public static $methods = array(); | |
public static $callbacks = array(); | |
/** | |
* little bit magic here, to catch all http methods to define a named route | |
* <code> | |
* Router::get(null, 'page=(\d+)', array('as'=>'page', function ($page) { | |
* //with this query string: ?page=n this closure will be triggered | |
* })); | |
* </code> | |
* @param $method | |
* @param $params | |
* @return static | |
*/ | |
public static function __callstatic($method, $params) | |
{ | |
self::checkParams($method, $params); | |
$uri = ltrim($params[0],"/"); | |
$qs = $params[1]; | |
$name = $params[2]['as']; | |
self::$routes[$name] = $uri; | |
self::$qs[$name] = $qs; | |
self::$remove[$name] = array(); | |
self::$methods[$name] = strtoupper($method); | |
self::$callbacks[$name] = $params[2][0]; | |
return new static(); | |
} | |
/** | |
* dispatch the router: check for all defined routes and call closures | |
*/ | |
public static function dispatch() | |
{ | |
$uri = \Request::path(); | |
$qs = parse_url(\Request::fullUrl(), PHP_URL_QUERY); | |
$method = \Request::method(); | |
foreach (self::$routes as $name=>$route) { | |
$matched = array(); | |
if ($route=='' || preg_match('#' . $route . '#', $uri, $matched) && self::$methods[$name] == $method) { | |
array_shift($matched); | |
if (self::$qs[$name]!='' && preg_match('#' . self::$qs[$name] . '#', $qs, $qsmatched)) { | |
array_shift($qsmatched); | |
$matched = array_merge($matched, $qsmatched); | |
call_user_func_array(self::$callbacks[$name], $matched); | |
} elseif (self::$qs[$name] == '') { | |
call_user_func_array(self::$callbacks[$name], $matched); | |
} | |
} | |
} | |
} | |
public static function isRoute($name, $params) | |
{ | |
$uri = \Request::path(); | |
$qs = parse_url(\Request::fullUrl(), PHP_URL_QUERY); | |
$method = \Request::method(); | |
$route = @self::$routes[$name]; | |
$matched = array(); | |
if ($route=='' || preg_match('#' . $route . '#', $uri, $matched) && self::$methods[$name] == $method) { | |
array_shift($matched); | |
if (self::$qs[$name]!='' && preg_match('#' . self::$qs[$name] . '#', $qs, $qsmatched)) { | |
array_shift($qsmatched); | |
$matched = array_merge($matched, $qsmatched); | |
if ($matched == $params) | |
return true; | |
} elseif (self::$qs[$name] == '') { | |
if ($matched == $params) | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* check if route method call is correct | |
* @param $method | |
* @param $params | |
*/ | |
private static function checkParams($method, $params) | |
{ | |
if (! in_array($method, array('get','post','patch','put','delete'))) | |
throw new \BadMethodCallException("valid methods are 'get','post','patch','put','delete'"); | |
if (count($params)<3 || | |
!is_array($params[2]) || | |
!array_key_exists('as', $params[2]) || | |
!array_key_exists('0', $params[2]) || | |
!($params[2][0] instanceof \Closure) ) | |
throw new \InvalidArgumentException('third parameter should be an array containing a | |
valid callback: array(\'as\'=>\'routename\', function () { }) '); | |
} | |
/** | |
* queque to remove from url one or more named route | |
* | |
* @return static | |
*/ | |
public function remove() | |
{ | |
$routes = (is_array(func_get_arg(0))) ? func_get_arg(0) : func_get_args(); | |
end(self::$routes); | |
self::$remove[key(self::$routes)] = $routes; | |
return new static(); | |
} | |
/** | |
* return a link starting from routename and params | |
* like laravel link_to_route() but working with rapyd widgets/params | |
* | |
* @param $name route name | |
* @param $param one or more params required by the route | |
* @return string | |
*/ | |
public static function linkRoute($name, $params, $url = null) | |
{ | |
$url = ($url != '') ? $url : \Request::fullUrl(); | |
$url_arr = explode('?', $url); | |
$url = $url_arr[0]; | |
$qs = (isset($url_arr[1])) ? $url_arr[1] : ''; | |
if (!is_array($params)) $params = (array)$params; | |
//If required we remove other routes (from segments or qs) | |
if (count(self::$remove[$name])) { | |
foreach (self::$remove[$name] as $route) { | |
if (self::$routes[$route]) | |
$url = preg_replace('#(\/?)'.self::$routes[$route].'#', '', $url); | |
if (self::$qs[$route]) { | |
$url = preg_replace('#(&?)'.self::$qs[$route].'#', '', $url); | |
} | |
} | |
} | |
//if this route works with uri | |
//I check for all params to build the append segment with given params, | |
//then I strip current rule and append new one. | |
if (self::$routes[$name]) { | |
$append = self::$routes[$name]; | |
if (preg_match_all('#\(.*\)#U',$append, $matches)) { | |
foreach ($params as $key=>$param) { | |
$append = str_replace($matches[0][$key],$param, $append); | |
} | |
} | |
$url = preg_replace('#(\/?)'.self::$routes[$name].'#', '', $url); | |
$url = ltrim($url."/".$append,'/'); | |
} | |
//if this route works on query string | |
//I check for all params to buid the "append" string with given params, | |
//then I strip current rule and append the new one. | |
if (self::$qs[$name]) { | |
$append = self::$qs[$name]; | |
if (preg_match_all('#\(.*\)#U',$append, $matches)) { | |
foreach ($params as $key=>$param) { | |
$append = str_replace($matches[0][$key],$param, $append); | |
} | |
} | |
$qs = preg_replace('#(&?)'.self::$qs[$name].'#', '', $qs); | |
$qs = str_replace('&&','&',$qs); | |
$qs = rtrim($qs, '&'); | |
$qs = $qs .'&'.$append; | |
} | |
if ($qs != '') $qs = '?'.$qs; | |
return $url.$qs; | |
} | |
} | |
....... | |
//example | |
\Zofe\Rapyd\Router::get(null, null, array('as'=>'current', function() { | |
//will be execuded every request (no uri and no query if filtered) | |
})); | |
## DataGrid | |
\Zofe\Rapyd\Router::get(null, 'ord=(-?)(\w+)', array('as'=>'orderby', function($direction, $field) { | |
//orderby will be executed for every request where there is a "ord" parameter | |
\Event::queue('dataset.sort', $direction, $field); | |
}))->reset('page'); | |
\Zofe\Rapyd\Router::get(null, 'page=(\d+)', array('as'=>'page', function($page) { | |
\Event::queue('dataset.page', $page); | |
})); | |
## DataForm | |
\Zofe\Rapyd\Router::post(null, 'process' , array('as'=>'process', function() { | |
\Event::queue('dataform.save'); | |
}); | |
## DataEdit | |
\Zofe\Rapyd\Router::post(null, 'insert=(\d+)', array('as'=>'insert', function($id) { | |
\Event::queue('dataedit.insert', $id); | |
}); | |
\Zofe\Rapyd\Router::patch(null, 'update=(\d+)', array('as'=>'update', function($id) { | |
\Event::queue('dataedit.update', $id); | |
}); | |
\Zofe\Rapyd\Router::get(null, 'delete=(\d+)', array('as'=>'delete', function($id) { | |
//delete confirmation | |
\Event::queue('dataedit.delete', $id); | |
})->persist(false); | |
\Zofe\Rapyd\Router::delete(null, 'do_delete=(\d+)', array('as'=>'do_delete', function($id) { | |
\Event::queue('dataedit.dodelete', $id); | |
}); | |
//Alternative o variazioni possibili: | |
## DataGrid | |
\Zofe\Rapyd\Router::get('/ord/(-?)(\w+)', null , array('as'=>'orderby', function($direction, $field) { | |
//orderby will be executed for all request where there is a uri segment /ord/field or /ord/-field | |
}); | |
\Zofe\Rapyd\Router::get('/page/(\d+)', null, array('as'=>'page', function($page) { | |
//limit offset | |
}); | |
##### altri parametri.. | |
/* | |
indica di non preservare i parametri delle route indicate | |
(es. la route dell'orderby resetta la paginazione) | |
nota: per default tutte le route post/patch vengono rimosse sempre. | |
*/ | |
->remove('nomeroute', 'nomealtraroute'...): | |
obiettivo attuale..
fare named route, obbligatoriamente (per poterle lavorare nei widget devono avere un nome)- il terzo parametro potenzialmente deve poter essere solo la closure da eseguire (per compatibilità con il router di laravel)
<?php
\Zofe\Rapyd\Router::get(null, 'ciccio=(\d+)', function($x) {
die('hai richiesto ?ciccio='.$x);
}));
- deve essere possibile definire una route con un array, per semplificare la possibilità di salvarli in un file di configurazione, sovrascrivibile dagli utenti finali es:
<?php
Zofe\Rapyd\Router::route(array(
'method'=>'patch',
'qs'=>'update=(\d+)',
'as'=>'update',
'remove'=>array('orderby'),
'action'=>function($id) {
//closure..
}));
prevedere oltre al->persist(false);
un->remove(array(named.route1, named.route2));
per permettere la rimozione di uno o più parametri (es. se sto' costruendo il link per fare l'orderby, devo necessariamente resettare la paginazione, e via dicendo).. verificare la possibilità di fare un removeAll()- i link interni andranno fatti così (già funziona)
<?php
$orderby = Rapyd::linkRoute('orderby', '-', 'title');
// on /rapyd-demo/grid?ord=-id&page=2&custom=3
// will output /rapyd-demo/grid?custom=3&ord=-title
- spostare la logica degli "sniff action/status" nelle closure :
al momento nelle closure ho previsto queue di eventi dei vari widget.
Poi nei widget andranno creati i singoli metodi e i listener, poi al build o nel costruttore.. a seconda dei casi ci và unEvent::flush('nomewidget.*')
per scatenare gli eventi.
In questo modo si dovrebbe riuscire a fare a meno del controller, e (poichè si usano gli eventi) dovrebbe essere semplice, per chi ne ha la necessità, controllare il comportamento dei widget, e "crearne di nuovi".
problema con paginazione & dataset (come scavalcare il paginator di laravel)
risolto con nel listener:
Paginator::setCurrentPage($pag)
e l'override di link nel dataset
$links = $this->paginator->links($view);
$links = preg_replace('@href="(.*\?page=(\d+))"@U',
'href="'.Rapyd::linkRoute('page', '$2').'"', $links);
sembra funzionare bene tutta la logica.. si dovrebbe fare un handler ma al momento basta:
- creare i metodi nei widget
- fare i listen sul metodo statico principale (source)
- e al build fare il flush/fire degli eventi:
<?php
...
public function sort($direction, $field)
{
...
}
public function page($page)
{
\Paginator::setCurrentPage($page);
}
public static function source($source)
{
$ins = new static();
$ins->source = $source;
\Event::listen('dataset.sort', array($ins, 'sort'));
\Event::listen('dataset.page', array($ins, 'page'));
....
}
public function build()
{
\Event::flush('dataset.sort');
\Event::flush('dataset.page');
....
Limiti:
Ora come ora.. si è limitati comunque all'uso della PK nelle route di CRUD, perchè il "find" dei record viene appunto fatto su id.
Laravel di suo ha una roba potentissima che è il Route model binding, http://laravel.com/docs/4.2/routing#route-model-binding
per fare DI del model, definendo eventualmente il modo con cui viene caricato model.
Si puo' trovare un modo di sfruttarlo?
il "router" (anche se è improprio chiamarlo così) serve a centralizzare la logica degli eventi e degli stati dei widgets, ed a creare le url internamente con un meccanismo simile alle named-routes (ti dico la route, ti passo i parametri e mi costruisci il link).
Serve anche a personalizzare completamente le uri.. es: non ti piace la query string? usa le uri (anche scavalcando il router di laravel)
funziona così: