Google Hosting Popular JS Libraries

I saw this randomly this morning, but I can't find the source.

In any case, Google are now hosting many popular JavaScript libraries (jQuery, prototype/script.aculo.us, MooTools, dojo) on their own servers - cached, compressed and minified, ready to consume.

It's as simple as you expect:
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"></script>

(though, there's a longer, "better" way to do it if you're that way inclined, also)

See it here and the announcement at AJAX Search API Blog

Wednesday, May 28. 2008

2 Comments

sfPropelParanoidBehavior vs Propel 1.3

Just a quick one, mostly for my sake.

If you're trying to use sfPropelParanoidBehavior with (recent? Since r474 (see http://propel.phpdb.org/trac/changeset/474)) Propel 1.3, you'll notice the selects aren't filtering out the deleted items.

Easy fix!

In plugins/sfPropelParanoidBehavior/config/config.php, change (on line 9) Peer:doSelectRS to read Peer:doSelectStmt. Leave the doSelectRS in the array, that's the callback in the plugin which is fine.

Friday, March 14. 2008

0 Comments

Custom Caching in symfony

Just quickly, because it's handy and simple.

I'm building a page that's reading a twitter-feed to display on the front page. I don't want to be hitting twitter on every page hit though (obviously), so we need to cache the data. Could it be any simpler? (Thanks symfony! *cheesy wink*)

Let's dive into the code:

<?php
public function executeIndex()
{
    $cache = new sfFileCache(array('cache_dir' => '/tmp/twitter'));

    if (!$cache->has('tweet'))
    {
        $status = TwitterFeed::getLatestTweet(sfConfig::get('app_twitter_user_id'));
        $cache->set('tweet', $status, 600); // 10 mins cache expiry
    }

    $this->status = $cache->get('tweet', '');
}


Holy crap, that was too easy!

That said, it'd be stupid if it were any harder.

Now, a few tweaks you could do. You could, instead of caching to /tmp, you could cache to sf_cache_dir, so that a symfony cc would also clear that cached data. Could be handy, but might not be what you want.

I suppose if you weren't so lazy, you'd pull those out into app.yml variables. I would, if I were you! It's a pity I'm lazy, really.

Thursday, February 21. 2008

0 Comments

Symfony 1.1 Form Templating

So, I'm tired of how the forms look in symfony 1.1. I mean c'mon, tables? We can do better than that.

Note: we can do better than my solution too, I'm sure, but this is way better than tables!

So inside of /lib/widget, I've created sfWidgetFormSchemaFormatterJosh.class.php which looks a little like:

class sfWidgetFormSchemaFormatterPulse extends sfWidgetFormSchemaFormatter
{

  protected
    $rowFormat      = "<div class=\"form-row\">\n %error%%label%\n %field%%help%\n%hidden_fields%\n</div>",
    $errorRowFormat = "<div class=\"error\">\n%errors%</div>\n";
}


