The CakeRequest object in CakePHP 2.0

Work on CakePHP 2.0 is moving along, and I wanted to take some time to discuss and explain one of the sizeable refactorings that has been done for 2.0. In previous versions request parameters were just a bunch of arrays that were copied to the various places they were needed in the framework. Methods related to request features were spread across RequestHandler, Router, Dispatcher and Controller. There was no authoritative object on what information the request contained. In many other frameworks the request is represented by a single object. This seemed like and excellent solution for CakePHP that would allow us to both consolidate the existing code, and provide some useful new features.

Enter CakeRequest

CakeRequest is the default request object used in CakePHP 2.0. It centralizes the many features available in the classes previously mentioned into a single object. This object is then passed by reference to the various objects that use request data. This makes changes to that object reflect uniformly across all references. Some of the duties CakeRequest performs include:

  • Process the GET, POST, and FILES arrays into the data structures you are familiar with.
  • Provide environment introspection pertaining to the request. Things like the headers sent, the client’s IP address, and the subdomain/domain information about the application the server is running on.
  • Provide access to request parameters both as array indices and object properties.

The request object is available in all the objects that used to have a params property. The params property still exists and contains the request object, but its been deprecated in favour of $this->request. I think this gives easier to read code than params does, and more accurately reflects what it does.

Getting at params with CakeRequest

As previously mentioned CakeRequest can be accessed in an array fashion, like the old params property was or, you can use object property access. Since both these interfaces existed on controllers in previous versions, both interfaces were moved to the request object.

Show Plain Text
  1.  
  2. $this->request['controller'];
  3. $this->request->controller;
  4.  

Will both access the same values. The two ways of accessing values was done to ease the transition to using CakeRequest. In addition to the normal route parameters, properties like base, webroot, and here were also moved off the controller and into the request object. These properties were again things that had been spread about the framework, but belonged with the request object.

Checking the request

Detecting various request conditions used to require using RequestHandlerComponent. These methods have been moved to CakeRequest, and offer a new interface alongside a more backwards compatible usage:

Show Plain Text
  1. $this->request->is('post');
  2. $this->request->isPost();

Both method calls will return the same value. For the time being the methods are still available on RequestHandler, but are deprecated and still might be removed before the final release. You can also easily extend the request detectors that are available, by using CakeRequest::addDetector() to create new kinds of detectors. There are four different types of detectors that you can create:

  • Environment value comparison – An environment value comparison, compares a value fetched from env() to a known value the environment value is equality checked against the provided value.
  • Pattern value comparison – Pattern value comparison allows you to compare a value fetched from env() to a regular expression.
  • Option based comparison – Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already defined options detector will merge the options.
  • Callback detectors – Callback detectors allow you to provide a ‘callback’ type to handle the check. The callback will receive the request object as its only parameter.

Some examples would be:

Show Plain Text
  1. // Add a environment detector.
  2. $this->request->addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'));
  3.  
  4. // Add a pattern value detector.
  5. $this->request->addDetector('iphone', array(
  6.     'env' => 'HTTP_USER_AGENT',
  7.     'pattern' => '/iPhone/i'
  8. ));
  9.  
  10. // Add an option detector
  11. $this->request->addDetector('internalIp', array(
  12.     'env' => 'CLIENT_IP',
  13.     'options' => array('192.168.0.101', '192.168.0.100')
  14. ));
  15.  
  16. // Add a callback detector. Can either be an anonymous function or a regular callable.
  17. $this->request->addDetector('awesome', function ($request) {
  18.     return isset($request->awesome);
  19. });

CakeRequest also includes methods like domain(), subdomains() and host() to help applications with subdomains, have a slightly easier life.

CakeRequest and RequestHandlerComponent

Since many of the features CakeRequest offers used to be the realm of RequestHandlerComponent some rethinking was required to figure out how it still fits into the picture. I was never really happy with having to use a component that did many things on top of request introspection just to get the request introspection. For 2.0, RequestHandlerComponent acts as a sugar daddy. Providing a layer of sugar on top of the utility CakeRequest affords. Sugar like switching layout and views based on content types or ajax is the domain of RequestHandlerComponent. This separation of utility and sugar between the two classes lets you more easily pick and choose what you want and what you need. Although the work for CakeRequest is not 100% complete, as some deprecated code still needs to be excised, the transformation is mostly complete, and I’m rather happy with the way it turned out.

Comments

Mark,

Is there any sort of document or blog post outlining what sorts of things are planned for Cake 2.0?

I know that the plan is to drop PHP 4 support but besides that, not much more info on it that I can find.

