Code-Completion Console Script

Many good IDEs for Webdevelopment cannot automatically understand the structure of a framework and its classes/objects.
They need a small file initializing the objects so that they know in which context they appear.

E.g.:
$this->Session (Session Helper)
its impossible for the IDE to know that the the helpers are inside the View class.
Same goes for controller components and model behaviors

I wrote a little script which produces mockup code. My IDE "PHPDesigner" works very well with it.

Usage

Drop this script into the /shells folder (either vendors or app).
Now just type cake cc inside the cake shell.

This will create the code completion script in your APP folder. For projects created via PHPDesigner this file will automatically be parsed and the code completion should work out of the box (the parsing can take a few minutes depending on the project size).

Code

<?php

App::import('Core', 'Folder');
App::import('Core', 'File');

/**
 * Code Completion
 * 2009-12-26 ms
 */
class CcShell extends Shell {
    var $uses = array();

    private $content = '';

    function main() {
        $this->out('AutoComplete Dump');

        //TODO: ask for version (1.2 etc - defaults to 1.3!)

        $this->filename = APP.'code_completion__.php';

        # get classes
        $this->models();
        $this->components();
        $this->helpers();
        //TODO: behaviors

        # write to file
        $this->_dump();

        $this->out('...done');
    }

    /**
     * @deprecated
     * now use: Configure::listObjects()
     */
    function __getFiles($folder) {
        $handle = new Folder($folder);
        $handleFiles = $handle->read(true, true);
        $files = $handleFiles[1];
        foreach ($files as $key => $file) {
            $file = extractPathInfo('file', $file);

            if (mb_strrpos($file, '_') === mb_strlen($file) - 1) { # ending with _ like test_.php
                unset($files[$key]);
            } else {
                $files[$key] = Inflector::camelize($file);
            }
        }
        return $files;
    }

    public function _getFiles($type) {
    $files = App::objects($type);
    # lib
    $paths = (array)App::path($type.'s');
    $libFiles = App::objects($type, $paths[0] . 'lib' . DS, false);

    $plugins = App::objects('plugin');
    if (!empty($plugins)) {
      foreach ($plugins as $plugin) {
         $pluginFiles = App::objects($type, App::pluginPath($plugin) . $type.'s' . DS, false);
          if (!empty($pluginFiles)) {
              foreach ($pluginFiles as $t) {
                  $files[] = $t; //"$plugin.$type";
              }
          }
      }
    }
    $files = array_merge($files, $libFiles);
    $files = array_unique($files);

        $appIndex = array_search('App', $files);
        if ($appIndex !== false) {
            unset($files[$appIndex]);
        }

        # no test/tmp files etc (helper.test.php or helper.OLD.php)
    foreach ($files as $key => $file) {
            if (strpos($file, '.') !== false || !preg_match('/^[\da-zA-Z_]+$/', $file)) {
                unset($files[$key]);
            }
        }
    return $files;
    }

    function models() {
        //$files = App::objects('component', null, false);
        $files = $this->_getFiles('model');
        //$files = $this->_getFiles(COMPONENTS);

        $content = LF.'<?php'.LF;
        $content .= '/*** model start ***/'.LF;
        $content .= 'class AppModel extends Model {'.LF;
        if (!empty($files)) {
            $content .= $this->_prepModels($files);
        }
        $content .= '}'.LF;
        $content .= '/*** model end ***/'.LF;
        $content .= '?>';

        $this->content .= $content;
    }

    function components() {
        $files = $this->_getFiles('component');

        $content = LF.'<?php'.LF;
        $content .= '/*** component start ***/'.LF;
        $content .= 'class AppController extends Controller {'.LF;
        if (!empty($files)) {
            $content .= $this->_prepComponents($files);
        }
        $content .= '}'.LF;
        $content .= '/*** component end ***/'.LF;
        $content .= '?>';

        $this->content .= $content;
    }

    function helpers() {
        $files = $this->_getFiles('helper');

        $content = LF.'<?php'.LF;
        $content .= '/*** helper start ***/'.LF;
        $content .= 'class AppHelper extends Helper {'.LF;
        if (!empty($files)) {
            $content .= $this->_prepHelpers($files);
        }
        $content .= '}'.LF;
        $content .= '/*** helper end ***/'.LF;
        $content .= '?>';

        $this->content .= $content;
    }

    function _prepModels($files) {
        $res = '';
        foreach ($files as $name) {
            $res .= '
    /**
    * '.$name.'
    */
    public $'.$name.';
'.LF;
        }

        $res .= '	function __construct() {';

        foreach ($files as $name) {
            $res .= '
        $this->'.$name.' = new '.$name.'();';
        }

        $res .= '}'.LF;
        return $res;
    }

    function _prepComponents($files) {
        $res = '';
        foreach ($files as $name) {
            $res .= '
    /**
    * '.$name.'Component
    */
    public $'.$name.';
'.LF;
        }

        $res .= '	function __construct() {';

        foreach ($files as $name) {
            $res .= '
        $this->'.$name.' = new '.$name.'Component();';
        }

        $res .= '}'.LF;
        return $res;
    }

    function _prepHelpers($files) {
        # new ones
        $res = '';

        foreach ($files as $name) {
            $res .= '
    /**
    * '.$name.'Helper
    */
    public $'.$name.';
'.LF;
        }

        $res .= '	function __construct() {';

        foreach ($files as $name) {
            $res .= '
        $this->'.$name.' = new '.$name.'Helper();';
        }

        # old ones
        $res .= ''.LF;
        /*
        foreach ($files as $name) {
        $res .= '
        $'.lcfirst($name).' = new '.$name.'Helper();
        ';
        }
        $res .= LF;
        */

        $res .= '	}'.LF;

        return $res;
    }

    function _dump() {
        $file = new File($this->filename, true);

        $content = '<?php exit();'.LF;
        $content .= '//Add in some helpers so the code assist works much better'.LF;
        $content .= '//Printed: '.date('d.m.Y, H:i:s').LF;
        $content .= '?>'.LF;
        $content .= $this->content;
        return $file->write($content);
    }
}

?>

Result

The file will look like:

class AppModel extends Model {
    /**
    * Address
    */
    public $Address;
    ...

    function __construct() {
        $this->Address = new Address();
        ...
    }
}
class AppController extends Controller {
    /**
    * AclComponent
    */
    public $Acl;
    ...
    function __construct() {
        $this->Acl = new AclComponent();
        ...
    }
}
class AppHelper extends Helper {
    /**
    * AjaxHelper
    */
    public $Ajax;
    ...
    function __construct() {
        $this->Ajax = new AjaxHelper();
        ...
    }
}

Final notes

Feel free to update the TODOs and send me the improved file. A test file would be awesome, too, i guess 🙂

For LF constants see my article about "bootstrap goodies".

Update 2012-12-23 ms

For Cake2.x take a look at the new version of it in my Tools Plugin.

0.00 avg. rating (0% score) - 0 votes

3 Comments

  1. Works like a charm in PhpStorm2.1 So nice to be able to use autocomplete properly in the Cake environment!

    This is incredibly helpful. Thanks so much for putting this online.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.