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 :D

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 :D
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! :D
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
:lol:

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 :P 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 Image
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 :P

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 :D

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 :)