And, from there we need to tell our form to use our own formatter, so in your sfForm (child) class, in either configure() or setup() (I use setup() - I don't know if there is a difference, really. Is there? Anyone?), add the following line:

$this->getWidgetSchema()->setFormFormatterName('pulse');

And that's it! Quite simple really. (When the form is rendered, symfony will look for $class = 'sfWidgetFormSchemaFormatter' . ucfirst($name); (where $name is 'josh', or whatever you called your formatter), and does the rendering using that class!)

For full customisation, have a look in sfWidgetFormSchemaFormatter, and look at the protected variables at the top. They're what you have to play with.

For examples of other implementations, symfony have their own formatters: sfWidgetFormSchemaFormatterTable (default) and sfWidgetFormSchemaFormatterList.

Good luck!

Saturday, February 9. 2008

0 Comments

PEAR Bad! Phing Weirdness

So I was just trying to deploy some demo code, and my server was whining at me. Bigtime.

./symfony propel:build-all-load frontend

Yielded the error:

Fatal error: Declaration of SequentialTask::addTask() must be compatible with that of TaskContainer::addTask()

The code I had deployed was identical to my local code. The error message even identified the deployed code!

Something was up.

INSTALLED PACKAGES, CHANNEL PEAR.PHP.NET:
=========================================
PACKAGE VERSION STATE
Archive_Tar 1.3.2 stable
Console_Getopt 1.2.2 stable
PEAR 1.5.4 stable
Structures_Graph 1.0.2 stable
phing 2.1.1 stable


Ah ha!

sudo pear uninstall phing
uninstall ok: channel://pear.php.net/phing-2.1.1


Success!

So there you go: PEAR bad, bundled libs good.

Tuesday, December 4. 2007

0 Comments

Getting symfony 1.1 Running

I was just thinking, I wrote a blog entry about symfony 1.1, without any guide to getting symfony 1.1 up and running. If you're not sure what you're doing, don't do it! Wait for the official release (or RC, etc) which will no doubt come with docs as well. You'll probably need them.

Note: I don't know how well this will run on Windows. I think it should (from looking at symfony.bat), but your mileage may vary. Cross your fingers, let's jump in!

To get the code, I've been pulling it down from SVN into a local lib directory. Though, there's one thing to think about before we go there.

We need to decide which code to pull down. The most obvious option is to pull down the trunk/ folder, but I've been having some sweet success with the branches/dwhittle/ folder. Among other things, the dwhittle branch has Propel 1.3 support, which I'm clearly an avid fan of (after writing sfPropel13Plugin). It's kept fairly well up to date, so it seems fine.

So, you've chosen a branch, let's pull down the code:

  • Navigate to your local repo: cd /<path to local repo (I have it in /Library somewhere)>/
  • Checkout the code: svn co http://svn.symfony-project.com/branches/dwhittle/ sf11 (or use /trunk if you want to stick to trunk.)
  • (Optional, for convenience. Or else skip ahead to the third last step) Copy the symfony script somewhere useful: cp sf11/data/bin/symfony ~/util
  • Edit the symfony script, and add in your lib dirs: (lines 44 and 45, under the PEAR comment) $sf_symfony_lib_dir = '/<path to local repo>/sf11/lib'; $sf_symfony_data_dir = '/<path to local repo>/sf11/data' ;
  • Run the script! ~/util/symfony - there should the list of Pake tasks
  • Create a new project folder, and cd into it
  • Generate a project! ~/util/symfony generate:project <project name>
  • You should now be able to run symfony and it'll automatically refer to your symfony 1.1 Pake tasks. Success!


From here, you can do the same old tricks, like symlink'ing/svn:externals'ing the symfony libs into lib/vendor/symfony/, and modify config/config.php so you're not referring to the global lib. But that's entirely optional (I only do it because my IDE likes it).

And for you Windows users, if the steps above broke for you, try including the symfony.bat into your util directory, so that Windows has something to execute. It should work, though!

Best of luck!

Tuesday, November 27. 2007

1 Comment

Symfony 1.1 Forms and Validation

Okay, so you want the quick rundown of how forms and validation work in symfony 1.1?
  • First, download the demo project - http://notjosh.com/labs/sf11forms.zip
  • Extract the project to somewhere on your http://localhost/ (the project should run as a sandbox project)
  • (Probably only necessary for *nix/Mac) On the command line, navigate to the directory of the project, and run ./symfony project:permissions
  • Point your favourite browser to http://localhost/<your project path>/web/frontend_dev.php/


You'll be presented with a pretty basic form, with some ridiculous validation rules. The scenario we're going for is that this is a user registration form. The validation rules are mostly just so you can see how flexible they are and how to use them.

That's the quickstart. For an explanation of how it all works, read on!

Okay, so let's have a look at the file structure of the project. It's very much what you're used to in symfony 1.0. Knowing that, let's head into some code.

Open your favourite file-browser/IDE to /apps/frontend/modules/user.

  • In our actions file, we have one action called 'register'.
  • We have two templates - a form, and a page that will just echo the form values once a successful submission is received
  • In our module's lib/ directory, we have three folders, each with a single class in them. (Note: this is just my convention, you don't need to have the classes in the folders)


Let's start with the things we're used to. Let's have a look in the action:


1 public function executeRegister()
2 {
3 $form = new RegisterForm();
4
5 if ($this->getRequest()->isMethod('post'))
6 {
7 $form->bind(
8 array(
9 'username' => $this->getRequest()
->getParameter('username'),
10 'password' => $this->getRequest()->getParameter('password'),
11 'email' => $this->getRequest()->getParameter('email'),
12 )
13 );
14
15
16 if ($form->isValid())
17 {
18 $values = $form->getValues();
19
20 $this->setTemplate('registered');
21
22 $this->values = $values;
23 return sfView::SUCCESS;
24 }
25 }
26
27 $this->form = $form;
28 }



So, a summary:
  • We're creating a form.
  • We're checking if it's a POST.
    • If so:
      • We'll bind the parameters from the form back to the form object
      • We'll check the form passes validation
      • If it's valid, pass the values to a new 'registered' template
    • If not, we'll assign the form variable to the template


