Tag: cakephp 1.2

CakePHP and Aptana Cloud = Love at First Sight

Before I write a post, I usually test the code I write about… and try to copy/paste it the best I can… so I don’t look like an ass later on.

This post, however, I simply cannot prepare for…

I’m just way too excited (yes, even more than Pacquiao’s dismantling of De La Hoya last week) about the topic, which deals with CakePHP in a littler manner, but mostly focuses on my new found love: “Aptana Cloud”.

Aptana IDE has been my choice in web development for a little while now (Eclipse users take note, all other IDE users… at least give it a shot ;)). It has solid support for JS, CSS and PHP. Thankfully, I don’t care about Java anymore, thus it works out perfectly well. Just the ability to insert a color hex value from a tiny built-in pallet, while working on a CSS file… puts a smile on my face every time.

I do love such attention to detail, but just to make things a little better, it also has support (i.e. auto-complete, proper highlighting and other time-saving features for coders) for jQuery (which I use) as well as top popular JS frameworks such as ExtJS, Prototype, … etc., so it appears like the Aptana team is “on top of web dev things” ;)

Alright, that seems like a few good-enough reasons to make Aptana my top IDE.

Well… all of a sudden (to me, which means I’m way behind the trends) Aptana folks introduced a cloud computing service, that kicks major ass as far as I can see… while testing it for about two days.

So this post will be just about my ventures into Aptana Cloud, setting up a cake app and being all happy about the potential with such a service.

(… guess I should have prefaced this post by saying that I’m not a paid actor :))

Alright… so first, if you’d like, you can read about Aptana Cloud (and watch a nice little video) here: http://www.aptana.com/cloud.

Now, I’m pretty much going to outline my baby-steps with Aptana Cloud + CakePHP and what I did (and learned a little) to launch a CakePHP application on Aptana Cloud.

1.Why bother and what is this all about?

To me, when working with multiple clients or multiple developers (or even on a single project), management of deployment/synchronization and consistency between development, staging and production somehow always caused a little pain. (I hope most people follow dev->stage->production release cycle ;)) Of course, if you have IT guys, or your are an admin systems guru, managing servers becomes a lot easier, but even still… if one is focused on web development, is it worth the time and effort to somehow worry about the state of your server (Email, Database, SVN, this, that, etc.)? (To me, at least, it always seemed like a boring and mundane task, sorry to all the geeky sysops ;) ).

There had to be a better way…

2.Creating a cloud… and whatta f… is this “cloud”?

Somehow I found out about the Aptana Cloud in my IDE…

After reading up on the service… I thought something along the lines of: “Well, that might address my pain just mentioned above… why not give it a shot?”.

Without boring you with anymore details in the IDE it’s basically 3 clicks and your cloud is setup…

… Which means, you’ve got a fully working:

Staging Server, Production Server and Private Server.

They are all up and running with SVN, SSH access, Postfix (for outgoing emails only, as far as I know). Oh yeah, all up and ready in *real-time*… and $0 for you to spend. (GTFO, you might think)… well, you get a 21 day free-trial. (Don’t even have to pull out a CC… that’s people thinking about doing things right… another smile).

3.So how to get my app running?

Well, I SSH’d into the “virtual” (cloud) server (right from the IDE too;)) and probably did a bunch of things wrong, clicked around here and there and after deliberation decided to do the following…

Obviously I need the cake core up there, and the default doc root is:


/home/aptana/homes/teknoid/staging/web/htdocs

I thought the “right” way to install an app in this scenario was to put the cake core in the “web” directory and then *sync* my app into htdocs.

After that, I’d set the doc root (which is easily changed right in IDE) to:


/home/aptana/homes/teknoid/staging/web/htdocs/webroot

4.Getting the cake core installed

After kicking around some ideas for a while, I decided to use the latest SVN repo from CakePHP. Thankfully, the SVN is already good to go in the cloud so in my:


/home/aptana/homes/teknoid/staging/web/

I did:


svn checkout https://svn.cakephp.org/repo/branches/1.2.x.x/cake/

Alright, I get the cake core in the place where I hope it should be.

5.Syncing up the app from Aptana

Now I need to get my project (app) uploaded to the staging server. That’s what I think I did next…

Right click the project name in my IDE’s project browser, went to “Synchronize” (you can decide if you wish to sync up your local or development project to public or staging server).

The IDE takes a “minute to think” and then it creates a sync-up dialog. Since it was my first time uploading the project, all files had to be created and after some “syncing up” my app was ready to run on the test server. And it did! :)

6.Following up

