September 2007


Regular task is keep all routes to controller action is normall way, and all other links used as a link by name of user profile for example.

 So /controller1/action1 should redirect to action1 of controller1, and /username1 or /username2 should open profiles of users.

First of all i create component with function that return list of all controllers. Code is based on Felix blog post (thinkingphp.org)

<?php
class UtilsComponent extends 
Object
{

    var $controller

    function startup(&$controller) {
        
$this->controller = &$controller
;
    } 

    function listControllers() {
        
$Configure =& Configure::getInstance
();
        
$controllers 
= array();
        foreach(
$Configure->controllerPaths as $path
) {
            
$controllers am($controllersarray_map(array(&$this‘__controllerize’), listClasses($path
)));
        }
        
        return 
array_unique($controllers
);
    }
     
    function 
__controllerize($file
) {
        return 
Inflector::camelize(r(‘_controller.php’$file
));
    }     
     
    
     
    
}
?>

Next step is add list of default routes in /config/routes.php file before most common action. In router class /bare and /ajax routes marked as obsolete but i decide to keep it. This routes create by the router class but when we  create most common route it does not work.

    loadComponent(‘Utils’);
    
$utils=& new UtilsComponent
;
    foreach (
$utils->listControllers() as $controllerName
) {
        
$controller=Inflector::underscore($controllerName
);
        
Router::connect(“/$controller”, array(‘controller’ => $controller,‘action’ => ‘index’
));
        
Router::connect(“/bare/$controller/:action/*”, array(‘controller’ => $controller,‘bare’ => ‘1′
));
        
Router::connect(“/ajax/$controller/:action/*”, array(‘controller’ => $controller,‘bare’ => ‘1′
));
        
Router::connect(“/$controller/:action/*”,array(‘controller’ => $controller
));
    } 

     
And last step is declare common  route in  /config/routes.php file.


    
Router::connect(“*”, array(‘controller’ => ‘users’‘action’ => ‘profile’
)); 

 There I prepare some screencast how my solution work.

It is avi file that I place at box.net  http://www.box.net/shared/ad44br2bz0

Some  times we want to display in view combination of diferent fields of model. For example we want to have list box with full name (that combination of first_name last_name or prefix).

Also we want to paginate by this  column (full name).

 First class tasks is much more simple.

We can solve this problem in two way. First add column in after find callback, and after it we can use for examlpe $model->generateList method for such multiColumn. This method used in Autofield behavior.

Another class of tasks require sorting during fetching data from DB. In this case only one sollution possible. We need to add columns to fields list ib beforeFind callback. This method used in Truefield behavior.

<?php/* 
 * Autofield behavior for cakePHP 
 * comments, bug reports are welcome skie AT mail DOT ru 
 * @author Yevgeny Tomenko aka SkieDr 
 * @version 1.0.0.0
 
 configuration is
 mask is sprintf like mask
 
   array (‘newFieldName’ => array(‘fields’=>array(‘field1′,’field2′), ’mask’=>’(%s,%s’),
            ’newFieldName2′ => array(‘fields’=>array(‘field1′,’field3′), ’mask’=>’%s: %s’)
            );
 
  
 */ 
class AutoFieldBehavior extends ModelBehavior 

{    var $settings null;
    
//var $_model = null;
    
    
function setup(&$model$config 
= array()) {
        
$this->autoFieldSetup(&$model$config
);
    }
    
    function 
AutoFieldSetup(&$model$config 
= array()) {
        
//$config = array(‘enabled’=>true);        foreach ($config as $newField => $fieldDsc

) {
            if (isset(
$fieldDsc['fields'
])){
                foreach (
$fieldDsc['fields'] as $field
) {
                    if(!
$model->hasField($field
)) {
                        
//user_error(“Model {$model->name} does not have a field called {$field}”, E_USER_ERROR);
                    
}
                }
            }
            if (!isset(
$config[$newField]['fields'
])) {
                
$config[$newField]['fields'
]=array();
            }
            if (!isset(
$config[$newField]['mask'
])) {
                
$config[$newField]['mask']=
;
            }
        }
        $this->settings[$model->name] = $config;
    }
    
    
    
/**
     * After  find method. Called after all find
     *
     * Add aditional fields
     *
     * @param AppModel $model
     * @return boolean True to continue, false to abort the save
     */ 
    
function afterFind(&$model,  $results 
) {
        
$config=$this->settings[$model->name
]; 
        if (
is_array($results
)) {
            
$i 0
;
            while(isset(
$results[$i][$model->name]) && is_array($results[$i][$model->name
])) {
                if(
count($results[$i][$model->name])>0
) {
                    foreach (
$config as $newField => $fieldDsc
) {
                        
$fieldVals
=array();
                        foreach (
$fieldDsc['fields'] as $field
) {
                            if (isset(
$results[$i][$model->name][$field
])) {
                                
$fieldVals[]=$results[$i][$model->name][$field
];
                            } else {
                                
$fieldVals[]=
;
                            }
                        }
                        
$results[$i][$model->name][$newField]=vsprintf($fieldDsc['mask'],$fieldVals
);
                    }
                }
                
$i
++;
            }
        }
        return 
$results
;
    }    
}
?>

<?php/* 
 * True Field behavior for cakePHP 
 * comments, bug reports are welcome skie AT mail DOT ru 
 * @author Yevgeny Tomenko aka SkieDr 
 * @version 1.0.0.0
 
 configuration is
 
   array ( //’newFieldName’ => ’natave sql function calls’,
       ’newFieldName2′ => ’concat(‘(‘,id,’, ’,numid,’)')’
            );
 
  
 */ 

class TrueFieldBehavior extends ModelBehavior {    var $settings null

;
    
//var $_model = null;
    
    
function setup(&$model$config 
= array()) {
        
$this->trueFieldSetup(&$model$config
);
    }
    
    function 
TrueFieldSetup(&$model$config 
= array()) {
        foreach (
$config as $newField => $fieldDsc
) {
        }
        $this->settings[$model->name] = $config;
    }
    
    
    
/**
     * Before find method. Called before all find
     *
     * Set scope filer settings
     *
     * @param AppModel $model
     * @return boolean True to continue, false to abort the save
     */ 
    
function beforeFind(&$model$cond
) {
        
$model->log(array(‘true columns: beforeFind’=>$cond‘conf’=>$this->settings[$model->name
]));
        
$config=$this->settings[$model->name
]; 
        
$conds
=array();
        foreach (
$config as $newField => $fieldDsc
) {
            
$conds[]=“$fieldDsc as {$model->name}__$newField”
;
        }
        if (
count($conds)>0
) {
            if (
is_array($cond['fields'
])) {
                
$cond['fields']=am($cond['fields'],$conds
);
            } elseif (
$cond['fields']!=
) {
                
$cond['fields']=am($cond['fields'],$conds
);
            } else {
                
$cond['fields']=$conds
;
            }
            
$cond['fields'][]=“{$model->name}.*”
;
            foreach (
$model->belongsTo as $assoc => $assocDsc
) {
                
$cond['fields'][]=“$assoc.*”
;
            }
        }
        
        return 
$cond
;
    } 
 
    
    
/**
     * After  find method. Called after all find
     *
     * Add aditional fields
     *
     * @param AppModel $model
     * @return boolean True to continue, false to abort the save
     */ 
     
    
function afterFind(&$model,  $results 
) {
        
$config=$this->settings[$model->name
]; 
        if (
is_array($results
)) {
            
$i 0
;
            while(isset(
$results[$i][$model->name]) && is_array($results[$i][$model->name
])) {
                foreach(
$results[$i] as $key => $value
) {
                    if(
is_numeric($key
)) {
                        
$cleanList
=array();
                        foreach (
$value as $field => $fieldVal
) {
                            
$fieldDsc preg_split(‘/__/’,$field
);
                            if (
count($fieldDsc)==2
) {
                                
$results[$i][$fieldDsc[0]][$fieldDsc[1]]=$fieldVal
;
                                
$cleanList[]=$field
;
                            }
                        }
                        foreach (
$cleanList as $field
) {
                            unset(
$results[$i][$key][$field
]);
                        }
                        if (
count($results[$i][$key])==0
) {
                            unset(
$results[$i][$key
]);
                        }
                    }
                }
                
$i
++;
            }
        }
        return 
$results
;
    }
    
}
?>

It is very usefull during development if you have ability of navigation dirrectly to source code from explorer page.
I create such solution for my editor Notepad++.

First of all please download last version of editor from sourceforge page. http://notepad-plus.sourceforge.net/

Now i describe the basic solution that allow you to open model, controller and view from browser.

As you see in Debug controllear in before filter method created two config parameters.
First Editor.path is a path to the your editor.
Second Editor.linekey is string mask which used when need inform editor in which line need to open file.

I have editor at folder c:notepad++. All works well in this case.

How all works? When you click the link, browser open page with link to debuger controller in new window, and this page close by the javascript. Controller make system call and start editor with necessery parameters. All really simple.

Plece next controller in controllers folder with name debug_controller.php

Controller Class:

<?php 
class DebugController extends AppController {
    var $name 'Debug';
    var 
$helpers = array('Html''Form''Ajax''Debug');
    var 
$uses = array();
    var 
$components = array();
    
    function 
beforeFilter() {
        
Configure::write('Editor.path''C:\Notepad++\notepad++.exe');
        
Configure::write('Editor.linekey''-n%s');
        
        
parent::beforeFilter();
        return 
true;
    }    
    
    function 
open($file$line=null) { 
        
$file=base64_decode($file);
        
$this->_open($file$line);
        
$this->render('close'); 
    }
    
    function 
_open($file$line=null$options=array()) { 
        if (!empty(
$line)) {
            
$linekey=sprintf(Configure::read('Editor.linekey'),$line);
        } else {
            
$linekey='';
        }
        
$command=sprintf('%s "%s" %s',Configure::read('Editor.path'),$file,$linekey);
        
$this->log($command);
        
$result=system($command);
    } 
    
    function 
open_controller ($name$action=null) {
        
$name=Inflector::camelize($name);
        if (
loadController($name)) {
            
$cname=$name.'Controller';
            
$filepath=APP.'controllers'.DS.Inflector::underscore($cname).'.php';
            
$line=null;
            if (!empty(
$action)) {
                
$file=& new File($filepath);
                if (
$file->exists()) {
                    
$text=$file->read();
                    
$textarr=split("\n",$text);
                    foreach(
$textarr as $key => $str) {
                        if (
preg_match("/function\s+$action/i",$str)) {
                            
$line=$key+1;
                            break;
                        }
                    }
                }
            }
            
$this->_open($filepath,$line);
        }
        
$this->render('close'); 
        
        
    }     function 

open_model ($name) {
        
$name=Inflector::singularize(Inflector::camelize($name));
        if (
loadModel($name)) {
            
$cname=$name;
            
$filepath=APP.'models'.DS.Inflector::underscore($cname).'.php';
            
$file=& new File($filepath);
            if (
$file->exists()) {
                
$this->_open($filepath);
            }
        }
        
$this->render('close'); 
    }
    function open_view ($controller,$name) {
        
$cname=Inflector::camelize($controller);
        if (
loadController($cname)) {
            
$filepath=APP.'views'.DS.$controller.DS.$name;
            
$file=& new File($filepath.'.ctp');
            if (
$file->exists()) {
                
$this->_open($filepath.'.ctp');
            } else {
                
$file=& new File($filepath.'.thtml');
                if (
$file->exists()) {
                    
$this->_open($filepath.'.thtml');
                }
            }
        }
        
$this->render('close'); 
    }
    
}
?>

Create view /debug/show.ctp

View Template:


<script type="text/javascript">
 window.close();
</script>

Place next code in /views/layouts/default.ctp

View Template:


    <?php if (Configure::read()>0) {echo $debug->current_links();}?> 

If you use delault cake layout I recomend you to place it after header div (inside container div). This allow you always have links at top of page.

And last feature is a helper that you need to place at views/helpers/debug.php

Helper Class:

<?php 
class DebugHelper extends Helper 

    var 
$helpers = array('Html''Form''Ajax');//, 'Javascript');
    
var $view=null;
    var 
$options = array ('target' => '_blank');
    function link($file$line=null$title='Show') {
        
$link=$this->Html->link($title,    array ('controller' => 'debug''action' => 'open',$file$line),$this->options);
        
$this->log($link);
        return 
$link;
    }
    
    function 
current_links() {
        
        
$modelLink=$this->Html->link('Show Model',    array ('controller' => 'debug''action' => 'open_model',$this->params['controller']),$this->options);
        
$controllerLink=$this->Html->link('Show controller',    array ('controller' => 'debug''action' => 'open_controller',$this->params['controller'], $this->params['action']),$this->options);
        
$viewLink=$this->Html->link('Show view',    array ('controller' => 'debug''action' => 'open_view',$this->params['controller'], $this->params['action']),$this->options);
        
        
        return 
"$modelLink $controllerLink $viewLink";
    }
    
    
}
?>