RSS
 

Posts Tagged ‘redirect’

CakePHP Tips – Summer 2013

30 Jul

While CakePHP 2.4 is now just around the corner (already beta), a few tips gathered the last few days.

Don’t mix controller and component pagination

Currently, if you mix controller pagination setup and call the Pagination component then, it can easily backfire as the two different
settings are not always merged as you would expect it to. So don’t mix em.

Either use the controller only:

public $paginate = array(...);
// and
$this->paginate = array(...) + $this->paginate;
$results = $this->paginate();

or the component only:

$this->Paginator->settings(array(...));
$results = $this->Paginator->paginate();

My recommendation: Stick to controller pagination for now (as we all have the last years) as it seems it won’t be deprecated after all. It is a nice
convenience access to the component anyway.

PS: If sticking to controller pagination, add this rejected PR snippet to your AppController to avoid the notice thrown.

Test your code

Even if you don’t assert the outcome.

"But that is time consuming. I dont have time!"

Yeah, sometimes you are in a hurry and can’t properly assert all tests. Still no excuse.
Just be running the code via "dummy call" and having the test not through any notices/warnings/errors you already have a very basic level of testing. And it really doesn’t need time to set such a basic test run up.
As soon as there is time you can then still address the proper result. But until then you at least know that no line of code is suddenly gone missing or variables have
accidentally been renamed etc.
Such a basic "run-through" test is still better than no test at all:

/**
 * Test myMethod()
 * 
 * @return void
 */
public function testMyMethod() {
	$this->MyClass->myMethod();
	//TODO: assert return value etc
}

Return your redirects

I always used to bake my templates with simple redirect() calls:

if ($this->Model->save()) {
	$this->redirect(...);
}

But, especially with testing your code, this might have unexpected results, as there you cannot exit, but need to return early (by overwriting the redirect method in your test case file).

The cookbook now states this, as well:

When testing actions that contain redirect() and other code following the redirect it is generally a good idea to return when redirecting. The reason for this, is that redirect() is mocked in testing, and does not exit like normal. And instead of your code exiting, it will continue to run code following the redirect.

So it’s wiser to cleanly return here, as well:

// in your controller action
if ($this->Model->save()) {
	return $this->redirect(...);
}

Even if for your productive environment the return will be superfluous, the test cases will not return wrong results.

I just recently adjusted my bake templates and it has also been changed in the 2.4 branch of the Cake core. I advice you to update your files, as well. You can use my Upgrade shell task "controllers" to do that as a batch job for your app and plugins:

// for your app
cake Upgrade.Upgrade controllers
// for a plugin FooBar
cake Upgrade.Upgrade controllers -p FooBar

Return your shell errors

Same applies here: Testing does not halt the code. Therefore you also need to return early here:

public function some_command() {
	if (!$this->foo()) {
		$this->error('Abort');
	}
	...

becomes

public function some_command() {
	if (!$this->foo()) {
		return $this->error('Abort');
	}
	...

This is also covered by my Upgrade shell now.

Debug SQL queries by throwing exceptions

Ever worked on an ajax, csv or even a redirect action where there will be no debug SQL bar at the bottom etc?
You can easily display your SQL queries up to a specific point by throwing an exception:

$someRecord = $this->Model->find(...);
 // We just add this snippet here to halt and see the actual SQL query before we continue:
throw new Exception();
if (!$someRecord) {
	return $this->redirect(...);
}
return $this->redirect(...);

This halts the code, shows the error layout with all the queries listed below. Neat, isn’t it? And it is quite fast and works anywhere in your application.

Switch from constants to a more flexible way of configuration

In our case we can leverage the Configure class and its read() and write() methods to do so. Better than constants anyway as they can only be defined once and not changed anymore.
So it’s only reasonable that the 2.4 core deprecated quite a few of those constants like FULL_BASE_URL, DEFAULT LANGUAGE, CAKE_SHELL etc.
Using Configure we can store a default value and overwrite it any time after that with a different value if we need/want to.

In the past I also caught myself using constants where Configure would have sufficed. Partly to make myself believe my app will be faster this way.
The speed advantage is so minimal though that it does not outweigh the reduced flexibility that comes with it.

So instead of using define('FULL_BASE_URL', 'http://example.com') you can now use Configure::write('App.fullBaseURL', 'http://example.com'); and overwrite it again later on.

Same goes for any settings you might want to set. Chose a meaningful namespace to group them, though:

Configure::write('Config.someKey', 'somevalue'); // with Config as namespace

You can read it anywhere in your app as always, of course:

$key = Configure::read('Config.someKey'); // in this case probably boolean (1/0 alias true/false)
if ($key) {
	// some code here
}

Change your default validation error message

It will be changed in 3.x from "This field cannot be left empty" to "The provided value is invalid".
This makes sense as in most cases it would display this error message when a value is provided but does not pass the specific validation rule (like numeric or email).
This is highly confusing then.

'email' => array(
	'notEmpty' => array(
		'rule' => array('email')))

This example would just fallback to "This field cannot be left empty" here – both on empty submit and if you input some invalid email.

So you should already address that in 2.x by always defining the error message for your rules with the above wording. Clearer validation error messages will help your users to correct their mistakes and submit the form properly. Only use "This field cannot be left empty" for actual "notEmpty" cases then:

// in your model
public $validate = array(
	'email' => array(
		'notEmpty' => array(
			'rule' => array('email'),
			'message' => 'The provided value is invalid' // or even 'This is not a valid email address'
		)
	),
	'postal_code' => array(
		'notEmpty' => array(
			'rule' => array('notEmpty'),
			'message' => 'This field cannot be left empty',
			'last' => true
		),
		'exists' => array(
			'rule' => array('validatePostalCodeExists'), // custom validation rule
			'message' => 'The provided value is invalid', // or even 'This postal code is not valid'
		)
	)
);

Also try to use the verbose array declaration and don’t forget 'last' => true for consecutive validation rules (as in postal_code above).

Text(area) to paragraphs

Use the new Cake2.4 method TextHelper::autoParagraph() to display your textarea input. It will automatically format single line breaks to <br /> and double ones to <p> blocks.
If you are still on 2.3, you can copy this method over into a TextExt helper, for example, and use aliasing to map it back to "Text" helper alias. When upgrading you won’t have to change your code then a bit 🙂

Important: Do not forget that this method does not escape your output. Thus you need to use h() for (potentially unsafe) user input:

echo $this->Text->autoParagraph(h($text));

h() will effectively remove any XSS dangers as well as defuse any HTML chars like < or > that could otherwise destroy the layout (but are totally valid chars for quoting in some languages).

Read more about output and security in CakePHP here.

Check if you need the hardcoding of your core lib path

By default CakePHP "bake project" for new apps will hardcode the paths in your index.php and test.php – if you don’t have CakePHP in your include_path. Which most don’t, especially when maintaining multiple different Cake versions and apps.
So you might want to (I do anyway) comment those lines out again after baking the project:

define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');
// to
//define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');

You will stumple upon that when deploying your scripts. So better take care of it asap.

Check out the 2.4 core

While it is still beta, you can test it already, though. I even use it for new apps (Disclaimer: don’t do that if you are new to CakePHP!).
You can test the new features or how your 2.3 app would run with it. You can realize quite early this way where potential pitfalls are.
In the past I often discovered some "bugs" this way that then could easily be fixed or adjusted before your code actually some day depends on it.
While still in beta you might also be able to add some additions to new features before you will have to wait for 2.5 again or something.
So it’s worth to at least play around with it now.

Also keep an eye on the 2.4 migration guide and maybe even 3.x migration guide as well as 3.x chatter and how your code would be affected or would have to be modified. It might save you some time in the upgrade process if you can already eliminate some of the necessary changes in advance.

A very good example is the removal of the named params in favor of query strings. Don’t start to use those for new apps. Use the future proof query strings even for your 2.x apps.

One example for 3.x, that is not yet officially mentioned yet, is the possible deprecation and removal of Model::read(). It is a method I have always been preaching to avoid. Especially if you are a beginner, but also if you can (and you always do) use find() instead. The cookbook now also states this in a warning.

I never used it once and it seems that there is very high possibility that it will be removed in 3.x. So if you catch yourself using it today, think about this and if you might be able to replace it with the find() wrapper from here on 🙂 To be on the safe side.

 
 

Forms and redirecting

20 Aug

Redirecting in your CakePHP app

My CommonComponent now contains three redirecting methods:

/**
	 * @param mixed $url
	 * @param bool $useReferer
	 * returns nothing and automatically redirects
	 * 2010-11-06 ms
	 */
	public function autoRedirect($whereTo, $useReferer = true) {
		if ($useReferer && $this->Controller->referer() != '/' . $this->Controller->params['url']['url']) {
			$this->Controller->redirect($this->Controller->referer($whereTo, true));
		} else {
			$this->Controller->redirect($whereTo);
		}
	}
	
	/**
	 * should be a 303, but:
	 * Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
	 * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
	 * @param mixed $url
	 * TODO: change to 303 with backwardscompatability for older browsers?
	 * 2011-06-14 ms
	 */
	public function postRedirect($whereTo, $status = 302) {
		$this->Controller->redirect($whereTo, $status);
	}
	
	/**
	 * only redirect to itself if cookies are on
	 * prevents problems with lost data
	 * Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
	 * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
	 * TODO: change to 303 with backwardscompatability for older browsers?
	 * 2011-08-10 ms
	 */
	public function prgRedirect($status = 302) {
		if (!empty($_COOKIE[Configure::read('Session.cookie')])) {
			$this->Controller->redirect('/'.$this->Controller->params['url']['url'], $status);
		}
	}

autoRedirect and postRedirect

Where do I use it? In pretty much every form (for instance edit action):

if (empty($id) || !($user = $this->User->find('first', array('conditions'=>array('User.id'=>$id))))) {
	$this->Common->flashMessage(__('invalid record', true), 'error');
	$this->Common->autoRedirect(array('action' => 'index'));
}
if (!empty($this->data)) {
	if ($this->User->save($this->data)) {
		$var = $this->data['User']['id'];
		$this->Common->flashMessage(sprintf(__('record edit %s saved', true), h($var)), 'success');
		$this->Common->postRedirect(array('action' => 'index'));
	} else {
		$this->Common->flashMessage(__('formContainsErrors', true), 'error');
	}
}
if (empty($this->data)) {
	$this->data = $user;
}

The autoRedirect automatically redirects back to the site the user came from if possible (if the user clicked a link). Otherwise it will use the provided fallback url.
The postRedirect is a wrapper for the future where one day 303 can be used without causing trouble (read further for details).

prgRedirect

Why is this necessary in some forms? Most search forms do a simply post. What they should do is a POST + GET afterwards. Thats called PRG pattern and is described here.
Especially after posting search forms or entering data you want to avoid a nasty message like some modern browsers produce if you then hit the back button. You want to graciously display the page prior to the post. Thats where this extra redirect comes into play. Always redirect after a post – quite easy to remember.

if (!empty($this->data)) {
	if ($this->Model->search($this->data)) {
		# save POST search to session, redirect and display the search result as GET
		$this->Common->prgRedirect();
	}
}
if ($search = $this->Session->read(...)) {
	...
}

As the comment in the method head as well as other sources explain one should NOT use 303 or you end up with broken forms for some users.

Note the failsafe with the cookie. If cookies are disabled this would result in empty sessions and therefore never work. For disabled cookies there cannot be a redirect and therefore needs the POST to display the data.
Therefore your forms should work with both POST and GET for this very same reason. The "prg" redirect is only an enhancement to provide better functionality in the normal use case (where users do use the back button).

Moved/deleted content

If you moved or deleted some content you can use the 301 redirect to tell search engines and browsers where to find the same content at the new location or where to go to instead:

if (empty($manufacturer)) {
	$this->Session->setFlash(__('Invalid Manufacturer', true));
	$this->redirect(array('action'=>'index'), 301);
}

In the example I use this to redirect from views back to index if no manufacturer (retrieved via slug) was found. This is necessary to avoid duplicate content for invalid slugs.

Complete list of browsers that are not capable of handling 303s

//TODO – does anyone have infos on that matter?

 
No Comments

Posted in CakePHP

 

Redirect Root Domain to WWW Subdomain

13 Jul

The Problem

"When you have two different addresses pointing to the same page, like www.example.com/offers.html and example.com/offers.html, many search engines (or so we are led to believe) will treat those two URLs as two separate pages. When you, as a human, see those two pages and notice they are identical, you will automatically realise (correctly) that they are actually the same page. Apparently, the search engines do not make this assumption, and will regard those as different pages with duplicate content."
(source: www.thesitewizard.com)

I personally think that apart from the SEO problem there shouldn’t be two different urls to the same content in the first place.

So what do we do?

The bad thing to do would be to disable one of the domains (root or www).
We want to select one (www.example.com) as default domain and if someone just enters "example.com" he will be automatically redirected to our default domain.
Again – the bad thing would be to use meta redirects. We want to use so called permanent redirects ( code 301) in order to notify search engines and browsers about the reason we want to redirect.

Using Mod Rewrite

For the cake app to be available per "www.example.com" (and a 301 redirect from mydomain.de) you just need to modify the htaccess file in the /app/webroot/ folder:

<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^www\. [NC]
	RewriteCond %{HTTP_HOST} !^localhost [NC]
	RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

The "localhost" part is not necessary – but it prevents your local htaccess file to redirect on your development computer. That is if you have your htaccess file in svn or git and therefore both versions (local and stage) are the same.

The reason why i did not lay out the other direction (www to root) is simple: I don’t think this makes sense. Most people not so comfortable with the internet usually type the complete address anyway – they just expect a "www" in front of the domain for the main website.
And with the internet getting more and more complex, some guidelines should just remain. One is that you don’t use root domains for websites. (I could go into details here – about other side effects like "technical" cookie problems occuring with root domain sites etc – but i will leave that out for now)

Using Apache Directives

This is way faster because the server doesn’t have to open the htaccess files for it. It can directly use what it has to have available anyway.

Inside /etc/apache2/sites-available/ there should be your domain file.
Between <Directory> and </Directory> you can add the above lines.
To actually increase the performance we now need to disable htaccess files for this domain.
Add AllowOverride None directly above your new lines. This prevents the server from looking for, opening and processing htaccess files.

Note: Usually this is only available on root servers.

Update Cake2.x

For Cake2 you need to slightly change the second rule:

RewriteRule ^(.*)$ index.php?/$1 [QSA,L]