I realized that syncing-up app/config and app/tmp directories (as some other folders/files) probably isn’t a good idea, and thankfully you can easily add such exceptions in the IDE.

….

Well, that covers my little venture into CakePHP and Aptana Cloud. As you can guess so far it has been a very positive experience.

7.Some random notes

  1. First and foremost, to me, is the “team” option… Say, I have a project that I cannot finish due to laziness or whatever other strange reasons. I can easily “delegate” the coding to someone else with a simple SVN account setup and possibly some free beer. That type of “delegation and collaboration” pain is simply removed from the equation.
  2. I found that Apatna team is very responsive and open to suggestions, so if you feel like sharing some ideas on how it could benefit the community and CakePHP (but more importantly, your exciting life as a web developer), throw them on the Aptana forums or even here.
  3. I’m still very early in my testing, there are probably lots of things I miss or don’t know yet.
  4. The service is improving daily, so I think following the solid growth patterns of CakePHP, these kinds of projects I do really enjoy because it’s an amazing learning experience.
  5. I’ve tested the “sync” option numerously and it works quite well, I’ve not found a single problem… other than that it’s a bit slow (at least for me) and I could see about 3 UI improvements :)
  6. Give it a shot!
  7. Git support should be there any day now ;)
  8. I think the “Private” server is meant for your own stats and overall cloud management (i.e. don’t sync your CakePHP projects there).
  9. Yep, you get tools like phpMyAdmin to manage your DB (or internal IDE DB browser), all the obvious that you need to use the terminal… and well so far that’s all I’ve needed :)
  10. And I hear that there are some very interesting things coming down the line in Aptana… concerning some PHP frameworks, the IDE and the app deployment ;)

CakePHP URL-based language switching for i18n and l10n (internationalization and localization)

Update (08/03/2012): Dorin M. has refactored this solution for 2.x series, look for the code here. For details of what’s going on, read on below.

———–

I should preface this post by saying that it does not cover the basics of i18n and l10n so, please, first take a look at the manual on how to get the basics going.

To better understand the goal and why some things were done the way they were, I’ll summarize the requirements:

  1. The app has to support two languages or more (in this case English and Russian)
  2. Default language is English
  3. The language switching is based on a URL param
  4. The URL format should be: example.com/eng/controller/action
  5. Language choice should persist in the session and a cookie

Just a note here… there are other ways to determine the language requested by the user, for example it could come from a domain name like eng.example.com or rus.example.com. Hopefully the approach outlined here will also be helpful if other methods of language switching are used in your app…

Also, worth while to mention, that having language name in the URL (as opposed to just reading it from the session or cookie) helps with SEO… I won’t bore you here with details, but basically it helps to ensure that a variation of each page, based on the language param in the URL, is properly indexed by the search engines. Thus, each indexed page can be found later in the native language of the user.

Last, but not least, CakePHP uses three letter language name abbreviation, based on this, so I figure, should be fine to use the same in the URL’s.

Alright, so looking at the URL format, instantly raises a question… how do we tack on the language name to the “front” of each URL?

Thankfully the Router accomplishes that pretty easily (in app/config/routes.php):

Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => '[a-z]{3}'));

It takes a ‘language’ parameter and throws it to the front of the URL, just as we need it.

Now, we need to specify a default language to use, I just add this to my app/config/core.php

Configure::write('Config.language', 'eng');

So, when someone comes to the http://example.com/users/home, the site is displayed in English by default. Then we usually see a link somewhere (with a little flag next to it :)), to switch to another language.

In cake we can make those language-switching links like this:

$html->link('Русский', array('language'=>'rus'));

Notice that we set the language param, which we’ll rely on to do our switching. Providing no other params, will simply reload the current page (in the new language) with the param tacked to the front of the URL (more about this later).

Side note, it’s not a good idea to use the __() translation function on language-switching links… If I get to the site and it’s displayed in the language I can’t understand even remotely, the only savior would be a link in my native language, which indicates that i can switch to it (and well, a little flag would help too :))

So now we actually need the code to switch the language, when a user clicks on the link, like above.

It’s best done in the App Controller, kinda like here:

    var $components = array('Session', 'Cookie');

    function beforeFilter() {
        $this->_setLanguage();
    }

    function _setLanguage() {

        if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
            $this->Session->write('Config.language', $this->Cookie->read('lang'));
        }
        else if (isset($this->params['language']) && ($this->params['language']
                 !=  $this->Session->read('Config.language'))) {

            $this->Session->write('Config.language', $this->params['language']);
            $this->Cookie->write('lang', $this->params['language'], false, '20 days');
        }
    }

