Hi David,

> I'd make it
>
> <route name="books" pattern="^/books" module="Books">
>   <route name=".index" pattern="^/$" action="Index" />
>   <route name=".book" pattern="^/(id:\d+)$" action="Book" />
> </route>
>
> Mind the trailing slash in /books/.

OK, thanks. Will do.

> You should also consider using HTTP Accept headers rather than .xml
> and .json.
>

I did originally consider doing that but then read this article which
suggests that this is not such a  great idea:
http://www.newmediacampaigns.com/page/browser-rest-http-accept-headers.
Would you agree with the author's views on this?

If I were to do this, I assume I'd need to update the WebRequest class
to check the Accept header instead of the Content-type header. If both
are present, which one should get preference?

> Furthermore, it might really be a good idea to swap the method
> mappings for PUT and POST...

OK

Would you also let me know your thoughts on the questions below:

>> 3. For DELETE operations using the XML API, is it correct to return a
>> 204 response code and no content at all? Or should I return a 200 code
>> and an XML document with a <status>OK</status> type of body?
>>
>> 4. I'm setting an attribute in each execute() method indicating the
>> HTTP request method. I then check this in the view to decide whether
>> to send a 201 or 204 status code to the client. I'm pretty sure
>> there's a better way to do this...
>>
>> 5. In case of errors in a POST/PUT/DELETE operation, what response
>> should I send back for (a) JSON and (b) XML requests?

Many thanks,

Vikram



