[TDD] Who wants to help me build an http request class?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

[TDD] Who wants to help me build an http request class?

Post 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?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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 :)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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?
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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..
Last edited by Jenk on Tue Aug 22, 2006 5:25 pm, edited 1 time in total.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

"Tests passed: 0 of 0, no failures" wooo! :) -- couldn't resist.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

:lol:

ok.. revised..
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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 :?
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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 :( )
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post 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
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post 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 :)
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

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