And how does the default template look?


1 <form action="<?php echo url_for('user/register') ?>" method="post">
2 <table>
3 <?php echo $form ?>
4 <tr><td colspan="2"><input type="submit" /></td></tr>
5 </table>
6 </form>


So, we define a form, echo our form variable and give it a submit button. Easy! (You can modify the presentation of the form and add nested forms in code, but I'll cover that another day..)

The downside is that it's using tables for presentation. It's my understanding that you can plug in different form templates, but I haven't got that far yet.

And the other template? Well that looks like:


1 <p>Form successful! Here are the values:</p>
2
3 <?php foreach ($values as $id => $value): ?>
4 <p>
5 <strong><?php echo $id ?></strong>:
6 <?php echo $value ?>
7 </p>
8 <?php endforeach ?>


Simple, just a foreach over the values. We're outputting the POSTs, basically.

So far, so good.

Now we'll get into the nitty-gritty a little more. We'll have a look at how the form object (RegisterForm) is created.

So, let's take a look at RegisterForm:


1 class RegisterForm extends sfForm
2 {

3 public function configure()
4 {
5 $this->setValidatorSchema(new RegisterValidatorSchema());
6 $this->setWidgetSchema(new RegisterWidgetFormSchema());
7 }
8
9 public function bind($taintedValues)
10 {
11 $request = sfContext::getInstance()->getRequest();
12 if ($request->hasParameter(self::$CSRFFieldName))
13 {
14 $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName);
15 }
16
17 parent::bind($taintedValues);
18 }
19 }


Take a look at the configure() method, it's the important one. We're assigning the form with a widget schema (a set of form widgets), and a validation schema (the new version of validation YAML). Really simple stuff, definitely.

Note: I don't know if there's a better way than overriding the bind method. Overriding the bind method is just to transparently manage the CSRF protection. You can ignore that for now - the easy way is to add the extra array option in the action where you call ->bind on the other POST vars.

Let's look at the widget schema we're using (RegisterWidgetFormSchema):


1 class RegisterWidgetFormSchema extends sfWidgetFormSchema
2 {

3 public function __construct($options = array(), $attributes = array(), $labels = array(), $helps = array())
4 {
5 parent::__construct(
6 array(
7 'username' => new sfWidgetFormInput()
,
8 'password' => new sfWidgetFormInputPassword(),
9 'email' => new sfWidgetFormInput(),
10 ),
11 $options,
12 $attributes,
13 $labels,
14 $helps
15 );
16 }
17 }


I'm not certain this is the best way to do this, but it seems to work just fine. I'm overriding the constructor, and passing in the values I know the 'register' form should have (in this case, a 'username', a 'password' and an 'email'). The sfWidgetFormInput is a simple input element (type=text), and predictably the sfWidgetFormInputPassword is just a password input element.

So there we have it - now we can render a form, and have no validation on it.

We can do more complex things in here, like nesting different forms and having groups of elements (I think, anyway). But I'll leave that for now.

So, let's look at our validation rules in RegisterWidgetFormSchema:


1 class RegisterValidatorSchema extends sfValidatorSchema
2 {

3 public function __construct($options = array(), $messages = array())
4 {
5 parent::__construct(
6 array(
7 'username' => new sfValidatorString()
,
8 'password' => new sfValidatorAny(
9 array(
10 new sfValidatorString(array('min_length' => 10))
,
11 new sfValidatorRegex(array('pattern' => '/^\d{2}/')),
12 )
13 ),
14 'email' => new sfValidatorAll(
15 array(
16 new sfValidatorString(array('min_length' => 10))
,
17 new sfValidatorEmail(),
18 )
19 )
20 ),
21 $options,
22 $messages
23 );
24 }
25 }

Similar to above, I'm just overriding the constructor as passing in validators (making sure the array keys match the inputs defined in the widget schema).

Let's take a closer look at our (wildly stupid) validation rules:

  • username: simple string validator. This should be fairly familiar to symfony 1.0 kind of functionality.
  • password: this gets a bit more complex, our validator is 'sfValidatorAny'. This validator takes an array of parameters (which are other validators). It's like a boolean 'OR' that says "we must match any of the following validators". So what are the possible validators we can match?
    • sfValidatorString, min_length = 10
    • sfValidatorRegex, patterm = /^\d{2}/ (for the regex challenged, we must have two numbers at the start of the string)
    So, in human terms, we must either have a password at least 10 chars long, or we must start our password with two numbers. So, 'abcdefgh' is an invalid password, but 'abcdefghij' is valid, and so is '12abc'.
  • email: similar to password, our validator is 'sfValidatorAll'. In the way that sfValidatorAny was 'OR', this is like 'AND'. It says that all of the validators must pass. So what are the validators?
    • sfValidatorString, min_length = 10
    • sfValidatorEmail
    Both of these should be pretty obvious to you - it must be an email that's at least 10 characters long. 'a@b.com' will fail validation, but 'abcde@fghij.com' will pass!


