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
September 11, 2007
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
September 10, 2007
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;
}
}
?>
September 7, 2007
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
<?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
<script type="text/javascript">
window.close();
</script>
Place next code in /views/layouts/default.ctp
<?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
<?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";
}
}
?>
August 28, 2007
First of all in app controller we need to gather all exists actions.
class AppController extends Controller {
function __construct()
{
if (isset($this->othAuthOpen)) {
$othOpen=array_map(’strtolower’,$this->othAuthOpen);
} else {
$othOpen=array();
}
$protected = array( ‘object’, low($this->name. ‘Controller’), ‘controller’, ‘appcontroller’,
‘tostring’, ‘requestaction’, ‘log’, ‘cakeerror’, ‘constructclasses’, ‘redirect’, ’set’, ’setaction’,
‘validate’, ‘validateerrors’, ‘render’, ‘referer’, ‘flash’, ‘flashout’, ‘generatefieldnames’,
‘postconditions’, ‘cleanupfields’, ‘beforefilter’, ‘beforerender’, ‘afterfilter’, ‘disablecache’, ‘paginate’);
$protected=am($protected,array_map(’strtolower’,get_class_methods(‘AppController’)));
$controllerName = Inflector::camelize($this->name);
$controllerPath = Inflector::underscore($controllerName);
$methList=array();
$methods=get_class_methods($controllerName.‘Controller’);
if (is_array($methods)) {
foreach($methods as $method) {
if ($method{0} != ‘_’ && !in_array(low($method), $othOpen) && !in_array(low($method), am($protected, array(‘delete’, ”)))) {
$methList[]=$method;
}
}
}
//debug($methList);
$this->othAuthRestrictions=$methList;
}
}
After it we just need to declare in controller list of opened methods:
var $othAuthOpen = array(‘login’, ‘logout’, ‘forget’ , ‘register’, ‘confirm’, ‘noaccess’);
August 27, 2007
This behavior allow to limit selected and update rows of model with some scope.
Example of using if you want to scope articles with scope user_id then in beforeFiler of ArticlesController we place next code.
Value ‘User.id’ placed into session during login.
$this->Article->scopeSetup(array('user_id'=>$this->Session->read('User.id')));
$this->Article->scopeEnable();
After this all insert and read will scoped with user_id value.
Now for example possible to call $this->paginate() without any additional conditions.
Also before create and delete record behavior check that record from required scope.
<?php /*
* Scope behavior for cakePHP
* comments, bug reports are welcome skie AT mail DOT ru
* @author Yevgeny Tomenko aka SkieDr
* @version 1.0.0.1
configuration is
1) array (’scope’ => array(‘parent_id’=>$parentIdValue, ’secondParent’=> $secondParentValue), ’enabled’ => true);
2) array(‘parent_id’=>$parentIdValue, ’secondParent’=> $secondParentValue); 3) array (‘enabled’ => false);
*/
class ScopeBehavior extends ModelBehavior { var $settings = null
;
function setup(&$model, $config = array()) {
$this->scopeSetup(&$model, $config);
}
function scopeSetup(&$model, $config = array()) {
$settings = array(‘enabled’=>true);
if (isset($config['scope'])) {
$settings['scope']=$config['scope'];
} else{
$settings['scope']=$config;
}
if (isset($config['enabled'])) {
$settings['enabled']=$config['enabled'];
}
$this->settings[$model->name] = $settings;
}
function scopeEnable (&$model){
$this->settings[$model->name]['enabled'] = true;
} function scopeDisable (&$model){
$this->settings[$model->name]['enabled'] = false;
}
/**
* 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) {
if (!$this->settings[$model->name]['enabled']) return true;
if (!is_array($cond['conditions'])) {
$cond['conditions']=array($cond['conditions']);
}
foreach ($this->settings[$model->name]['scope'] as $key => $value) {
$cond['conditions'][$model->name.'.'.$key]=$value;
}
return $cond;
} function beforeSave(&$model
) {
if (!$this->settings[$model->name]['enabled']) return true;
if (empty($model->data[$model->name][$model->primaryKey])) { //add
foreach ($this->settings[$model->name]['scope'] as $key => $value) {
$model->data[$model->name][$key]=$value;
}
} else { //update
// now nothing to do
}
return true;
} function beforeDelete(&$model) {
if (!$this->settings[$model->name]['enabled']) return true;
if ($model->id!==false) {
$curr=$model->read(null,$model->id);
}
foreach ($this->settings[$model->name]['scope'] as $key => $value) {
if ($curr[$model->name][$key]!=$value) return false;
}
return true;
}
}
?>
August 27, 2007
In last build I got inforation next warning
Dispatcher::start – Controller::$beforeFilter property usage is deprecated and will no longer be supported. Use Controller::beforeFilter().
So now in app_controller necessry to use beforeFilter function. I used beforeFilter property in app_controller before and now i got next trouble: some my controllers redeclare beforeFilter. So don’t forget to call parent class in controllers.
function beforeFilter() {
parent::beforeFilter();
}
August 23, 2007
As you know cakephp 1.2 allow to call custom functions during validation.
var $validate = array( 'alias' => array(
'rule' => array('unique', 'alias'),
'required' => true, 'allowEmpty' => false,
'message' => 'Such alias already exists' ),
);
This is sample when you need to have unique field in whole table.
function unique($data, $name){
$this->recursive = -1;
$found = $this->find(array(“{$this->name}.$name” => $data));
$same = isset($this->id) && $found[$this->name][$this->primaryKey] == $this->id;
return !$found || $found && $same;
}
This example from cakebaker blog.