Podpora REST služeb v Nette 2.1
- David Grudl
- Nette Core | 8218
Sám nic takového nechystám, ale pokud se toho někdo chopí a připraví RFC / implementaci, může být.
- Erik Ferčák
- Člen | 10
Potreboval som niečo podobné už v Netee 0.9.x, napísal som si na to vlastnú RestRoute:
<?php
class RestRoute extends Route
{
const METHOD_POST = 4;
const METHOD_GET = 8;
const METHOD_PUT = 16;
const METHOD_DELETE = 32;
const RESTFUL = 64;
public function match(IHttpRequest $httpRequest)
{
$httpMethod = $httpRequest->getMethod();
if (($this->flags & self::RESTFUL) == self::RESTFUL) {
$presenterRequest = parent::match($httpRequest);
if ($presenterRequest != NULL) {
switch ($httpMethod) {
case 'GET':
$action = 'default';
break;
case 'POST':
$action = 'create';
break;
case 'PUT':
$action = 'update';
break;
case 'DELETE':
$action = 'delete';
break;
default:
$action = 'default';
}
$params = $presenterRequest->getParams();
$params['action'] = $action;
$presenterRequest->setParams($params);
return $presenterRequest;
} else {
return NULL;
}
}
if (($this->flags & self::METHOD_POST) == self::METHOD_POST
&& $httpMethod != 'POST') {
return NULL;
}
if (($this->flags & self::METHOD_GET) == self::METHOD_GET
&& $httpMethod != 'GET') {
return NULL;
}
if (($this->flags & self::METHOD_PUT) == self::METHOD_PUT
&& $httpMethod != 'PUT') {
return NULL;
}
if (($this->flags & self::METHOD_DELETE) == self::METHOD_DELETE
&& $httpMethod != 'DELETE') {
return NULL;
}
return parent::match($httpRequest);
}
}
?>
Použitie:
<?php
$router[] = new RestRoute('/1.0/resource/<id [0-9]+>', array(
'presenter' => 'Resource',
'action' => 'default',
), RestRoute::METHOD_GET);
$router[] = new RestRoute('/1.0/resource/<id [0-9]+>', array(
'presenter' => 'Resource',
'action' => 'delete'
), RestRoute::METHOD_DELETE);
?>
- Tharos
- Člen | 1030
Již nějaký měsíc používám lehce modifikovanou verzi té od Erika (už ji zde na fóru totiž jednou publikoval) a funguje výborně. Takže já bych klidně hlasoval za její přímé začlenění. :)
V abstraktním předkovi REST presenterů mám také takovou zkratku pro pohodlnější dolování proměnných předaných v těle HTTP požadavku:
/**
* @param string $name
* @return mixed
*/
protected function getRestParameter($name)
{
$request = $this->getHttpRequest();
if ($request->getMethod() === 'GET') { // TODO: zkonstantit
return $request->getQuery($name);
} else {
return $request->getPost($name);
}
}
To pochopitelně už nemá smysl cpát přímo do frameworku, ale uvádím to jen pro inspiraci, protože je to praktické.
- Filip Procházka
- Moderator | 4668
Mně se to nelíbí. Tohle by měl řešit presenter, né router. Respektive, nevidím důvod, proč by každá akce nemohla mít annotaci, jaké HTTP metody zpracovává (cca tak jako v RoR). To chcete mít milion rout?
Editoval HosipLan (9. 5. 2012 10:39)
- Tharos
- Člen | 1030
@HosipLan: Jasně, ale tohle můžeš říct o celém routování. :) Proč by každá akce nemohla mít anotaci, jakou masku „matchne“… když to přeženu (jasně, že by to bylo komplikovanější). Mně se to Erikovo řešení líbí, protože HTTP sloveso považuji za informaci pro router, skoro mi to přijde jako detašovaná součást masky. :)
Myslím, že existují rozumné důvody pro oba přístupy. Namátkou vyjmenuji nějaké výhody zde zmíněného řešení: že vše ohledně routování je na jednom místě, nejsou zapotřebí anotace.
Jinak osobně bych dokázal bych žít s oběma variantami a poděkoval bych tomu, kdo by v duchu jakékoliv z nich připravil pull. :) Protože si myslím, že by lepší podpora REST architektury Nette slušela.
Editoval Tharos (9. 5. 2012 10:48)
- Filip Procházka
- Moderator | 4668
Máš pravdu, že u rout je potřeba rozlišovat, jestli je možné danou akci vůbec zpracovat, to tedy musí být na úrovni routování. Jak se ale zbavit potřeby, mít routu pro každou hloupost?
- Tharos
- Člen | 1030
No, to je právě to, že těch rout v praxi nevzniká až tolik, kolik by
se mohlo na první pohled zdát. :) Stručné vysvětlení je, že uvedená
routa ke konci matche stejně volá parent::match(...)
, takže
potřeba mít routu pro každou hloupost je úplně stejná, jako u routování
pomocí vestavěné Route
.
Dále – velmi užitečný je ten příznak RESTFUL
, který Ti
v jedné routě vyřeší CRUD nad jednou „záležitostí“ /entitou,
objektem, prvkem… určitě víš, co tím myslím :)/ a naroutuje Ti jej na
individuální akce. Dokážu si představit jednu jedinou routu s maskou
/<presenter>
s příznakem RESTFUL
, se kterou
bych si vystačil pro kompletní CRUD nad uživateli, články, vzkazy,
produkty…
Hodně záleží na designu API. Pokud jej chceš mít vyloženě CRUDové, vystačíš si s minimálním počtem rout. Pokud jej chceš mít více use-case lazené, tak se bohužel více routám nevyhneš, ale ty bys ta vstupní URL stejně někde nějak musel naroutovat.
Editoval Tharos (9. 5. 2012 13:09)
- David Grudl
- Nette Core | 8218
REST je forma routování používající pro akci HTTP metodu. Cílem REST routeru tedy není rozlišovat routy podle metody, ale podle metod stanovit action. V REST routě tak stačí uvést jen zdroj, se kterým pracuju, a to je všechno.
$routes[] = new RestRoute(‚posts‘);
A umožní mi to následující:
GET /posts | index | Render a list of all post
GET /posts/new | new | Render a form for creating a single new post
POST /posts | create | Create a single new post from the received data
GET /posts/1 | show | Render a single existing post
GET /posts/1/edit | edit | Render a form for editing a single existing post
PUT /posts/1 | update | Update a single existing post based on the received data
DELETE /posts/1 | destroy | Destroy a single existing post
Používání HTTP metod, jako je tomu třeba v Rails, je dle mého omyl, protože takové odkazy nelze vytvářet. Leda AJAXem a ne v každém browseru. Proto musí fungovat tohle:
GET /posts | index | Render a list of all post
GET /posts/new | new | Render a form for creating a single new post
GET /posts/create | create | Create a single new post from the received data
GET /posts/1 | show | Render a single existing post
GET /posts/1/edit | edit | Render a form for editing a single existing post
GET /posts/1/update | update | Update a single existing post based on the received data
GET /posts/1/destroy | destroy | Destroy a single existing post
Což můžeme zapsat cca jako
$router[] = new Route('<resource>[/<id>][/<action>]');
RestRoute by mělo mít oproti Route navíc logiku, která podle přítomnosti/nepřítomnosti id zvolí mezi show a index apod. Samozřejmě může taky respektovat HTTP metodu.
- paranoiq
- Člen | 392
definice metod, jak ji píšeš mi nějak nesedí. dle mě:
POST = update (UPDATE v SQL, jazyce mého kmene)
PUT = create (INSERT nebo REPLACE v SQL)
koukni na „idempotentní metody“: https://cs.wikipedia.org/…fer_Protocol
- Vojtěch Dobeš
- Gold Partner | 1316
paranoiq Hm, Davidem odkazovaný článek zase tvrdí opak. PUT jako UPDATE a POST jako CREATE implementuje například i Backbone.js, a předpokládám, že tomu tak bude s naprostou většinou knihoven využívajícíh RESTový CRUD.
- Tharos
- Člen | 1030
Rozhodně PUT === UPDATE, nebo alespoň pro ty, co chtějí být kompatibilní s REST specifikací a 99,99% světa. :)
@David Grudl: Přiznám se, že jsem z toho Tvého popisu trochu zmaten a přemýšlím, jestli jsem o pár příspěvků výše nemluvil o koze a Ty teď o voze. :)
Všechna čistokrevná REST API, se kterými jsem se kdy setkal, rozlišovala
to, co se má dít, právě podle kombinace metody a URL. Zkrátka když volám
URL /posts
metodou GET, čtu data, když volám URL
/posts
metodou POST, vytvářím nový záznam. IMHO tam nepatří
žádné /posts/create
a podobné „berličky“ (to ještě za
chvíli rozvedu).
Že na to nelze vytvořit odkaz přece vůbec nevadí, protože kdo by odkazoval na webu přímo na nějaké REST API? K čemu by to mělo být dobré? K REST API se většinou vytváří pouze API dokumentace a v té se zpravidla jako API metoda považuje kombinace metody a URL adresy.
A také mi moc nedává smysl Tebou zmíněná URL /posts/new
,
která by měla vykreslovat nějaký formulář? REST API typicky vrací
nějakou JSON nebo XML strukturu. Nějaké HTML přece patří až do aplikaci,
která je nad API postavená.
Ještě k těm berličkám… Již jsem zažil taková REST API, ve kterých
se vše volalo GET metodou a URL měly právě tvar /posts/create
,
/posts/delete
a tak podobně, ale tohle už není REST API
v pravém slova smyslu.
No, přiznám se, že pokud by se do Nette mělo dostat nativně něco, co není REST v pravém slova smyslu… tak to bych tam asi radši osobně nedával nic. :)
Editoval Tharos (16. 5. 2012 22:45)
- Vojtěch Dobeš
- Gold Partner | 1316
Mohl bych poprosit o nějaké příklady API využívajících PUT k updatu? Pro rozšíření obzorů :)
- Honza Marek
- Člen | 1664
Ostatně může fungovat pravý rest + berličky najednou. DELETE /posts/1 a třeba GET /posts/1/delete nebo GET /posts/1?method=delete by routa hodila na tu samou akci.
Vykreslování formuláře mi přijde taky navíc.
- Tharos
- Člen | 1030
@vojtech.dobes: No tak obzory Ti rozšířím snadno :), namátkou třeba REST API od project trackeru JIRA (ono to tu asi nikdo znát nebude, ale třeba s tím jsem právě přišel do styku…). Pokud se Ti na tom webu nechce API doc dlouze hledat, koukni rovnou sem.
No a pak třeba viz originální práce od Roye Fieldinga, Wikipedia… Kde vůbec vzniklo, že by k vytváření záznamu měl sloužit PUT?
Jinak nekoukejte na „REST API“ velkých sociálních sítí ala Twitter, protože ty jsou fakt plná berliček (v podstatě se omezují pouze na metody GET a POST a chybějící výrazivo nahrazují právě něčím v URL…).
- Vojtěch Dobeš
- Gold Partner | 1316
Díky Tharos :) Eh… PUT, UPDATE, POST… spletl jsem si to, teprve teď mi došlo, že ty, Hosiplan i já říkáme to samé :)
- David Grudl
- Nette Core | 8218
Tharos napsal(a):
@David Grudl: Přiznám se, že jsem z toho Tvého popisu trochu zmaten a přemýšlím, jestli jsem o pár příspěvků výše nemluvil o koze a Ty teď o voze. :)
Snažím se říct jen to, že implementace by měla být taková, aby
- stačila jen jedna routa
- existoval fallback pro případy, že nelze použít jiné HTTP metody, protože to dost často nelze
- jasir
- Člen | 746
Jestli jsem pochopil správně tu Erikovu routu, tak přeci jediná stačí obsloužit všechny REST presentery, například takto:
<?php
$router[] = new RestRoute('rest/<presenter>/<id [0-9]+>', array(
'presenter' => 'Resource',
'action' => 'default',
), RestRoute::RESTFUL);
?>
Tedy funguje úplně stejně normální routování, akorát
action
se přířadí podle metody.
Editoval jasir (17. 5. 2012 1:26)
- Patrik Votoček
- Člen | 2221
V Express.js se používá jako fallback parametr _method pomocí kterého lze „změnit“ HTTP metodu tam kde to nejde nativně. Převod se pak provádí na nejnižší možné úrovni spracování HTTP požadavku ve frameworku (tj. ostatní části systému jsou od toho celkem dobře odstíněny).
- Vojtěch Dobeš
- Gold Partner | 1316
Pro pestřejší paletu, Backbone.js
zase využívá hlavičky X-HTTP-Method-Override
, kde uvádí
název metody (PUT
nebo DELETE
), ve skutečnosti
realizované skrze POST
.
- mishak
- Člen | 94
Shodou okolností nebo protože nic neexistovalo sem na něco podobného
dělal.
https://gist.github.com/2781069
není to dokončené, ale už tak obsluhuji, některé požadavky.
- David Grudl
- Nette Core | 8218
jasir napsal(a):
Jestli jsem pochopil správně tu Erikovu routu, tak přeci jediná stačí obsloužit všechny REST presentery
Máš pravdu, umí to.
- patriksima
- Člen | 58
Tharos napsal(a):
Již nějaký měsíc používám lehce modifikovanou verzi té od Erika (už ji zde na fóru totiž jednou publikoval) a funguje výborně. Takže já bych klidně hlasoval za její přímé začlenění. :)
V abstraktním předkovi REST presenterů mám také takovou zkratku pro pohodlnější dolování proměnných předaných v těle HTTP požadavku…
Ahoj, mě to v PHP 5.3.5 nefunguje. Musím například PUT request číst takto:
parse_str(file_get_contents("php://input"),$put_vars);
možná jdou pro čtení používat i tyto funkce http://php.net/…est-body.php
Editoval patriksima (25. 7. 2012 9:13)
- patriksima
- Člen | 58
A kdyby někdo potřeboval výjimky zobrazovat v jsonu, tak to jde v presenteru udělat nějak takhle:
protected function startup()
{
parent::startup();
// debug off, jinak nam do jsonu poleze debug bar
Debugger::enable(Debugger::PRODUCTION);
// json exception handler
set_exception_handler(function($e){
echo json_encode(array('error'=>$e->getMessage()));
});
}
Editoval patriksima (25. 7. 2012 9:57)
- Zdeněk Topič
- Člen | 14
Zdravím, jak toto dopadlo prosím?
Dále se chci zeptat, nebylo by také dobré, když už teda framework si je vědom toho, že zpracovává požadavek na REST službu, aby předal i data do parametru metody? Použiju Davidům příklad:
GET /posts | actionDefault($id = null, $data = null) | Render a list of all post
GET /posts?offset=10 | actionDefault($id = null, $data = null) | Render a form for creating a single new post
POST /posts | actionPostDefault($data) | Create a single new post from the received data
GET /posts/1 | actionDefault($id = null, $data = null) | Render a single existing post
PUT /posts/1 | actionPut($id, $data = null) | Update a single existing post based on the received
Samozřejmě proměnná $data tedy bude vždy obsahovat pole/strukturu s daty dané metody.
Editoval Zdeněk Topič (15. 11. 2012 23:59)