Somebody like to have one installation of cake core and many applications with it.

But  when you have many projects written in different time in diferent versions of cake this is impossible.

Appears problem how to easy use cake shells if you can’t add this util to path.

I propose next solution.

All we need is call modified script from app folder.

Example:

  cd /var/www/shop/app

  ./cake bake

Windows version cake.bat
@echo.
@echo off
@set path=%path%;C:\dev\_tools_\graphviz-2.16\bin\

SET app=%0
SET app1=%CD%
 
cd ..\cake\console
SET lib=%~dp0

php -q “%CD%\cake.php” -working “%app1%”  %*

echo.
cd %app1%
@echo on

Unix version: ./cake

#!/bin/bash
clear

APP=`pwd`
APP1=`pwd`
cd ../cake/console
LIB=${0/%cake/}

echo “Hello $USER,”

exec php -q ${LIB}cake.php -working “${APP1}” “$@”

cd ${APP1}
@echo on

echo ” “;

exit;

Advertisements

Simple task for build graphic representation of cake schema based on graphwiz.

For this task need to have installed graphviz in path or use “-tool path/to/graphwiz/dot”

Graphviz youcan dowload from  http://www.graphviz.org/Download..php 

 Live example was generated on well known aplication – Bakery. I also include bakery schema made in designer. For me generated graphviz schema looks more pretty and easier to explore.

Bakery DBDesigner schema.

Example of schema – bakery application

