środa, 14 stycznia 2009

Linki w CakePHP a'la Rails.

Piszą aplikacje w Ruby on Rails bardzo podobał mi się sposób tworzenia linków :
link_to "Dodaj", new_admin_client_reservation_path(client)
Może nie do końca chodzi mi o samo link_to ale już tworzenie url'a to fajna sprawa.

Pisząc aplikacje w CakePHP nie jestem szczególnie fanem korzystania z metody link znajdującego się w HtmlHelper. Osobiście wolałem wykorzystać samo Router::url() i wstawić to w element. Wszystko pięknie ale w momencie gdy trzeba zrobić link w którym konieczne jest podanie kontrolera, akcji, prefixu admin, id i jeszcze czegoś to już nie wygląda to tak pięknie gdy przekazujesz wszystko za pomocą tablicy:
Router::url(array('controller'=>'posts', 'action'=>'view', 'admin'=>true, 'id'=>$post['Post']['id']))
Dlatego też postanowiłem napisać mały helper który pozwala na tworzenie linków w trochę przyjemniejszy sposób. Oto mały przykład. Powiedzmy że mamy w aplikacji
Configure::read('Routing.admin') => secure
//w widoku
$post = array(['Post']=>array('id'=>3,'title'=>'Test post','slug'=>'test-post'));
Możemy teraz tworzyć linki tak :
$r->posts() # => /posts
$r->securePosts() # => /secure/posts
$r->formatedPosts('xml') # => /posts.xml
$r->secureFormatedPosts('xml') # => /secure/posts.xml

$r->addPost() # => /posts/add
$r->secureAddPost() # => /secure/posts/add
$r->formatedAddPost('xml') # => /posts/add.xml
$r->secureFormatedAddPost('xml') # => /secure/posts/add.xml

$r->editPost($post) # => /posts/edit/3
$r->secureEditPost($post) # => /secure/posts/edit/2
$r->formatedEditPost($post,'xml') # => /posts/edit/3.xml
$r->secureFormatedEditPost($post,'xml') # => /secure/posts/edit.xml
Naturalnie nie ma problemu żeby podać również standardowe argumenty jakie można przekazać do Router::url()
$r->SecureFormatedEditPost($post,'xml',array('full_base'=>true,'simple','#'=>'top')); # => http://example.com/secure/posts/edit/3/simple.xml#top
$r->SecureFormatedEditPost($post,'xml',array('full_base'=>true,'simple','foo'=>'bar','#'=>'top')) # => http://example.com/secure/posts/edit/3/simple/foo:bar.xml#top
Postanowiłem również ułatwić trochę proces przekazywania danych z wybranego rekordu do linku. Zawsze denerwowało mnie gdy musiałem wpisywać $post['Post']['id'] itp. Dlatego też w moim helperze można zrobić to tak:
$r->SecureFormatedEditPost($post,'xml',array('foo'=>'bar','title'=>':slug')); # => /secure/posts/edit/3/foo:bar/title:test-post.xml
$r->ViewPost($post,array(':slug')); # => /secure/posts/view/3/test-post
Uwaga! nie jest to jeszcze finalna wersja helpera! To co jeszcze trzeba zrobić
  • Poprawienie wydajności ( helper jest minimalnie wolniejszy od samego Router::url() ale wydaje mi się, że można jeszcze to poprawić )
  • Dla tych co jednak używają $html->link() możliwość tworzenia kompletnych linków
Helper był testowany w CakePHP 1.2  korzystając z PHP 5 ( sorry, PHP 4 jest blee ).
class rHelper extends AppHelper{
private $adminRouting = null;
private $idMethods = array('view','edit','delete');
private $methods = array('add','view','edit','delete');
public function __construct(){
parent::__construct();
$this->adminRouting = Configure::read('Routing.admin');
}
public function __call($method,$args){
$method = explode('_',Inflector::underscore($method));
$url = array();
if($this->_adminSection($method[0])){
$url[$this->adminRouting] = true;
array_shift($method);
}
if($method[0]=='formated'){
array_shift($method);
if($this->_idRequired($method[0])){
if(isset($args[1]) && is_string($args[1])){
$url['ext'] = $this->_clearArgs($args,1);
}else{
throw new Exception('Link format is not specified or it\'s not a string.', E_USER_ERROR);
}
}else{
if(isset($args[0]) && is_string($args[0])){
$url['ext'] = $this->_clearArgs($args,0);
}else{
throw new Exception('Link format is not specified or it\'s not a string.', E_USER_ERROR);
}
}
}
if($this->_hasMethod($method[0])){
$url['action'] = $method[0];
array_shift($method);
}else{
$url['action'] = 'index';
}
$url['controller'] = Inflector::pluralize($method[0]);
array_shift($method);
if($this->_idRequired($url['action'])){
if(!is_array($args[0])){
throw new Exception('Param $args must be an array.', E_USER_ERROR);
}
$model_name = Inflector::classify($url['controller']);
$data = $this->_clearArgs($args,0);
if(!isset($data[$model_name])){
throw new Exception('"'.$model_name.'" Model data not found.', E_USER_ERROR);
}else{
$data = $data[$model_name];
}
$model = ClassRegistry::getObject($model_name);
if(!$model){
App::import('model',$model_name);
$model = new $model_name;
}
if(!is_object($model)){
throw new Exception('Model "'.$model_name.'" not found.', E_USER_ERROR);
}
$model_id = $model->primaryKey;
if(!isset($data[$model_id])){
throw new Exception('Column "'.$model_id.'" is not specified.', E_USER_ERROR);
}
$url['id'] = $data[$model_id];
}
if(isset($args[0]) && is_array($args[0])){
foreach($args[0] as $n => $v){
if($v[0]==':' && isset($data[substr($v,1)])){
$v = $data[substr($v,1)];
}
if(is_numeric($n)){
$url[] = $v;
}else{
if($n[0]==':' && isset($data[substr($n,1)])){
$v = $data[substr($n,1)];
}
$url[$n] = $v;
}
}
}
return Router::url($url);
}
private function _idRequired($method){
return in_array($method,$this->idMethods);
}
private function _adminSection($method){
return $method == $this->adminRouting;
}
private function _hasMethod($method){
return in_array($method,$this->methods);
}
private function _clearArgs(&$args,$index){
$value = $args[$index];
unset($args[$index]);
$args = array_values($args);
return $value;
}
}

0 komentarze: