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
