Page 1 of 5
[TDD] Who wants to help me build an http request class?
Posted: Tue Aug 22, 2006 3:42 pm
by Luke
I have been using arborint's A_HTTP_Request class (actually a modification of it) for all of my HTTP_Request needs. While it works, I am not 100% sure how and why it is built how it is.
Here is what I'm using:
Code: Select all
<?php
/**
* A basic Request class for accessing any
* user request information available to the script.
*/
class Request{
/**
* Set to true is request method is post
*/
protected $is_post = false;
/**
* Request Data Container
*/
protected $data = array();
/**
* Container for path info
*/
protected $path_info = array();
/**
* Recursively remove php-generated autoslashes
*/
public function __construct() {
if (get_magic_quotes_gpc()) {
$this->removeSlashes($_GET);
$this->removeSlashes($_POST);
$this->removeSlashes($_COOKIE);
}
if (!strcasecmp($_SERVER['REQUEST_METHOD'], 'POST')) {
$this->data =& $_POST;
$this->is_post = true;
} else {
$this->data =& $_GET;
}
if (isset($_SERVER['PATH_INFO'])) {
$this->path_info = explode('/', trim($_SERVER['PATH_INFO'], '/'));
}
}
/**
* Recursively remove php-generated autoslashes
*/
protected function removeSlashes(&$var) {
if (is_array($var)) {
foreach ($var as $name => $value) {
if (is_array($value)) {
$this->removeSlashes($value);
} else {
$var[$name] = stripslashes($value);
}
}
} else {
$var = stripslashes($var);
}
}
/**
* Accessor for Request::data
*/
public function get($name) {
return (isset($this->data[$name]) ? $this->data[$name] : null);
}
/**
* Setter for Request::data
*/
public function set($name, $value) {
$this->data[$name] = $value;
}
/**
* Checks for $name in Request::data
*/
public function has($name) {
return isset($this->data[$name]);
}
public function __get($name){
return $this->get($name);
}
public function is_post(){
return $this->is_post;
}
}
?>
What I would like to do is broaden the scope.. and build a few classes to deal with ALL types of request... maybe build a Request class which is then extended to work with HTTP requests, command line requests, etc.
I have a few problems:
I don't know really all that much about command line
I don't really know all that much about http
I don't really know where to start... I don't really understand why the posted class uses EITHER get or post... why not have methods like getGet() and getPost()?
Any help/resources/advice is appreciated!
EDIT: Oh yea... keep in mind... I have not even touched anything you could consider ajax, but wouldn't ajax be another type of request?
Posted: Tue Aug 22, 2006 4:42 pm
by Chris Corbyn
OK, I'll do this on the basis that we use TDD (design driven by writing unit tests before writing the code) (I'm a selfish bugger and want to learn this stuff) along the way
Before we start though, how much does this want to include? Does it need to do filtering too, or can we leave that down to a separate input filter? I guess technically I shouldn't be thinking about input filtering if we're using true TDD... but the back of my mind tells me it should be decoupled from the http request class in any case so we're good.
If you don't want to use TDD/SimpleTest I'll still be following this thread, it just seems like an ideal place to start using it

Posted: Tue Aug 22, 2006 4:52 pm
by Luke
d11wtq wrote:If you don't want to use TDD/SimpleTest I'll still be following this thread, it just seems like an ideal place to start using it