That pretty much wraps it up. It's pretty basic, but it'll give you enough to start building some basic forms and validating them. If I get a chance, I'll run through some more complex stuff another day? Hrm, maybe.

Best of luck! Happy sf1.1'ing!

Monday, November 26. 2007

1 Comment

MoBlog: Dennis Station Pride

yep.


Click for full size image

Wednesday, November 7. 2007

0 Comments

Why Motorsports Suck!

Over the last few weeks, a lot of what I've seen on (weekend) TV has been centred around motor sports. I was up late watching some random F1 racing, and I gained a whole new appreciation for the way they work. I mean, there's not a lot of room for error - the speed and consistency at which they operate is pretty mind blowing.

The next event was the inaugural Bathurst 1000. Followed a week or two later by the Phillip Island MotoGP and some miscellaneous drag racing.

My gripe is the amount of fuel we're completely wasting in the name of entertainment. Hell, ultimately I'm grumpy about the overall amount of fuel/energy we're wasting in most aspects of day-to-day life, but folk doing it on TV set a pretty average precedent.

I shudder to think about things like the amount of fuel consumed around the Phillip Island MotoGP weekend - not only on the track, but by the ba-jillions of fans, commuting to and from the event.

From what I understand (which isn't much..), the fuels used in the SUPER ELITE SPORTING VEHICLES is pretty clean, and emits less 'crap' than your general unleaded petrol, but the fact is we're still running out of fossil fuels, and the cost of oil isn't coming down much. At all. And the goal is to be the fastest, not the most efficient (well, in most cases). Then there's the hours poured into testing of new engines and tyres and whatnot, which of course burns more fuel. So all things considered, there's a lot of wastage.

These days, I go through quite little petrol in my car. I still catch a bus and a train to work (which obviously burns some fuel), but it's definitely better than driving AND having the buses/trains running. The most fuel I've used this year can be attributed to getting to and from the snow, which on reflection, feels horrible. I know I'm merely a drop in the ocean, and I know that in the big picture I'm doing a horribly minor percentage of the damage, but I still can't shake the feeling that I'm doing something I really shouldn't be.

I really want to go camping, and I really want to go on more road trips around the place, but I still can't justify it to myself. Granted, that little voice in my head likely wouldn't often stop me actually doing whatever I felt I wanted to (going camping, going to the snow), but it still feels pretty average.

A quick (i.e. likely wrong) calculation. Bathurst 1000 had 18 (or so?) cars starting? Something like that. Hell, let's go ultra conservative here. Let's say 10 cars. To use round (and very optimistic) numbers, let's say it does 10L/100km. So, every car uses 100L of fuel to get through 1000km of track. And because we said there was 10 cars, that's 1000L of fuel used on the day, with 10000km of track covered. And that is very conservative (okay, I did some research now. According to v8supercars.com.au, there were 30 cars starting, with 16 cars finishing the race. So, we're talking at least 16000km of driving, but likely somewhere over 20000km)

Judging by my service history, so far this year my car has travelled just a little over 10000km in total (which included a winter of trips to the snow). And I can say with near-certainty that my car (2.0 litre, 4 cylinder, all-wheel drive) is a lot more fuel efficient than theirs (despite me having air conditioning and all those other luxuries that reduce overall efficiency). Suddenly I feel a whole lot less guilty in my simple pleasures.

I'm still not sure about the camping road-trips, though. Time to buy a Prius?

P.S. I'm sure I'm using the wrong word when I talk about the 'efficiency' of these vehicles. It's completely in their best interests to be as efficient as possible (aerodynamics, not wasting energy from the engine to the wheel, etc). They still use a helluva lot of fuel, though. I guess my car would use a lot of fuel at 300km/h too. Hmm.

Sunday, October 21. 2007

3 Comments

MoBlog: Apology Denied!

:[


Click for full size image


What's worse - they seemed to have stopped making the yoghurt I (occasionally) eat. No more yoghurt evar.

Tuesday, September 11. 2007

0 Comments