Writing a custom CakePHP console script

Published on May 07, 2007 and tagged with cakephp  console

As you may have noticed, last weekend all command line related files in CakePHP 1.2 have been moved from /cake/scripts to /cake/console. Coupled with this move was a change in the way you use the command line scripts. Instead of

php bake.php -app /path_to_app_dir

you now have to use

./cake bake -app /path_to_app_dir

to run the bake script. The ACL script can be used in a similar way.

This switch to the new console is probably the end of bake2, as it doesn’t (yet) work with the console, and the console provides a similar functionality as bake2, i.e. the console allows you to execute your own command line scripts. In the meantime the bake2 script has been removed from the repository. (Update 2007-05-15: The bake2 script has been removed from the repository)

Writing such a command line script (or shell scripts as they are called in CakePHP) is easy, so let us write a simple script which creates a file specified by the user. The custom scripts are placed in /app/vendors/shells, if they are specific to an application, or in /vendors/shells, if they are generic.

In our case it doesn’t matter where we place the file as it is only an example, so we put it to /vendors/shells. A shell class has to extend the class “Shell” and the class name has to end with “Shell”. All public functions of a shell class are treated as “commands”, i.e. they can be called directly via command line. What the “index” function is for controllers, is the “main” function for shell scripts. It is automatically called if you don’t specify a different command.

In our case we also have to override the “initialize” function, as we don’t have a database.php file in app/config (else we would get an error). The code for our shell script is straight-forward and should be self-explanatory:

// vendors/shells/demo.php
class DemoShell extends Shell {

    function initialize() {
        // empty
    }

    function main() {
        $this->out('Demo Script');
        $this->hr();

        if (count($this->args) === 0) {
            $filename = $this->in('Please enter the filename:');
        } else {
            $filename = $this->args[0];
        }
        $this->createFile(TMP.$filename, 'Test content');
    }

    function help() {
        $this->out('Here comes the help message');
    }
}

This script can now be executed in one of the following ways:

./cake demo  // asks the user for a file name
./cake demo test.txt   // uses the specified file name
./cake demo help   // shows the help message

Happy baking :)

Update (2007-05-13): Adapted to the latest changes in the code (CakeScript has been renamed to Shell)