<?php/**
 * 
 * 
 * PHP versions 4 and 5
 *
 * Copyright (c) Tomenko Yevgeny
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 */
uses(‘Folder’,‘File’,‘model’.DS.‘connection_manager’
);class VisualizeShell extends Shell {       var $DOC_DIR ;
    var 
$PREFIX
;
    var 
$graphToolPath ‘dot.exe’
;
// cake visualize -tool C:\dev\_tools_\graphviz-2.16\bin\dot.exe
    
function help
() {
        
$this->out(‘CakePHP visualise:’
);
        
$this->out(“cake visualise [-tool graphVizTool]”
);
        
$this->hr
();
    }
    function initialize() {
        
$this->DOC_DIR APP “doc”
;
        
$this->PREFIX“img_”
;
        if (isset(
$this->params[‘tool’
])) {
            
$this->graphToolPath =$this->params[‘tool’
];
        }
        return 
true
;
    }    
    function 
main
() { 
        
$this->help
();
        
$this->doVisualize
();    
    }
    
    function 
getSchemaInfo($modelName,$table_name
) {
        
$attrs 
= array();
        if (
App::import(‘model’,$modelName
)) {
            
$model = & new $modelName
();
            
$attrs=$model->schema
();
            return 
$attrs
;
        } else {
            
$DynamicModel = new Model(array(‘name’=> $modelName‘table’=> $table_name
)); 
            
$attrs=$DynamicModel->schema
();
            return 
$attrs
;
        }
        return 
false
;
    }     
    
    
    function 
doVisualize
() {
        
$header=$this->PREFIX+strftime(‘%Y-%m-%d %H:%M:%S’,time
());
        
$version=0
;
        if (
$version 0
) {
          
$header .= “\\nSchema version $version”
;
        }
        
$tableInfos 
= array();
        foreach(
$this->getAllTables() as $table_name
) {
            
$this->out(“Looking at table: {$table_name}”
);
            
$modelName=$this->_modelName($table_name
);
            
$tableInfos[$modelName] = $this->getSchemaInfo($modelName,$table_name
);
        }
        if (!file_exists($this->DOC_DIR) || !is_dir($this->DOC_DIR)) {
            
$this->out(“Creating directory \”{$this->DOC_DIR}\”…”
);
            
$folder = & new Folder($this->DOC_DIRtrue
);
        }
        
$this->writeDotFile($header$this->DOC_DIR$tableInfos
);
    }       function 
getAllTables($useDbConfig ‘default’
) {
        
$db =& ConnectionManager::getDataSource($useDbConfig
);
        
$usePrefix = empty($db->config[‘prefix’]) ? ” $db->config[‘prefix’
];
        if (
$usePrefix
) {
            
$tables 
= array();
            foreach (
$db->listSources() as $table
) {
                if (!
strncmp($table$usePrefixstrlen($usePrefix
))) {
                    
$tables[] = substr($tablestrlen($usePrefix
));
                }
            }
        } else {
            
$tables $db->listSources
();
        }
        
$this->__tables $tables
;
        return 
$tables
;
    }
    function writeDotFile($header$target_dir$tableInfos) {
        
$tmp_dot_file $target_dir .DS“model_information.dot”
;        $f = & new File($tmp_dot_filetrue
);
        
        
// Define a graph and some global settings
        
$f->append(“digraph G {\n”
);
        
$f->append(“\toverlap=false;\n”
);
        
$f->append(“\tsplines=true;\n”
);
        
$f->append(“\tnode [fontname=\”Helvetica\”,fontsize=9];\n”
);
        
$f->append(“\tedge [fontname=\”Helvetica\”,fontsize=8];\n”
);
        
$f->append(“\tranksep=0.1;\n”
);
        
$f->append(“\tnodesep=0.1;\n”
);
//    $f->append(“\tedge [decorate=\”true\”];\n”);        // Write header info
        $f->append(“\t_schema_info [shape=\”plaintext\”, label=\”{$header}\”, fontname=\”Helvetica\”,fontsize=8];\n”);
        
        
$assocs 
= array();
        
// Draw the tables as boxes
        
foreach ($tableInfos as $table=>$attributes
) {
          
$attrs “”
;
          if (
is_array($attributes) && count($attributes)>0
) {
              foreach (
$attributes as $attrname=>$attr
) {
                if (
substr($attrname, –3) == ‘_id’
) { 
                  
# Create an association to other table
                  
$table_name Inflector::camelize(r(‘_id’,,$attrname
));
                  if (!empty(
$tableInfos[$table_name
])) {
                    
$other_table $tableInfos[$table_name
];
                    
$assocs[] = array(‘label’=> $attrname‘node1’=> $table‘node2’=> $table_name
);
                  }
                }
                if (!empty(
$attr[‘length’])) $attr[‘type’].=“[{$attr[‘length’]}]”
;
                
$attrtype=$attr[‘type’
];
                
$attrs .= “{$attrname} : {$attrtype}”
;
                if (!empty(
$attr[‘default’])) $attrs .= “, default: \\\”{$attr[‘default’]}\\\””
;
                
$attrs .= “\\n”
;
              }
          }
          
$f->append(“\t\”{$table}\” [label=\”{{$table}|{$attrs}}\” shape=\”record\”];\n”
);
        }
        
// Draw the relations
        
foreach ($assocs as $assoc
) {
          
$f->append(“\t\”{$assoc[‘node1’]}\” -> \”{$assoc[‘node2’]}\” [label=\”{$assoc[‘label’]}\”]\n”
);
        }
        
        
// Close the graph
        
$f->append(“}\n”
);
        
$f->close
();        
// Create the images by using dot and neato (grapviz tools)
        // We’ll create several images with different layout. There is no “prefect” layout algorithm that suits all models
        
$this->createImg($this->graphToolPath“-Gmode=hier”$tmp_dot_file$target_dir .DS“model_overview_neato_hier”
);
        
$this->createImg($this->graphToolPath“”$tmp_dot_file$target_dirDS“model_overview_neato_plain”
);
        
        
// Remove the .dot file
        
$f->delete
();
        
    }     
  
    function 
createImg($app$args$dot_file$img_file_base
) {        
        
$img_file “$img_file_base.png”
;
        
$command “{$app} {$args} -Tpng  -o\”{$img_file}\” \”{$dot_file}\””
;
        
system($command,$retval
);
        if (!
$retval
) {
            
$this->out(“Generated {$img_file}\n”
);
        } else {
            
$this->out(“Failed to execute the ‘{$app}’ command! Is grapviz (www.graphviz.org) installed?\n”
);          
        }
    }     
    
    
 
    
}
?>

All scripts in this post checked with laast nightly 1.2 build.

In continue of previous post i want to show how to extract similar elements of views to partials on example.

I create several bake scripts that allow to solve this.

After we place files to desired places we can use it with bake script.

So if we have controller Articles we run next commands.

cake bake view articles partial _partial 

cake bake view articles add

cake bake view articles edit

/vendors/shell/templates/views/form.ctp

<div class=”<?php echo $pluralVar;?> form”>
<?php echo “<?php echo \$form->create(‘{$modelClass}’);?>\n”;?>
    <fieldset>
         <legend><?php echo “<?php __(‘”.Inflector::humanize($action).” {$singularHumanName}’);?>”;?></legend>