Dan on 29/8/10

The Detector stuff looks very useful.

petteyg on 29/8/10

As much as I applaud the work on keeping as much backwards compatibility as possible, I would prefer that backwards compatibility be dropped where possible. We are already losing PHP4 support, and with that many internals are being rewritten regardless. Why not drop support for old methods? I doubt many will port existing apps to CakePHP 2.0, and will just do rewrites.

Regardless, good work so far, and I look forward to playing with the 2.0 codebase in the near future :)

Jose Diaz-Gonzalez on 30/8/10

I do not know why you want to keep things backwards compatible on a “rewrite” of the framework. I think having that will only make it harder on people when its finally dropped. Its also going to make code harder to follow with different code doing the same thing all over the place.

Like Jose Diaz-Gonzalez said, I doubt apps will be ported to cake2, they will just be rewritten.

dogmatic69 on 30/8/10

I, for one, will be porting my apps from 1.3 to 2.0. It wouldn’t make sense to just bin them and start all over – not from a business perspective anyway.

I welcome the efforts for creating an easy upgrade path!

Kim Biesbjerg on 30/8/10

I second the importance of an easy upgrade path! Doing a complete rewrite of a big project doesn’t make too much sense.

Edmunds Kalnins on 30/8/10

I would say drop backward-compatibility wherever possible, as long as you have a thorough migration guide there should be no major issues with upgrading existing apps. I am switching to li3 mainly for my future development because it seems more of a forward-thinking framework. I hope CakePHP 2.0 can make my wildest dreams come true though, I really do.

Travis Rowland on 30/8/10

I, like many others, would not mind doing some work to upgrade, given the substantial, long awaited, glaring benefits which are on tap for cake 2.0.

The upgrade from 1.1 to 1.2 involved some work, but I think all would agree it was well worth it.

Cake 2.0, with all the fundamental new improvements, will certainly be worth some work.

keymaster on 30/8/10

AMAZING!!!

great work

Eugenio on 30/8/10

I’ll third the importance of an upgrade path! We are currently pretty excited about migrating our formerly CakePHP 1.2 app to 2.0 – it’s now been (mostly) completely migrated to 1.3, and of course all of the old 1.1 functions that programmers were still calling in 1.2 have been removed.

Michael Clark on 30/8/10

“CakeRequest also includes methods like domain(), subdomains() and host() to help applications with subdomains, have a slightly easier life.”

Awesome. So far, this has been all the apps I’ve written, and my approach has gotten better, but I can’t wait to see this in the core.

Cottser on 30/8/10

Nice work Mark.

Consolidating the request object is a nice new feature.

I really hope you and the rest of the dev team get a chance to refactor/lighten the bootstrap process.

The lazy loading on the 2.0 road-map is more of patch then a solution to an core problem.

Arnold Almeida on 31/8/10

@Arnold Almedia:

I wouldn’t refer to lazy loading as a “patch”. It’s the right way to go, IMHO.

@Mark Story:

Thanks so much for carrying the brunt of cake development on your shoulders these last few months.

It’s good to see you are taking a step back and refactoring/consolidating what makes sense (eg. CakeRequest).

Any murmurings regarding refactoring the core for performance in 2.0?

keymaster on 31/8/10

My stance on keeping backwards compatibility is, that if its possible to include backwards compatibility in a way that doesn’t cause code sprawl and can be self contained so its easy to remove in the future, why not. It makes upgrading easier, and I think that many people do in fact upgrade applications instead of re-writing. For a company that has invested time into an application, there are many times where a rewrite is simply not possible. With that said I think providing a backwards compatible interface on CakeRequest isn’t an overly costly venture and should be kept, at least for now.

As for addressing performance issues, we are of course always looking for ways to improve performance. Of course if you know of easy wins that we’ve missed you can always help make things better as well. The more eyes on a problem the easier and faster they are to solve :)

mark story on 1/9/10

Hi Mark,

Does this mean the 2.0 version will fully implement the Active Record Pattern, and we will be able to use Objects instead of arrays to access to the models data? (I think that’s what you meant)
Could this also mean that a function from a controller could then be called directly from another controller, or is it just all about the data?

Cheers,
Nicolas.

Nicolas on 22/11/10

Nicolas: The request object is entirely separate from any model changes. The request object is only about encapsulating request information into an object. Nothing more really. There isn’t any intention to make it so controllers can call each other in new ways other than requestAction. Your controllers really shouldn’t have that much need to call each other.

mark story on 27/11/10

Have your say:

*
* You can use Textile markup, but be reasonable