Let’s take a look at the code quickly and consider some scenarios…

I created a separate method _setLanguage();, the reason I like doing this is that it keeps the beforeFilter() cleaner, which already has enough crap in there usually.
Secondly, it can be overridden in the child controllers, if required.

So let’s consider some user-case scenarios:

  1. The user comes to the site for the very first time

    In this case the default language is read from the core.php file, so the site is set to English

  2. The user starts clicking around the site for the very first time in his native English

    Nothing really needs to be done, so we can happily skip that part

  3. The user comes to the site and has to switch the language to Russian

    Thankfully he sees a link to do so, and clicks on it. Now we check our else if, since no cookie or session with configured language exist yet. We see that the link has a /rus/ param in the URL and it is not yet stored in the session, therefore we write the new value of the default language to the session and the cookie.

  4. The above user browses around the site, leaves, and then comes back

    The session value is still present and therefore the site is automagically translated to Russian. This is good if the user forgot or doesn’t care to use links like example.com/rus/controller/action, because even plain links like example.com/controller/action will display the site in the right language because of the session value.

  5. The above user closes the browser, goes out hunting for wild boars, and comes to the site on some other day

    Now we rely on our previously stored cookie to read in the language and ensure we don’t override anything that might be in the session already. (the first if )

  6. Now if the user decides to read the site in English

    We pretty much follow through the same steps as above.

Now the last thing we need to do is to ensure that a URL param gets automatically added to all the links on the site, if a given language is chosen. Remember, that this is important to have such links for SEO as well.

Well, we’re sure as hell not going to supply the ‘language’ param manually to each link, so let’s override the cake’s default url() method to ensure the language param is now added to all links.

We create app_helper.php in /app/ (same place for app_controller.php and app_model.php), something like this:

class AppHelper extends Helper {

   function url($url = null, $full = false) {
        if(!isset($url['language']) && isset($this->params['language'])) {
          $url['language'] = $this->params['language'];
        }

        return parent::url($url, $full);
   }

}

Basically we check if ‘language’ param is already in the URL if it is, we don’t need to worry about it.
If not, and $this->params['language'] is available we pre-pend the required language to the URL.
The rest of the site, and all standard links will now include that ‘language’ param at the front of the URL (again, good for SEO).

And that’s pretty much it, even though the post was a bit long-winded (and beer to you, if you’ve made through the whole thing) it is quite nice to be able to do i18n & l10n in just about 15 lines of code.

A little disclaimer: even though the code seems to work fine, it is still experimental… so if you find some problems I haven’t yet encountered, please be sure to let me know.

P.S. Here’s a sampe test view, from which you can generate your .po files (easily done with cake i18n console command, but this is a topic for another tutorial and there are plenty of them “out there”).

<?php
    __('This is only a test message');
?>

<p>
    <?php echo $html->link(__('Regular link', true), array('action'=>'test')); ?>
</p>

<p>
    <?php echo $html->link(__('Regular link two', true), array('controller'=>'users', 'action'=>'test5', 'some stuff')); ?>
</p>

<p>
    <?php echo $html->link('English', array('language'=>'eng')); ?>
</p>

<p>
    <?php echo $html->link('Русский', array('language'=>'rus')); ?>
</p>

You’d probably want to switch your language, if you are not so good with Russian ;)

Make your CakePHP forms a lot more secure

Update: 11/06/2008
Tarique Sani pointed out that I had an extra line of code, which wasn’t necessary to make all this work (perhaps an old habit, but the post has been modified to reflect the change).
———————

My recent post started up some good conversations and I figured that a good follow-up would be an example of how to use the Security component to make your forms much more… secure.

We’ll start with a basic usage and then expand a little…

First let’s assume a basic model:

class User extends AppModel {

  var $name = 'User';

  var $validate = array(
      'name'=>array('rule'=>'notEmpty'),
      'email'=>array('rule'=>'email'),
      'password'=>array(
              'Cannot be empty' => array('rule'=>'notEmpty'),
            'Must be at least 4 chars' => array('rule'=>array('minLength', 4))
      )
  );
}

We’ve got some basic validation rules defined (and note, there is no ‘required’=>true, which was the point of confusion and problems for some people).

Now we’ll assume that we have an evil user, who wants to tamper with our form fields and bypass the ‘name’ field, for example, to save blank data into the DB. If we leave our form unsecured, it is very easy to remove the field from the data array (by using some basic hacking tool) and since it won’t be present in the array, the validation will be skipped and the form data will be saved with a blank name.