NO NO that's perfect! I am trying to learn simpletest right now too... this is a perfect chance to learn TDD. This works out for both of us!
d11wtq wrote:Before we start though, how much does this want to include? Does it need to do filtering too, or can we leave that down to a separate input filter? I guess technically I shouldn't be thinking about input filtering if we're using true TDD... but the back of my mind tells me it should be decoupled from the http request class in any case so we're good.
Filtering is not within the scope of this task. We are building a set of request classes or a request class... depending on what we decide to do. I would eventually like to build a filtering class to assist this class, but it is not one and the same.
Posted: Tue Aug 22, 2006 4:58 pm
by Jenk
I'm not keen on globals within a class, so perhaps pass the data into the constructor as an arg?
Code: Select all
$request = new HTTPRequest($_POST); // or get etc..
or even pass $_SERVER['REQUEST_METHOD'] as an arg and work with that. But also, let's not forget sometimes we can use both at the same time.. (2 Request objects maybe?)
Then just work with it as your everyday array within the object perhaps.
Something like.. (have literally typed this up just now.. haven't tested or even tried..)
Code: Select all
<?php
class Request
{
private $data;
public function __construct ($method = 'post')
{
if (in_array(strtoupper($method), array('POST', 'GET')) {
$this->data =& ${'_' . strtoupper($method)};
} else {
throw new UnexpectedValueException('Unexpected value "' . strval($method)
. '" provided to Request::__construct()');
}
if (get_magic_quotes_gpc()) $this->removeSlashes($this->data);
}
protected function removeSlashes(&$var)
{
if (is_array($var)) {
foreach ($var as $name => $value) {
if (is_array($value)) {
$this->removeSlashes($value);
} else {
$var[$name] = stripslashes($value);
}
}
} else {
$var = stripslashes($var);
}
}
public function get ($name)
{
if (isset($this->data[$name])) return $this->data[$name];
throw new OutOfBoundsException('Index "' . strval($name) . '" does not exist in Request::data');
}
public function set ($name, $val)
{
$this->data[$name] = $val;
}
public function hasData ()
{
return (bool) count($this->data);
}
}
?>
as you can see, I copied the removeSlashes() method.
btw, I'm not overly keen on this object having a set method - how often do you set $_POST/$_GET data? I can't remember a time I ever have

Posted: Tue Aug 22, 2006 5:07 pm
by Luke
Jenk wrote:btw, I'm not overly keen on this object having a set method - how often do you set $_POST/$_GET data? I can't remember a time I ever have

I haven't either
D11 - how you you propose we go about this? How can we both learn contribute and accomplish all that we have discussed thus far?
Posted: Tue Aug 22, 2006 5:13 pm
by Jenk
holy poop.. 2 posts before I submitted mine..
Right then.. TDD.. from the (two) articles/tutorials I have read.. write the test first.
Code: Select all
class RequestTest extends UnitTestCase
{
private $request;
function __construct ()
{
$this->UnitTestCase();
$this->request = new Request;
}
function TestGetMethod ()
{
}
function TestSetMethod ()
{
}
function TestRemoveSlashes ()
{
}
}
?>
details to be filled in as we progress..
Posted: Tue Aug 22, 2006 5:23 pm
by feyd
"Tests passed: 0 of 0, no failures" wooo!

-- couldn't resist.
Posted: Tue Aug 22, 2006 5:25 pm
by Jenk
ok.. revised..
Posted: Tue Aug 22, 2006 5:45 pm
by Chris Corbyn
The Ninja Space Goat wrote:Jenk wrote:btw, I'm not overly keen on this object having a set method - how often do you set $_POST/$_GET data? I can't remember a time I ever have

I haven't either
D11 - how you you propose we go about this? How can we both learn contribute and accomplish all that we have discussed thus far?
Looks like people carried on in my absence anyway so we're good.
My proposal was of course, write a test which inspects really basic thing the classs will be doing (i.e. reading a request variable). Remember, the code in your class needn't do anything at each stage your run a test at. It just needs to make the test give a pass before add something else. For example, if you were checking that a get() method returns a non-false result you'd test a true assertion on it. To pass that test initially you'd just place "return true" in the get() method

Eventually that will fail however so we'll need to adjust it.
OK I'm new to this too so I'm willing to risk making an idiot of myself
Let's start with thinking about how we want to interface with this. Sadly, I've already seen the original class so it may be jading my judgement but it seems all to natural to write this test initially.
Code: Select all
class testOfHttpRequest extends UnitTestCase
{
public function testCreationOfNewRequestMethod()
{
$_POST['dumy'] = 1;
$request = new HttpRequest('post');
$this->assertIdentical($_POST, $request->getRequestGlobal());
}
}
So to pass that we need something like this at minimum:
Code: Select all
class HttpRequest
{
protected $reqVar;
public function __construct($method)
{
if ($method == 'post') $this->reqVar = $_POST;
}
public function getRequestGlobal()
{
return $this->reqVar;
}
}
Ack! Who cares! It passes right? That's all we want at the moment... I think

Posted: Tue Aug 22, 2006 5:56 pm
by Luke
Nobody ever answered this question...
"wouldn't ajax be another type of request?"
I added it after original post... so just bringing it back up incase you guys missed it.
Posted: Tue Aug 22, 2006 6:17 pm
by Chris Corbyn
The Ninja Space Goat wrote:Nobody ever answered this question...
"wouldn't ajax be another type of request?"
I added it after original post... so just bringing it back up incase you guys missed it.
AJAX is not another type of request. AJAX uses either POST or GET. It's just a normal HTTP transaction
How do you feel about the first test? Happy to move on or something smells?
(PS: It's getting late (00:15) so I won't be awake much longer as I have work to do tomorrow

)
Posted: Tue Aug 22, 2006 6:20 pm
by Luke
d11wtq wrote:How do you feel about the first test? Happy to move on or something smells?
looks good to me
d11wtq wrote:(PS: It's getting late (00:15) so I won't be awake much longer as I have work to do tomorrow

)
Wow! Forgot about you being in the UK... 4:30pm here
Posted: Tue Aug 22, 2006 6:32 pm
by Jenk
<- in uk too but am a bit of an insomniac
Test looks good to me
Next stage?
Code: Select all
class testOfHttpRequest extends UnitTestCase
{
public function testCreationOfNewRequestMethod()
{
$_POST['dumy'] = 1;
$request = new HttpRequest('post');
$this->assertIdentical($_POST, $request->getRequestGlobal());
}
public function testRetrievalOfSingleRequestVar()
{
$_POST['dumy'] = 1;
$request = new HttpRequest('post');
$this->assertIdentical($_POST['dumy'], $request->get('dumy'));
}
}
updated class:
Code: Select all
class HttpRequest
{
protected $reqVar;
public function __construct($method)
{
if ($method == 'post') $this->reqVar = $_POST;
}
public function getRequestGlobal()
{
return $this->reqVar;
}
public function get ($name)
{
return $this->reqVar[$name];
}
}
P.S. I'm happy if we 'take turns' adding to the test

Posted: Tue Aug 22, 2006 6:43 pm
by Chris Corbyn
Looks good to me (at this stage) -- I have a thought in the back of my head but that's where it will remain right now... in the *back* of my head.
Taking turns makes sense.... and since I'm off to bed it suits me fine
This is going to turn out to be very basic to implement code for so I wouldn't mind writing an input filter (obsever/decorator) (using TDD of course) as an addon after this is done

Posted: Tue Aug 22, 2006 7:06 pm
by Jenk
but of course.. that's all part of the grand scheme of things. You need an object to decorate before you can decorate it
