Jak nejlépe na API pro nette aplikaci
- jarda256
- Člen | 130
Ahoj, chtěl bych se zeptat, jak nejlépe vytvořit API pro aplikaci na nette. Používám nette 2.4 a doctrine. Chtěl bych vytvořit API, které by mohlo spolupracovat s android aplikací a taky aby umělo třeba na jinou stránku sdílet nějaká data. Našel by se někdo, kdo by mě prosím postrčil správným směrem? Předem díky
- Jan Tvrdík
- Nette guru | 2595
@jarda256 Zkus být konkrétní. Jestli chceš hello world API, tak třeba takto
class HelloWorldApiPresenter implements IPresenter
{
public function run(Request $request)
{
$name = $request->getParameter('name');
return new JsonResponse(['message' => "Hello $name"]);
}
}
Tj. dostaneš objekt s požadavkem a vrátíš jiný objekt s odpovědí.
- jarda256
- Člen | 130
@JanTvrdík Mám aplikaci, kde se člověk přihlásí. Aplikace rozpozná podle přihlašovacích údajů jestli ho má hodit do uživatelského nebo admin rozhraní. A jako admin vidím v /admin/events/show všechny akce a počty přihlášených lidí na akci. Tak tenhle počet bych chtěl přes api vytáhnout. Samozřejmě je víc věcí co bych chtěl přes api dělat, ale tohle je to nejjednodušší co by měla umět.
- Jan Tvrdík
- Nette guru | 2595
@jarda256 A kde je problém? Můžeš napsat třeba něco jako
class EventAttendeesCountApiPresenter implements IPresenter
{
/** @var \App\ApiAuthorizator */
private $apiAuthorizator;
/** @var \App\EventsFacade */
private $eventsFacade;
public function __construct(\App\ApiAuthorizator $apiAuthorizator, \App\EventsFacade $eventsFacade)
{
$this->apiAuthorizator = $apiAuthorizator;
$this->eventsFacade = $eventsFacade;
}
public function run(Request $request)
{
$apiKey = $request->getParameter('apiKey');
$apiSecret = $request->getParameter('apiSecret');
$eventId = $request->getParameter('eventId');
if (!$this->apiAuthorizator->isValid($apiKey, $apiSecret)) {
return new JsonResponse(['error' => 'Invalid API key or API secret']);
}
$attendeesCount = $this->eventsFacade->getAttendeesCount($eventId);
return new JsonResponse(['attendeesCount' => $attendeesCount]);
}
}
- Jan Tvrdík
- Nette guru | 2595
EventAttendeesCountApiPresenter si vložím do modules/api/presenters
Můžeš to dát v zásadě kamkoliv, ale složka
app/modules/api/presenters
je poměrně dobrý začátek.
pak dotaz na api je domena.cz/api/eventattendeescountapi
Stejně jako u všech ostatních presenterů závisí na nastavení
routování. Pokud použiješ výchozí router ze sandboxu (tj.
new Route('<presenter>/<action>', 'Homepage:default')
a
dáš tu třídu do modulu Api
(tj. celý název třídy bude
App\ApiModule\Presenters\EventAttendeesCountPresenter
, tak
výsledné URL bude /api.event-attendees-count/default
. To koncové
/default
je u API zbytečné, takže můžeš upravit routovací
masku na <presenter>[/<action>]
a URL se tím
zjednoduší na /api.event-attendees-count
.
Stejně jako u ostatních presenterů vygeneruješ URL třeba pomocí
n:href
v šabloně nebo pomocí třídy
LinkGenerator
.
Doporučuji se taky podívat na přednášku Jan Nedbal: Pragmatické tipy pro kvalitní REST API.
Pokud narazíš na libovolný další problém, tak se neboj zeptat dál.
- jarda256
- Člen | 130
@JanTvrdík Mám upravenou strukturu a i routerFactory. Tak jen jestli jsem dobře pochopil. Přidám pod modules Api…a do router factory tedy api část viz níže
struktura
app
├── modules
│ ├── Web
│ │ ├── presenters
│ │ │ ├── Default
│ │ │ │ ├── templates
│ │ │ │ │ └── default.latte
│ │ │ │ └── DefaultPresenter.php
│ │ │ └── Product
│ │ │ ├── templates
│ │ │ │ ├── default.latte
│ │ │ │ └── edit.latte
│ │ │ └── ProductPresenter.php
│ │ └── templates
│ │ └── @layout.latte
│ └── Admin
│ ├── modules
│ │ └── (more modules)
│ ├── presenters
│ │ └── Default
│ │ ├── templates
│ │ │ └── default.latte
│ │ └── DefaultPresenter.php
│ └── templates
│ └── @layout.latte
├── presenters
│ └── Base
│ └── BasePresenter.php
└── templates
└── @layout.latte
router
$router = new RouteList;
$router[] = new Route('[index.[php][html]]', 'Homepage:default', Route::ONE_WAY);
$router[] = new Route('[wp-login.php]', 'Homepage:default', Route::ONE_WAY);
$router[] = new Route('[<locale=cs cs|en>/]sign/<action>', 'Sign:', Route::ONE_WAY);
$api= new RouteList('Api');
$api[] = new Route('api/<presenter>/[<action>]');
$router[] = $api;
$admin = new RouteList('Admin');
$admin[] = new Route('[<locale=cs cs|en>/]admin/<presenter>/<action>[/<id>]', 'Dashboard:default');
$router[] = $admin;
$web = new RouteList('Web');
$web[] = new Route('[<locale=cs cs|en>/]<presenter>/<action>[/<id>]', 'Homepage:default');
$router[] = $web;
$router[] = new Route('[<locale=cs cs|en>/]<presenter>/<action>[/<id>]', 'Homepage:default');
return $router;
Editoval jarda256 (10. 1. 2017 20:46)
- lukas.kupka
- Člen | 6
@JanTvrdík Honzo prosím doplnil byste Hello World pro situaci, kdy jako response bude plaintext a ne JSON? Byl bych moc vděčný.
- Jan Tvrdík
- Nette guru | 2595
@lukas.kupka Stačí takto?
class HelloWorldApiPresenter implements IPresenter
{
public function run(Request $request)
{
$name = $request->getParameter('name');
return new TextResponse("Hello $name");
}
}
- lukas.kupka
- Člen | 6
@JanTvrdík Díky Honzo perfektní, pomohlo. Vysvětlete mi ještě jak routovat na tento presenter. Nemá žádné <action> metody, které bych v routeru nastavoval a mám takovouto strukturu adresářů:
app/ApiModule/presenters/SmsPresenter
a potřebuji aby fungovalo volání tohoto api bylo: http://www.example.com/api/sms?……
Editoval lukas.kupka (29. 5. 2017 21:07)
- Jan Tvrdík
- Nette guru | 2595
@lukas.kupka Mělo by fungovat něco jako
$apiRouter = new Route('api/<presenter>', ['module' => 'Api']);
- lukas.kupka
- Člen | 6
Díky moc
Jan Tvrdík napsal(a):
@lukas.kupka Mělo by fungovat něco jako
$apiRouter = new Route('api/<presenter>', ['module' => 'Api']);
- hancs
- Člen | 57
Ahoj,
už dva dny procházím fórum a hledám nějaký návod jak začít s API pro Nette a marně, až tady odpovědi od @JanTvrdík mi dost pomohly. Akorát pořád nevím jak na autorizaci. Neporadil by mi někdo? Stačil by alespoň nějaký odkaz na example, ze kterého by to člověk dobře pochopil.
Předem díky.
- Ondřej Kubíček
- Člen | 494
podíval bych se na Appite – https://github.com/apitte/core
propracované knihovna na api, je tam i middleware na authorizaci
- mcmatak
- Člen | 504
tak jsem zkoušel ublaboo, pak sem to přepsal na apitte, a s tím se trápím už dva dny :( nepomohl by mi někdo s nastavením? rozběhnout tracy middleware mi nefunguje vůbec, debugplugin se chytne jen někdy netuším na čem to závisí, pro jeden controller se chytne pro druhý ne, na developmentu něco funguje na produkčním zase ne a nedokážu zalogovat error
pls. help :)
- chemix
- Nette Core | 1310
@mcmatak mrkni na projekt forest je tam nastaven cely apitte vcetne napojeni na doctrine https://github.com/…tte-skeleton
treba tam vyctes co potrebujes, pokud ne … tak je to zaminka tam neco noveho doplnit
- mcmatak
- Člen | 504
hm asi mi nechodí emaily? díky, ale o tvé odpovědi sem vůbec nevěděl
jinak jestli si matně vzpomínám musel sem trochu apitte přepsat nějaké drobnosti, koncepci snapshotexception sem vůbec nepochopil a pak tam byly nějaké chybky s násobným odesíláním header, což shodilo projekt a neřeklo vůbec kvůli čemu, což je mooc fajn věc, protože to je debugováníčko na pár dní
teď spíš bojuji s tím jak nejlépe mockovat api, ale založím na to nové vlákno
- Marek Bartoš
- Nette Blogger | 1280
@mcmatak
Snapshot je interní mechanizmus, proto vůbec není v dokumentaci.
Vícenásobné odesílání hlaviček znamená většinou spíš nepochopení
PSR-7. withHeader() přepíše stejnojmennou hlavičku, withAddedHeader()
přidá hlavičku nezávisle na tom, zda už stejnojmenná existuje
Pamatuju si, že zrovna v tvém případě to kolidovalo s nette/http, které
odesílá hlavičky ihned při startu DIC, což je bohužel kombinace několika
menších problémů a nedalo se to předvídat.
K middlewares – contributte/middlewares byly původně určené pro nette, budou se konceptuelně oddělovat. Konkrétně u tracy middleware by bylo asi dobré zmínit, že řeší totéž, co nette/bootstrap, jeho použití s bootstrapem není tedy nutné.
Debug plugin – U negotiations byl bug → pokud byla na konci url proměnná, např. {id}, tak nefungovaly koncovky .debug apod. Spraveno tuším od 0.5?
Logování errorů – od 0.5 má apitte vlastní error handler, od 0.7 (nynější dev alias) se zapíná automaticky i logování pokud je dostupný PSR logger
Na developmentu něco funguje, na produkci ne – nemám vůbec tušení, co řešíš nebo čím by to mohlo být. Pokud jde o debug mód, tak v tom je jen minimum změn oproti produkci. Chtělo by to víc rozvést.
A příště prosím piš někam, kde máš jistotu, že to zahlédne autor knihovny.
Editoval Mabar (7. 9. 2019 21:51)
- bajzecer
- Člen | 2
@Mabar Ohladom logovania errorov, pouzivam PsrLogErrorHandler, ale potreboval by som, aby sa logovali vsetky exceptions (aj ApiException). Ako najjednoduchsie zapnem logovanie vsetkych errorov, prosim? Musim si vytvarat vlastny plugin namiesto CoreServicesPlugin?
V PsrLogErrorHandler je podmienka:
<script>
if (!($error instanceof ApiException)) {
$this->logger->error($error->getMessage(), ['exception' => $error]);
}
</script>
- Marek Bartoš
- Nette Blogger | 1280
Na co to potřebuješ? Potomci ApiException (ServerErrorException a
ClientErrorException) jsou čistě pro zobrazení chyby uživateli. Pokud tento
typ výjimky způsobila skutečná výjimka, tak můžeš využít
ClientErrorException::create()->withPrevious($exception)
a
originální chyba se zaloguje.
Služby z extensions (a tedy i apitte pluginů) můžeš přetěžovat jako kterékoli jiné služby v neonu, přes název služby, jen vyměníš třídu.
Editoval Mabar (28. 11. 2019 17:27)
- bajzecer
- Člen | 2
Aha uz chapem, super, to je chytre!
Skusil som ClientErrorException::create()->withPrevious($exception)
(apitte 0.6.0, php 7.3.12), ale chyba sa nezalogovala, pretoze instancia
ClientErrorException obsahuje Exception v premennej $previous, ale metoda
getPrevious vracia null. Asi sa previous musi nastavit uz v construct, aby to
PHP chapalo.
Screenshot xdebug
Mam to zareportovat? Myslim, ze robim vsetko spravne.