So how can we avoid this problem, by using the Security component?

Let’s build our controller:

class UsersController extends AppController {

    var $name = 'Users';
    var $components = array('Security');

    function add() {
        if(!empty($this->data)) {
            $this->User->save($this->data);
        }
    }
}

And a basic view for the add action:

echo $form->create();
echo $form->inputs(array('name', 'email', 'password'));
echo $form->end('Register');

Pretty simple, right?
We’ve made our form quite secure with only one “extra” line of code:
var $components = array(‘Security’);

Let me explain exactly what happens behind the scenes…

The Security component will create a hash based on the form fields produced by our Form Helper. If someone tampers with the form fields (by adding or removing or changing any field), the hash is not going to match with the expected one and the add() action will fail.

Yep, it’s that simple. You are welcome to try to mess around with the form by using your favorite POST-modifier tool (maybe: https://addons.mozilla.org/en-US/firefox/addon/1290, thanks to Jonah for providing the link).

You’ll notice that the action will fail without any error message (you’ll just get a blank screen in most cases). In my opinion, that’s just fine for any evil user… why make the app user-friendly for them?

Well, let’s be nice and make it a bit more user-friendly. It’s a good exercise, if anything…

First, we’ll extend our controller a little by creating a beforeFilter() method, let’s add one more line of code to it:

  function beforeFilter() {
        $this->Security->blackHoleCallback = 'fail';
  }

The $this->Security->blackHoleCallback = ‘fail’; tells the Security component to call our custom fail() method, in case the action gets black-holed, which it will if someone tampers with the form.

So let’s create the fail() method:

 function fail() {
        $this->cakeError('youSuck');
    }

Alright, as you can see this method will in turn trigger the youSuck() error method, which we’ll need to build in our custom app_error.php handler:

So, if you don’t have one, create app_error.php in your /app/ root directory (this is where you define custom error methods, or override existing ones).

class AppError extends ErrorHandler {
    function youSuck() {
        $this->controller->set(array(
             'name' => __('You are an evil person', true)
         ));

        $this->__outputMessage('bad_user');
    }
}

And now we need a new view bad_user.ctp, which is placed in /app/views/errors/bad_user.ctp

<h2><?php echo $name; ?></h2>
<p class="error">
  <strong><?php __('Error'); ?>: </strong>
  <?php  __('Do not try that again!'); ?>
</p>

Now, when someone tampers with the form, they’ll get a nice error page suggesting what we really think of hackers.

Hopefully you can see how Security component can make your app a lot more secure with just 2-3 extra lines of code. And at the same time we’ve covered a little example of how to best handle custom errors.

P.S. You can make your form even more secure by supplying additional params to save().

A cleaner way to send emails

CakePHP 1.2 provides a very easy way to send emails using the built-in Email component. However, sending emails directly from your User’s controller add action, for example, is somewhat ugly. It’s better to write a custom method in your app controller to handle the sending of emails.

Here’s an example, add this to your app controller:

[sourcecode language='php']
function _sendEmail($templateName,
$emailSubject,
$to,
$from = ‘My emailer ‘,
$replyToEmail = ‘noreply@yourdomain.com’,
$sendAs = ‘both’ ) {

$this->Email->to = $to;
$this->Email->subject = $emailSubject;
$this->Email->replyTo = $replyToEmail;
$this->Email->from = $from;
$this->Email->template = $templateName;
$this->Email->sendAs = $sendAs;

return $this->Email->send();
}
[/cc]

(Update: Thanks Jan Seidl for suggesting the return for this function, so that the response can be captured).

Note: don’t forget to add var $components = array(‘Email’); to your app controller as well.

The first three params you pass to the _sendEmail() method are likely to change, so we’ll pass those first. The rest you can set as defaults and only pass them if the defaults need to be changed.

Now, using the example of User controller add action, you can send the email using something like:

[sourcecode language='php']
$this->_sendEmail(‘user_reg’, ‘User registration’, $this->data['User']['email']);
[/cc]

It is very likely that you’d want to set some variables to be sent in the email. Well, setting the variables for the email is just the same as you would for any view (after all the email template is a view). So before calling _sendEmail() you might do:

[sourcecode language='php']
$this->set(‘email’, $this->data['User']['email']);
[/cc]

Now the $email variable is available for use in your email template.

Using this approach keeps your controller’s action nice and clean.

P.S. You want to keep this method protected by prefacing the name with a “_”, so that it cannot be accessed by the URL. It is always a good idea to do so for any utility functions that need not to be accessed by the URL.