In the days of old (PHP 4) web programmers sometimes resorted to small shortcuts in their code. One of these was register_globals, which extracts the global arrays ($_REQUEST, $_POST, etc) into individual variables, allowing a programmer to write $name instead of $_POST['name']. This is an obvious security flaw - users can easily pass in custom parameters to overwrite variables in the code and cause unforeseen problems. In the below example, if register_globals was turned on in the PHP configuration, a tricky user can pass in a 'access' parameter and do some horrible things.
if($level == 'admin')
$user = 'admin';
$access = 'everything';
$restrictions = 'none';
if($access == 'everything')
// do some horrible things here
This is a good example of a bad programming practice - assuming that things will work the way you expect them to. That is, you assume that a level of 'admin' is the only way the 'access' variable could be set to 'everything'. The security-orientated approach would be to imagine all of the possible ways 'access' variable could be set before checking its value.
A modern example of this flaw can be seen with PHP 5 and some model-view-controller frameworks. An easy way of loading the correct controller with path is to mirror the requested url. So, if you want to access a controller file in controller/something/things/Object.php, you would point a link at domain.com/something/things/object/. While this works, if you don't have a general route checker for each and every url then all of your controller classes are now available from the browser. A better way to do this is to route every possible url through a mapping function. Here you can restrict access, change url structure, and change paths and only have to worry about changing the mapping function.
A much more problematic solution I've seen involved the methods within the class. By default an index() method was called each time the class was called from the url. AJAX requests pointed at the class would point towards different methods within the class. Now both classes and methods are completely exposed to the end user. While this works and might be a nice short cut for a programmer, the application is completely exploitable.
The solution that I've begun to use involves advanced abstraction. Plenty of today's web programmers are familiar with data abstraction (interacting with the server-side data through objects and relationships). This break between data structure and application allows changes with the data layer with minimal rewrite to the application code. I've been taking the next step by abstracting all client-side interactions. All cookies, urls, and request parameters are passed through mapping functions before initiating the application.
While this extra step brings in some problems (increased overhead, extra coding, etc), it's turning out to be really nice in the long run. By routing all of the urls through a mapping function, I can tweak the url as much as I want to be SEO friendly without worrying about the controllers involved. Even my AJAX calls go through their own router to make sure that the user is allowed to initiate them. I plan on mapping my request parameters in the long run, which will let me change the front-end of my forms without worrying about changing all of my application checks and validations.
A little bit of abstraction between the layers helps make my application more secure. However, you don't need to go this far to make sure that your program doesn't have any holes. The only assumption you should make with an application is that the end user knows every line of your code and will try to break something. By keeping your logic flow air-tight, your application will be safer and require less post-launch bug fixes.