33 comments baked

  • Brandon May 07, 2007 at 20:13

    I smell a new package installer … CakeBox perhaps? :)

  • mazoo May 07, 2007 at 20:42

    Hi Daniel, first, thanks a lot for your efforts!

    The idea with the demo script is nice. I would like to write such a script and use it from crontab, doing some periodic update/insert with my models. Now u mentioned the initalize function. Would it, if not overridden, connect to my database? And what approach should be used to work with Models (find,save,delete,…)?

    Good night! ;)
    mazoo

  • Mike May 08, 2007 at 01:48

    Hi Daniel,

    that looks very promising! And I was wondering the same thing as mazoo. Maybe you could follow up with how one could work with db models? Please? :)

    Thanks for your blog!

  • nate May 08, 2007 at 03:02

    Small correction to the way scripts are called. While it is valid and possible to call scripts like

    ./cake bake -app /path_to_app_dir

    as you have shown above, this defeats half the purpose of having a shell script wrapper. Instead, you can do the following:

    cd /path_to_app_dir
    ./cake bake

    That way, you can execute multiple commands without having to continually re-type your app path.

  • Darian May 08, 2007 at 14:34

    Nate,

    I’m confused. The cake script lives at cake/scripts/cake. How would one call it from app/ as ./cake?

  • nate May 08, 2007 at 17:08

    You add cake/console to your system path. (It was moved from cake/scripts to cake/console.)

  • cakebaker May 08, 2007 at 19:02

    @all: Thanks for your comments!

    @mazoo, Mike: At the moment it seems that the functionality to access the db is broken due to a bug, see https://trac.cakephp.org/ticket/2544. When this bug is fixed, I will write a follow-up.

    @nate: Thanks for the correction.

  • Dieter@be May 10, 2007 at 18:57

    How does it differentiate between function name and argument for the default function?
    in your example:
    [quote]
    ./cake demo test.txt // uses the specified file name
    ./cake demo help // shows the help message
    [/quote]
    what if “test.txt” was meant to be an action that needs to be called, or “help” the argument for the main function?
    Does cake search for all available functions first, and when it doesn’t find any match, it uses it as argument for the main function?

  • http://getopenid.com/carlosz May 11, 2007 at 05:24

    Cool, any guess on the possibility to have an interactive shell like RoR has?

    This days i’ve been looking for one, since the one that comes with php5 really sucks. But this one works great: http://jan.kneschke.de/projects/php-shell

    maybe they could implement something like it. :)

  • cakebaker May 11, 2007 at 08:56

    @Dieter: Yes, first it looks whether there is an action with the specified name. If there is no such action, the main action is used. To use a parameter in the main action with the same name as an action, you have to specify the action name explicitly:

    ./cake demo main help  // passes help as parameter to the main action
    ./cake demo help  // executes the help action
    

    @Carlos: I don’t know what features are planned for the console. If you think something could be useful in CakePHP, don’t hesitate to open an enhancement ticket on https://trac.cakephp.org

  • cakebaker » Shells and Tasks May 16, 2007 at 07:23

    [...] scripts are now called “Shells” instead of “CakeScripts”, so I adapted the previous article to those [...]

  • mjk November 26, 2007 at 19:14

    Hello Everybody,

    Could anybody please tell me if there is way where a console script could be used to access models. I am thinking of an approach where I could use something like “loadModel” in the console script file.

    Thanks in advance for the help! :)

  • cakebaker November 27, 2007 at 18:24

    @mjk: The answer is already in the question ;-)

    Use something like:

    loadModel('MyModel');
    $myModel = new MyModel();
    // do something with the model
    

    HTH

  • mjk December 05, 2007 at 15:31

    Thanks for the reply! But I think something is wrong with how I used the app_model as it creates an error when I load a model from the cake script:

    Fatal error: Class ‘CakeSession’ not found in c:\wamp\www\cake\src\apps\mjk\app_model.php on line 10

    that line corresponds to a line within the constructor in the app_model.php

    function __construct($id=false, $table=null, $ds=null)
    {
    // If the database was not specifically set, then use the value from the logged in user
    if ($this->useDbConfig == ‘default’)
    {
    $session = new CakeSession();
    $sessionVars = $session->__returnSessionVars();
    if (isset($sessionVars['Auth']['User']['databasename']))
    {
    $this->useDbConfig = $sessionVars['Auth']['User']['databasename'];
    }
    else
    {
    // User may ask for models they do not have access to, just ignore request
    return;
    }
    }

    parent::__construct($id, $table, $ds);
    }

    … and line 10 is $session = new CakeSession();

    Do i need to call the Cake Session in the cake script? If so can anybody please help me how to do it?
    I have just been working with cakephp for 4 months now and I’m not yet attuned to working on the “deeper” parts of the framework. Thanks in advance. and more power to cakephp and the people who use it! :)

  • cakebaker December 06, 2007 at 19:44

    @mjk: You can probably use the uses() function to load the CakeSession class:

    uses('session');
    

    But I am not sure whether there is a session available on the command line, probably not.

  • mjk December 12, 2007 at 09:26

    Thanks a lot for the tip! I have dropped this part of my project for a while to move on to other things.

  • mjk December 12, 2007 at 09:28

    thanks a lot for the tip. I have dropped this part of my project for the meantime. I’ll try to do as suggested when I come around it again. Thanks again.

  • cakebaker December 12, 2007 at 10:34

    @mjk: Good luck :)

  • Matt McDonald’s Website » My del.icio.us bookmarks for December 10th through December 12th December 13, 2007 at 15:24

    [...] cakebaker » Writing a custom CakePHP console script – [...]

  • ใช้ CakePHP จาก command line interface (terminal, console, etc.) « वीर April 22, 2008 at 11:56
  • Matt Huggins August 16, 2008 at 04:21

    I’d like to build a console script in Cake that sends emails (newsletters) to users via a cron job. In doing so, I’d like to create a newsletter view template. I see that there is a “View” shell task, but if I’m not mistaken, it looks like this is intended to be used for baking a new project, not for accessing views within a custom shell. So my question is, is there an easy way to access views from a shell console script?

  • cakebaker August 18, 2008 at 17:54

    @Matt: The “View” shell task is for creating new views, not for accessing views from within a shell script.

    To access a view you could try something like:

    class DemoShell extends Shell {
        public function initialize() {
            // empty
        }
    		
        public function main() {
            App::import('Core', array('View', 'Controller'));
            $view = new View(new Controller());
            $viewContent = $view->render('/controller_name/view_name');
        }
    }

    Hope that helps!

  • Matt Huggins August 18, 2008 at 20:02

    Ahh, right…I’m so used to using App::import for nothing other than vendor files, but I remember seeing this elsewhere now that you mention it. Thanks a bunch! :)

  • cakebaker August 19, 2008 at 08:14

    @Matt: You are welcome!

  • calzone November 20, 2008 at 21:14

    Aside from the cake manual, this is apparently one of only two pages on the internet that really discusses using the console for something other than baking.

    Unfortunately, I haven’t seen anyone actually describe how you can invoke and use your controllers from the console. The trouble I’m having is that if I do the following code, the controller action executes just fine, but in that action, there is a call to the model which uses findAll(). php throws “Fatal error: Call to a member function on a non-object .”

    Am I wrong in thinking the biggest selling point of the console is to be able to script and automate the running of your standard controller actions site-wide from behind the scenes? If I can’t invoke a model method from an imported controller, what’s the point of importing a controller at all since with precious few exceptions, virtually all controller actions use a model method? Why can I access the model and all it’s methods from the shell yet I can’t access those same methods from a controller that I’m trying to trigger from the same shell?

    Is the only way to accomplish this to re-write all my desired controller code as Shell code?

    <?php 
    class ReportShell extends Shell
    {
    	var $uses = array('City');
    	var $tasks = array('Project',
    				'DbConfig',
    				'Model',
    				'Controller',
    				'View',
    				'Plugin');
    
    	var $CitiesController = null;
    		
    	function initialize()
    	{
    		$this->_loadModels();
    	}
    
    	function main() 
    	{
    #		$city=$this->City->find(array('name'=>'Boston'));
    #		$this->out(pr($city));
    
    		App::import('Controller', 'Cities');
    		$this->CitiesController = new CitiesController();
    		$this->out($this->CitiesController->get_city('Massachusetts'));
    	}
    }
    ?>
  • cakebaker November 21, 2008 at 17:39

    @calzone: Thanks for your comment!

    To avoid the “Call to a member function on a non-object .” error you have to initialize the controller after it is instantiated:

    $this->CitiesController = new CitiesController();
    $this->CitiesController->constructClasses();
    ...

    Hope that helps!

  • calzone November 21, 2008 at 18:56

    Brilliant cakebaker!

    I’ve been chasing my tail over this one for a couple days now and I just knew it had to be some key element I was missing somewhere.

    Thank you so much, your help has been invaluable.

    –btw, happy holidays!

  • cakebaker November 26, 2008 at 18:17

    @calzone: I’m glad I could help!

  • Ulises Figueroa May 30, 2009 at 00:09

    I set up a Shell and a Task, Im using a Model to reach some data and send an email. But when I execute the shell I got the following error:

    Error: Missing Database Connection. Try ‘cake bake’

    This is the code for the Shell and Task

    class MemberShell extends Shell {
    var $tasks = array(‘NotifyNewMessage’);

    function main() {

    pr(‘Aqui va’);
    $this->NotifyNewMessage->execute();
    }
    }

    class NotifyNewMessageTask extends Shell {
    var $uses = array(‘Message’, ‘Member’);
    var $components = array (‘MemberMailer’);

    function execute() {
    //Reach data and send Email
    }
    }

  • Ulises Figueroa May 30, 2009 at 00:39

    It works now, I changed the app directory when calling the script from shell.

    /home/user/public_html/website/cake/console/cake member -app /home/user/public_html/website/app

    But now it doesnt show up any message, how do I debugg the script from shell ?? Any ideas?

  • cakebaker June 01, 2009 at 10:42

    @Ulises: Good to hear you could figure out your first issue in the meantime :)

    What do you mean with “it doesn’t show up any message”? Do you mean the output of the “pr” function is not shown? Is it possible that the debug level is set to 0? In that case the “pr” function doesn’t output anything by design (see cake/basics.php). Try to add the following snippet to your script:

    Configure::write('debug', 1);
    

    Hope that helps!

  • Ulises Figueroa June 02, 2009 at 18:59

    @cakebaker: I mean that even though I have set the Debug Mode to 1 or 2 I doesnt shows up any message from pr() or echo. I’m not getting any error from Linux bash nor the CakePHP shell. :S

  • cakebaker June 03, 2009 at 17:52

    @Ulises: Hm, no idea why it doesn’t work on your system… Do you get any output if you use $this->output(’some text’);?

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License