<?php
        
echo “\t<?php\n”;
        foreach (
$fields as $field) {
            if (
$action != ‘add’ && $field == $primaryKey) {
                
$humanName=$field;
                
$humanName=Inflector::humanize($humanName);
                echo 
“\t\techo \$form->input(‘{$modelClass}.{$field}’,array(‘label’=>__(‘{$humanName}’,true)));\n”;
            }
        }
        echo 
“\t\techo \$this->renderPartial(‘_partial’);\n”;
        echo 
“\t?>\n”;
?>
    </fieldset>
<?php
    
echo “<?php echo \$form->end(‘Submit’);?>\n”;
?>
</div>
<div class=”actions”>
    <ul>
<?php if ($action != ‘add’):?>
        <li><?php echo “<?php echo \$html->link(__(‘Delete’, true), array(‘action’=>’delete’, \$form->value(‘{$modelClass}.{$primaryKey}’)), null, sprintf(__(‘Are you sure you want to delete # %s?’, true), \$form->value(‘{$modelClass}.{$primaryKey}’))); ?>”;?></li>
<?php endif;?>
        <li><?php echo “<?php echo \$html->link(__(‘List {$pluralHumanName}’, true), array(‘action’=>’index’));?>”;?></li>
<?php
        $done 
= array();
        foreach (
$associations as $type => $data) {
            foreach(
$data as $alias => $details) {
                if (
$details[‘controller’] != $this->name && !in_array($details[‘controller’], $done)) {
                    echo 
“\t\t<li><?php echo \$html->link(__(‘List “.Inflector::humanize($details[‘controller’]).“‘, true), array(‘controller’=> ‘{$details[‘controller’]}’, ‘action’=>’index’)); ?> </li>\n”;
                    echo 
“\t\t<li><?php echo \$html->link(__(‘New “.Inflector::humanize(Inflector::underscore($alias)).“‘, true), array(‘controller’=> ‘{$details[‘controller’]}’, ‘action’=>’add’)); ?> </li>\n”;
                    
$done[] = $details[‘controller’];
                }
            }
        }
?>
    </ul>
</div>

/vendors/shell/templates/views/partial.ctp

 <?php
        
echo “\t<?php\n”;
        foreach (
$fields as $field) {
            if (
$field == $primaryKey) {
                continue;
            } elseif (!
in_array($field, array(‘created’‘modified’‘updated’))) {
                
$humanName=$field;
                if (
substr($humanName, –3) == ‘_id’) {
                    
$humanName substr($humanName0strlen($humanName) – 3);
                }                 
                
$humanName=Inflector::humanize($humanName);
                echo 
“\t\techo \$form->input(‘{$modelClass}.{$field}’,array(‘label’=>__(‘{$humanName}’,true)));\n”;
            }
        }
        if(!empty(
$associations[‘hasAndBelongsToMany’])) {
            foreach (
$associations[‘hasAndBelongsToMany’] as $assocName => $assocData) {
                echo 
“\t\techo \$form->input(‘{$assocName}’);\n”;
            }
        }
        echo 
“\t?>\n”;
?>

If I have a few views, add.ctp, edit.ctp all using the same code for the form.
I have created another view file called _form.ctp
For displaing same part we call $this->renderPartial(‘_form’);

How I implement it?

All we need is overload View class with renderPartial function implementation. The goal of function is call renderElement with correct path to view.

<?php
class ExtView extends 
View 
{    

    function renderPartial($name$params = array(), $loadHelpers false) {
        if ((
strpos($name‘\\’)===false) && (strpos($name‘/’)===false
)) {
            
$name ‘..’ DS Inflector::underscore($this->name) . DS $name
;            
        }
        return 
$this->renderElement($name$params$loadHelpers
);
    }

    
}
?>

Now in app_controller all I need is set default view class in beforeRender callback:

<?php
class AppController extends Controller 
{
   
    function 
beforeRender
() {
        
$this->view=‘Ext’
;
        return 
true
;
    }    
    
}
?>

This simple shell script allow remove log or tmp files from /tmp folder.

<?php

/**
 * 
 * 
 * PHP versions 4 and 5
 *
 * Copyright (c) Tomenko Yevgeny
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 */

class CleanShell extends Shell {   

    function help() {
        
$this->out(‘CakePHP Clean:’
);
        
$this->out(“cake clean logs – Clean log files”
);
        
$this->out(“cake clean cache – Clean models and persistent files”
);
        
$this->hr
();
    }

    function initialize() {
        return 
true
;
    }

    function __clean($path) {
        
        
$folder=& new Folder($path
);
        
$tree=$folder->tree($pathfalse
);
        foreach (
$tree as $files
) {
            foreach (
$files as $file
) {
                if (!
is_dir($file
)) {
                    
$file=& new File($file
);
                    
$file->delete
();
                }
                
            }
        }
        return ;
    }    
    
    function 
logs
() {
        
$this->__clean(TMP ‘logs’
);    
        
$this->out(‘Logs cleaned.’
);
    }    

    function cache() {
        
$this->__clean(TMP ‘cache’ DS ‘models’
);
        
$this->__clean(TMP ‘cache’ DS ‘persistent’
);        
        
$this->out(‘Cache cleaned.’
);
    }    
    
    
  
/**
     * Handles CLI calls for the deploy task
     *
     * @param array $params
     * @return unknown
     */
    
function main() { 
//$params
        
$this->help
();
    }
        
}

?>

In answer to post at google group i publick this simple shell utility that allow to generate list of “cake bake …” commands based on models in your database.

Usage “cake tasklist > generated.cmd”

And after it just edit generated.cmd and run it.

<?php

class TasklistShell extends Shell {

/**
 * Execution method always used for tasks
 *
 * @return void
 */
    
function main
() {
        
$this->__interactiveAuto
();

    }

/**
 * Handles auto baking
 *
 * @access private
 * @return void
 */
    
function __interactiveAuto
() {
        
$this->out(
);
        
$this->out(
);
        
$useTable null
;
        
$primaryKey ‘id’
;
        
$validate 
= array();
        
$associations = array(‘belongsTo’=> array(), ‘hasOne’=> array(), ‘hasMany’‘hasAndBelongsToMany’
=> array());
        
$useDbConfig ‘default’
;
        
$this->listAll($useDbConfig
);

        $db =& ConnectionManager::getDataSource($useDbConfig);

        $this->out(‘Models list’);
        
$this->hr
();
        foreach (
$this->_modelNames as $model
) {
            
$this->out(“cake bake model $model auto”
);
        }
        
$this->out(
);
        
$this->out(
);

        $this->out(‘Controllers list’);
        
$this->hr
();
        foreach (
$this->_modelNames as $model
) {
            
$controller=$this->_controllerName($model
);
            
$this->out(“cake bake controller $controller scaffold”
);
        }
        
$this->out(
);
        
$this->out(
);

        $this->out(‘Views list’);
        
$this->hr
();
        foreach (
$this->_modelNames as $model
) {
            
$controller=$this->_controllerName($model
);
            
$this->out(“cake bake view $controller”
);
        }
        
$this->out(
);
        
$this->out(
);

    }    
    
    

/**
 * outputs the a list of possible models or controllers from database
 *
 * @param string $useDbConfig
 * @param string $type = Models or Controllers
 * @return output
 */
    
function listAll($useDbConfig ‘default’
) {
        
$db =& ConnectionManager::getDataSource($useDbConfig
);
        
$usePrefix = empty($db->config[‘prefix’]) ? ” $db->config[‘prefix’
];
        if (
$usePrefix
) {
            
$tables 
= array();
            foreach (
$db->listSources() as $table
) {
                if (!
strncmp($table$usePrefixstrlen($usePrefix
))) {
                    
$tables[] = substr($tablestrlen($usePrefix
));
                }
            }
        } else {
            
$tables $db->listSources
();
        }
        
$this->__tables $tables
;
        
//$this->out(‘Possible Models based on your current database:’);
        
$this->_modelNames 
= array();
        
$count count($tables
);
        for (
$i 0$i $count$i
++) {
            
$this->_modelNames[] = $this->_modelName($tables[$i
]);
            
//$this->out($i + 1 . “. ” . $this->_modelNames[$i]);
        
}
    }

/**
 * Forces the user to specify the model he wants to bake, and returns the selected model name.
 *
 * @return the model name
 */
    
function getName($useDbConfig
) {
        
$this->listAll($useDbConfig
);

        $enteredModel ;

        while ($enteredModel == ) {
            
$enteredModel $this->in(‘Enter a number from the list above, or type in the name of another model.’
);

            if ($enteredModel == ” || intval($enteredModel) > count($this->_modelNames)) {
                
$this->out(‘Error:’
);
                
$this->out(“The model name you supplied was empty, or the number \nyou selected was not an option. Please try again.”
);
                
$enteredModel 
;
            }
        }

        if (intval($enteredModel) > && intval($enteredModel) <= count($this->_modelNames)) {
            
$currentModelName $this->_modelNames[intval($enteredModel) – 1
];
        } else {
            
$currentModelName $enteredModel
;
        }

        return $currentModelName;
    }
/**
 * Displays help contents
 *
 * @return void
 */
    
function help
() {
        
$this->hr
();
        
$this->out(“Usage: cake tasklist”
);
        
$this->hr
();
        
$this->out(“this shell generate list of bake commands”
);
        
$this->out(“”
);
        exit();
    }
}
?>

Goal: 

The goal of test is retrieve all articles tagged by one or several tags. So we doesn’t need the list of tags for each articles, but we need the conditions on the HABTM Tag model. We want to see articles list paginated with 30-50 records on each page. There is two possibility for searching articles my specific tags. 1) Use internal cakePHP search with condition to hasbtm model:         

$this->unbindAll(array('hasAndBelongsToMany' => array('Tag'))); 
$this->hasAndBelongsToMany['Tag']['conditions'] = $this->cond 
$result=$this->findAll( array('Article.id' => range(200,250))); 

In this case executed two query ([2], [3]). First used for conditions on Article model, and based on it cake build second query [3] condition. 2) Use my extension of dbo_mysql driver that posted at bakery: http://bakery.cakephp.org/articles/view/extending-of-dbosource-and-model-with-sql-generator-function         

$this->unbindAll();        

$this->ArticlesTag->unbindAll(array('belongsTo' => array('Tag')));
$q=$this->ArticlesTag->getQuery($this->cond,'article_id'); 
$result=$this->findAll(array(  array( 'Article.id' => range(200,250),  'id' => "IN -!("$q .")"),));  

In this case buld only one query to DB [1]. This query is combination of [2] and [3]. 

Nr Query Affected Num. rows Took (ms)
1 SELECT `Article`.`id`, `Article`.`num`, `Article`.`subject`, `Article`.`body` FROM `articles` AS `Article` WHERE (`Article`.`id` IN (200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250) ) AND (`id` IN (SELECT `ArticlesTag`.`article_id` FROM `articles_tags` AS `ArticlesTag` LEFT JOIN `tags` AS `Tag` ON (`ArticlesTag`.`tag_id` = `Tag`.`id`) WHERE ((`Tag`.`tag` like ‘text%’) OR (`tag` like ‘%msg%’)) ))   51 51 2
2 SELECT `Article`.`id`, `Article`.`num`, `Article`.`subject`, `Article`.`body` FROM `articles` AS `Article` WHERE `Article`.`id` IN (200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250) 51 51 1
3 SELECT `Tag`.`id`, `Tag`.`num`, `Tag`.`tag`, `ArticlesTag`.`tag_id`, `ArticlesTag`.`article_id` FROM `tags` AS `Tag` JOIN `articles_tags` AS `ArticlesTag` ON (`ArticlesTag`.`article_id` IN (200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250) AND `ArticlesTag`.`tag_id` = `Tag`.`id`) WHERE ((`Tag`.`tag` like ‘text%’) OR (`tag` like ‘%msg%’)) 290 290 5

   

Time for both query is comparable (6 ms for first test, and 2ms for second). Average 10 test runs show next resultsnative method: 0.2427011013031dbo_mysql_ext method: 0.026184034347534 As we analyze before mysql does not take many time so most time lost in data analyzation in case of cake core method. In first variant cake spend time for placing hasbtm records to parents. As I mention before we doesn’t need the list of tags for each articles. 

Conclussion: 

So we see that the extension of dbo driver give good perfomance (about ten times) in comparisson cake core method. 

Run benchmark: 

 1. Download file http://cakeexplorersamples.googlecode.com/files/bench.zip 

2. Start ‘cake schema create’ 

3. Populate test data by calling /articles/generate 

4. Run test by calling /articles/bench