> On 23.02.2010, at 10:24, [email protected] wrote:
>
>> Hi all,
>>
>> I have put together a simple REST API that supports both XML and JSON
>> request/response bodies. The code for this is inline with this email.
>> I tested this with jQuery and it seems to be fine, but I suspect the
>> internals could do with some improvements.
>>
>> I would be very interested in any comments to make this better.
>>
>> I also plan to turn this into a tutorial that will get published
>> online, so am keen that it conforms to Agavi best practices, for the
>> benefit of other developers.
>>
>> routing.xml
>> -------------
>> <routes>
>>  <!-- handler for .xml requests -->
>>  <route name="json" pattern=".json$" cut="true" stop="false"
>> output_type="json" />
>>  <route name="xml" pattern=".xml$" cut="true" stop="false"
>> output_type="xml" />
>>
>>  <!-- REST-style routes -->
>>  <route name="books" pattern="^/books$" module="Default"
>> action="Books" />
>>  <route name="book" pattern="^/books/(id:\d+)$" module="Default"
>> action="Book" />
>> </routes>
>>
>> BookAction
>> ---------------
>>
>> <?php
>>
>> class Default_BookAction extends ExampleAppDefaultBaseAction
>> {
>>       public function getDefaultViewName()
>>       {
>>               return 'Success';
>>       }
>>
>>       public function executeRead(AgaviRequestDataHolder $rd)
>>       {
>>    $q = Doctrine_Query::create()
>>          ->from('Book b')
>>          ->addWhere('id = ?', $rd->getParameter('id'));
>>    $result = $q->execute(array(), Doctrine::HYDRATE_ARRAY);
>>    $this->setAttribute('result', $result);
>>    $this->setAttribute('op', 'get');
>>    return 'Success';
>>  }
>>
>>       public function executeCreate(AgaviRequestDataHolder $rd)
>>       {
>>    $book = Doctrine::getTable('book')->find($rd->getParameter('id'));
>>    $book->author = $rd->getParameter('author');
>>    $book->title = $rd->getParameter('title');
>>    $book->save();
>>    $this->setAttribute('result', array($book->toArray()));
>>    $this->setAttribute('op', 'put');
>>    return 'Success';
>>  }
>>
>>       public function executeRemove(AgaviRequestDataHolder $rd)
>>       {
>>    $q = Doctrine_Query::create()
>>          ->delete('Book')
>>          ->addWhere('id = ?', $rd->getParameter('id'));
>>    $result = $q->execute();
>>    $this->setAttribute('result', null);
>>    $this->setAttribute('op', 'delete');
>>    return 'Success';
>>  }
>>
>> }
>>
>> ?>
>>
>> BooksAction
>> ---------------
>> <?php
>>
>> class Default_BooksAction extends ExampleAppDefaultBaseAction
>> {
>>       public function getDefaultViewName()
>>       {
>>               return 'Success';
>>       }
>>
>>       public function executeRead(AgaviRequestDataHolder $rd)
>>       {
>>    $q = Doctrine_Query::create()
>>          ->from('Book b');
>>    $result = $q->execute(array(), Doctrine::HYDRATE_ARRAY);
>>    $this->setAttribute('result', $result);
>>    $this->setAttribute('op', 'get');
>>    return 'Success';
>>  }
>>
>>       public function executeWrite(AgaviRequestDataHolder $rd)
>>       {
>>    $book = new Book;
>>    $book->author = $rd->getParameter('author');
>>    $book->title = $rd->getParameter('title');
>>    $book->save();
>>    $this->setAttribute('result', array($book->toArray()));
>>    $this->setAttribute('op', 'post');
>>    return 'Success';
>>
>>  }
>> }
>>
>> ?>
>>
>> BookSuccessView
>> ----------------------
>> <?php
>>
>> class Default_BookSuccessView extends ExampleAppDefaultBaseView
>> {
>>       public function executeHtml(AgaviRequestDataHolder $rd)
>>       {
>>               $this->setupHtml($rd);
>>       }
>>
>>       public function executeJson(AgaviRequestDataHolder $rd)
>>       {
>>       if ($this->getAttribute('op') == 'delete') {
>>               $this->getResponse()->setHttpStatusCode('204');
>>       }
>>    return json_encode($this->getAttribute('result'));
>>       }
>>
>>
>>       public function executeXml(AgaviRequestDataHolder $rd)
>>       {
>>       $result = $this->getAttribute('result');
>>       if ($this->getAttribute('op') == 'delete') {
>>               $this->getResponse()->setHttpStatusCode('204');
>>       }
>>       if ($result) {
>>      $dom = new DOMDocument('1.0', 'utf-8');
>>      $root = $dom->createElement('result');
>>      $dom->appendChild($root);
>>      $xml = simplexml_import_dom($dom);
>>      foreach ($result as $r) {
>>        $item = $xml->addChild('item');
>>        $item->addChild('id', $r['id']);
>>        $item->addChild('author', $r['author']);
>>        $item->addChild('title', $r['title']);
>>      }
>>       return $xml->asXml();
>>       }
>>       }
>> }
>>
>> ?>
>>
>> BooksSuccessView
>> ------------------------
>> <?php
>>
>> class Default_BooksSuccessView extends ExampleAppDefaultBaseView
>> {
>>       public function executeHtml(AgaviRequestDataHolder $rd)
>>       {
>>               $this->setupHtml($rd);
>>       }
>>
>>       public function executeJson(AgaviRequestDataHolder $rd)
>>       {
>>       $result = $this->getAttribute('result');
>>       if ($this->getAttribute('op') == 'post') {
>>               $this->getResponse()->setHttpStatusCode('201');
>>               $this->getResponse()->setHttpHeader('Location',
>> $this->getContext()->getRouting()->gen('book', array('id' =>
>> $result[0]['id'])));
>>       }
>>    return json_encode($result);
>>       }
>>
>>       public function executeXml(AgaviRequestDataHolder $rd)
>>       {
>>       $result = $this->getAttribute('result');
>>       if ($this->getAttribute('op') == 'post') {
>>               $this->getResponse()->setHttpStatusCode('201');
>>               $this->getResponse()->setHttpHeader('Location',
>> $this->getContext()->getRouting()->gen('book', array('id' =>
>> $result[0]['id'])));
>>       }
>>    $dom = new DOMDocument('1.0', 'utf-8');
>>    $root = $dom->createElement('result');
>>    $dom->appendChild($root);
>>    $xml = simplexml_import_dom($dom);
>>    foreach ($result as $r) {
>>      $item = $xml->addChild('item');
>>      $item->addChild('id', $r['id']);
>>      $item->addChild('author', $r['author']);
>>      $item->addChild('title', $r['title']);
>>    }
>>       return $xml->asXml();
>>       }
>>
>> }
>>
>> ?>
>>
>> Custom WebRequest class (per David's suggestion)
>> -----------------------------------------------------------------
>> <?php
>> class ExampleAppWebRequest extends AgaviWebRequest {
>>
>>    public function initialize(AgaviContext $context, array
>> $parameters = array()) {
>>        parent::initialize($context, $parameters);
>>        $rd = $this->getRequestData();
>>        if (stristr($rd->getHeader('Content-Type'), 'application/
>> json')) {
>>            switch ($_SERVER['REQUEST_METHOD']) {
>>                case 'PUT': {
>>                    $json = $rd->removeFile('put_file')->getContents();
>>                    break;
>>                }
>>                case 'POST': {
>>                    $json = $rd->removeFile('post_file')-
>> >getContents();
>>                    break;
>>                }
>>                default: {
>>                    $json = '{}';
>>                }
>>            }
>>            $rd->setParameters(json_decode($json, true));
>>        }
>>        if (stristr($rd->getHeader('Content-Type'), 'text/xml')) {
>>            switch ($_SERVER['REQUEST_METHOD']) {
>>                case 'PUT': {
>>                    $xml = $rd->removeFile('put_file')->getContents();
>>                    break;
>>                }
>>                case 'POST': {
>>                    $xml = $rd->removeFile('post_file')->getContents();
>>                    break;
>>                }
>>                default: {
>>                    $xml = '';
>>                }
>>            }
>>            $rd->setParameters((array)simplexml_load_string($xml));
>>        }
>>
>>    }
>>
>> }
>> ?>
>>
>> Questions to the list:
>>
>> 1. Would you recommend always remapping POST to "create" when building
>> REST APIs with Agavi?
>>
>> 2. Is there any suggestion to improve the routing configuration for
>> the /books route?
>>
>> 2a. Currently the REST endpoint is /books.xml or /books.json depending
>> on the format required. How could I alter this so that the router
>> looks for a ?format=xml|json parameter and sets the output type
>> accordingly?
>>
>> 3. For DELETE operations using the XML API, is it correct to return a
>> 204 response code and no content at all? Or should I return a 200 code
>> and an XML document with a <status>OK</status> type of body?
>>
>> 4. I'm setting an attribute in each execute() method indicating the
>> HTTP request method. I then check this in the view to decide whether
>> to send a 201 or 204 status code to the client. I'm pretty sure
>> there's a better way to do this...
>>
>> 5. In case of errors in a POST/PUT/DELETE operation, what response
>> should I send back for (a) JSON and (b) XML requests?
>>
>> Many thanks for your comments and advice,
>>
>> Vikram
>>
>> _______________________________________________
>> users mailing list
>> [email protected]
>> http://lists.agavi.org/mailman/listinfo/users
>>
>
> -------------- next part --------------
> A non-text attachment was scrubbed...
> Name: smime.p7s
> Type: application/pkcs7-signature
> Size: 3823 bytes
> Desc: not available
> URL: 
> <http://lists.agavi.org/pipermail/users/attachments/20100223/9654c64e/attachment-0001.bin>
>
> ------------------------------
>
> _______________________________________________
> users mailing list
> [email protected]
> http://lists.agavi.org/mailman/listinfo/users
>
>
> End of users Digest, Vol 40, Issue 9
> ************************************
>

_______________________________________________
users mailing list
[email protected]
http://lists.agavi.org/mailman/listinfo/users

Reply via email to