Jak nejlépe na API pro nette aplikaci

jarda256
Člen | 130
+
0
-

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
+
+2
-

Na validaci dat můžeš použít JSON schema.

jarda256
Člen | 130
+
0
-

@JanTvrdík Díky. Přednášku jsem zkouknul, ale měl jsem namysli úplný začátek jak vytvořit api pro nette aplikaci.

Jan Tvrdík
Nette guru | 2595
+
+2
-

@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
+
0
-

@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
+
+1
-

@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]);
	}
}
jarda256
Člen | 130
+
0
-

@JanTvrdík Problém je, že jsem to nikdy nedělal, tak jsem nevěděl ani jak bych začal. Jestli to tedy dobře chápu tak EventAttendeesCountApiPresenter si vložím do modules/api/presenters a pak dotaz na api je domena.cz/api/eventattendeescountapi ??

Jan Tvrdík
Nette guru | 2595
+
+2
-

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
+
0
-

@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
+
0
-

@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
+
+5
-

@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
+
0
-

@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
+
+1
-

@lukas.kupka Mělo by fungovat něco jako

$apiRouter = new Route('api/<presenter>', ['module' => 'Api']);
lukas.kupka
Člen | 6
+
0
-

Díky moc

Jan Tvrdík napsal(a):

@lukas.kupka Mělo by fungovat něco jako

$apiRouter = new Route('api/<presenter>', ['module' => 'Api']);
iNviNho
Člen | 352
+
0
-

@lukas.kupka možno ti to bude hlásiť problém, že neexistuje na to mapping, to bude potom treba pridať v configu…

hancs
Člen | 57
+
0
-

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
+
+2
-

podíval bych se na Appite – https://github.com/apitte/core
propracované knihovna na api, je tam i middleware na authorizaci

mcmatak
Člen | 490
+
0
-

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 | 1294
+
+2
-

@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 | 490
+
0
-

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 | 1146
+
0
-

@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
+
0
-

@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 | 1146
+
+1
-

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
+
0
-

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.

Marek Bartoš
Nette Blogger | 1146
+
0
-

Založ issue, je možné že jde o bug