instagram

WordPress backend REST API och Silex/javascript frontend

WordPress har sedan en tid ett REST API (som en plugin). Äntligen kan man tycka. Det fina är att man enkelt kan använda wp som ett datalager, och därmed frikoppla backend från frontend. Vi har sedan några veckor ett projekt på jobbet som gör just detta.

Varför är det intressant att del upp frontend och backend? Det finns flera olika anledningar till detta. Monoliter kan vara svåra att arbeta med, de tenderar att bli stora. Många små appar, kan vara lättare att förvalta. En annan sak är att vi vill skapa ett gemensamt datalager för flera olika typer av appar, vanliga webb-appar, men även ios och android-appar. Ett backend, flera olika frontar, på olika tekniker.

WordPress är inte en klockren match med Composer, och det finns många väldigt bra paket som vi gärna vill använda. Det är inget fel på wp:s plugin-system men det finns riktigt bra paket hos Packagist.

En tredje anledning är att wp i sig har frontend-begränsningar vi vill komma ifrån. Plattformen är tex inte bra på att hantera flera olika domäner.

I backend har vi en helt standard WP multisite, där vi har aktiverat WP-REST_API (ver 1.2). Vi har skrivit några nya egna endpoints som är specifika för vårt projekt, och vi stängt de vi inte vill exponera ut (tex. användare).

I frontend har vi en Silex-applikation. Varför använder vi just Silex? Jo, våra utvecklare är duktiga på php, så därmed blir uppstarstiden kortare. Silex bygger dessutom på Symfony, vilket är ett väl använt och supportat projekt. Vi vill använda Composer, och vi vill bygga med en väl använd stadard (psr-*). Det finns bra alternativ, tex Slim, men vårt val föll på Silex.

Hittills använder vi följande paket.

silex/silex
symfony/monolog-bridge
symfony/twig-bridge
guzzlehttp/guzzle
moust/silex-cache
igorw/config-service-provider

silex är ramverket, monolog-bridge för att skriva loggar i utvecklingsmiljön, twig-bridge för att använda templates med Twig, guzzle för att prata med wp:s api, silex-cache för att spara objekt i memcached och slutligen config-service-provider för att skapa configs i dev/prod-envs.

Projektets struktur är väldigt enkelt och ser ut ungefär på detta sätt.

src/
  Project/
    Controller/
    Wp.php
  config/
  app.php
  routes.php
templates
  /desktop
  /mobile
web/
  index.php

En fin sak med Silex är att saker är små och enkla. Det som har jobbat med Drupl vet jag jag talar om. Våra routes skriver vi tex bara rakt av i en fil som inkluderas i index.php.

$app->get('/', 'Job\Controller\IndexController::indexAction');

Kommunikationen med WP-REST-API rest api hanteras av Wp.php. För att hämta ut poster har vi metoder som motsvarar endpoints i API:et. För att hämta poster ser det ut så här.

public function posts( $page = null ) {
    return $this->_get('posts', ['page' => $page]);
}

Vi skriver metoder för de endpoint vi vill använda och kör alla genom _get som använder Guzzle cachar resultatet i memcached. Vi skapar en nyckel baserad på url till endpointen man ställer frågan till. Sedan tittar vi i cachen, hittar vi inget så låter vi Guzzle ställa skicka en request. Svaret caches, och returneras. wp rest api respekterar inställningar som görs i wp backend, så vi skickar inte med tex offset.

public function _get($method, array $query = array()) {

	try {
		// create cache key
		$cache_key = md5($this->endpoint . '/wp-json/' . $method . json_encode($query));

		// try to cet from the cache
		if( $this->_app['caches']['memcached']->fetch($cache_key) ) {

		$return = $this->_app['caches']['memcached']->fetch($cache_key);

		} else {

			// if cache is empty, get from endpoint and cache
			$response = $this->client->get($this->endpoint . '/wp-json/' . $method, ['query' => $query]);

			$return = [
				'results' => $response->json(),
				'total'   => $response->getHeader('X-WP-Total'),
				'pages'   => $response->getHeader('X-WP-TotalPages')
			];

			$this->_app['caches']['memcached']->store($cache_key, $return, 600); // 600 is 10 min caching

		}

	} catch (\GuzzleHttp\Exception\TransferException $e) {

		$error['message'] = $e->getMessage();

		if ($e->getResponse()) {
			$error['code'] = $e->getResponse()->getStatusCode();
		}

		$return = [
			'error'   => $error,
			'results' => [],
			'total'   => 0,
			'pages'   => 0
		];

	}

	return $return;

}

Via vår Controller, skickar vi svaret (som är json) vidare till Twig som renderar en sida. Principen är samma i de fall då vi vill rendera med javascript.

Med ganska små medel har vi frikopplat backend och frontend. Redaktörer kan jobba i wp med att skriva, lägga till media, taggar och kategorier. Frontend kan releasas helt separat, byggas av ett annat team och på annan teknik. Vi kan till exempel framöver bygga ios-appar på samma data som finns på våra webbar, och vi kan använda ett backend för att serva sajter på olika domäner.