Używanie Rewrite API do zbudowania RESTful URL

19

Próbuję wygenerować reguły przepisywania dla interfejsu API RESTful. Chcę tylko sprawdzić, czy jest lepszy sposób, aby to zadziałało niż zapisywanie każdej możliwej kombinacji przepisywania.

Ok, więc mam 4 zmienne zapytania do uwzględnienia w adresie URL

  • Wskaźnik
  • Kraj
  • Odpowiedź
  • Ankieta

Podstawowym adresem URL będzie www.example.com/some-page/ Kolejność 4 zmiennych będzie spójna, ale niektóre zmienne zapytania są opcjonalne.

Więc mógłbym ...

/indicator/{indicator value}/country/{country value}/response/{response value}/survey/{survey value}/

lub ... (nie / odpowiedź /)

/indicator/{indicator value}/country/{country value}/survey/{survey value}/

lub...

/indicator/{indicator value}/country/{country value}/

Czy istnieje lepszy sposób na osiągnięcie tego niż filtrowanie rewrite_rules_arrayi dodawanie tablicy moich reguł przepisywania utworzonych ręcznie? Czy add_rewrite_endpoint()przepisałbym_endpoint lub add_rewrite_tag()byłby dla mnie użyteczny?

kingkool68
źródło

Odpowiedzi:

18

Myślę, że najlepszą opcją jest punkt końcowy. Otrzymujesz wszystkie dane w postaci prostego ciągu, dzięki czemu możesz zdecydować, w jaki sposób zostaną one przeanalizowane, i nie musisz się martwić o kolizje z innymi regułami przepisywania.

Jednej rzeczy nauczyłem się o punktach końcowych: utrzymuj główną pracę tak abstrakcyjną, jak to możliwe, naprawiaj usterki w interfejsie API WordPress w sposób agnostyczny względem danych.

Podzieliłbym logikę na trzy części: kontroler wybiera model i widok, model do obsługi punktu końcowego i jeden lub więcej widoków, aby zwrócić przydatne informacje lub komunikaty o błędach.

Kontroler

Zacznijmy od kontrolera. Nie robi wiele, więc używam tutaj bardzo prostej funkcji:

add_action( 'plugins_loaded', 't5_cra_init' );

function t5_cra_init()
{
    require dirname( __FILE__ ) . '/class.T5_CRA_Model.php';

    $options = array (
        'callback' => array ( 'T5_CRA_View_Demo', '__construct' ),
        'name'     => 'api',
        'position' => EP_ROOT
    );
    new T5_CRA_Model( $options );
}

Zasadniczo ładuje model T5_CRA_Modeli przekazuje niektóre parametry… i całą pracę. Kontroler nie wie nic o wewnętrznej logice modelu ani widoku. Po prostu łączy oba elementy. To jedyna część, której nie można ponownie użyć; dlatego trzymałem to oddzielnie od innych części.


Teraz potrzebujemy co najmniej dwóch klas: modelu rejestrującego interfejs API i widoku do tworzenia danych wyjściowych.

Model

Ta klasa będzie:

  • zarejestruj punkt końcowy
  • wychwyć przypadki, w których wywołano punkt końcowy bez żadnych dodatkowych parametrów
  • uzupełnij przepisy przepisywania, których brakuje z powodu błędów w kodzie strony trzeciej
  • naprawić usterkę WordPress ze statycznymi stronami początkowymi i punktami końcowymi dla EP_ROOT
  • parsuj URI w tablicy (to też można oddzielić)
  • wywołaj moduł obsługi wywołania zwrotnego z tymi wartościami

Mam nadzieję, że kod mówi sam za siebie. :)

Model nie wie nic o wewnętrznej strukturze danych ani o prezentacji. Możesz więc użyć go do zarejestrowania setek interfejsów API bez zmiany jednej linii.

<?php  # -*- coding: utf-8 -*-
/**
 * Register new REST API as endpoint.
 *
 * @author toscho http://toscho.de
 *
 */
class T5_CRA_Model
{
    protected $options;

    /**
     * Read options and register endpoint actions and filters.
     *
     * @wp-hook plugins_loaded
     * @param   array $options
     */
    public function __construct( Array $options )
    {
        $default_options = array (
            'callback' => array ( 'T5_CRA_View_Demo', '__construct' ),
            'name'     => 'api',
            'position' => EP_ROOT
        );

        $this->options = wp_parse_args( $options, $default_options );

        add_action( 'init', array ( $this, 'register_api' ), 1000 );

        // endpoints work on the front end only
        if ( is_admin() )
            return;

        add_filter( 'request', array ( $this, 'set_query_var' ) );
        // Hook in late to allow other plugins to operate earlier.
        add_action( 'template_redirect', array ( $this, 'render' ), 100 );
    }

    /**
     * Add endpoint and deal with other code flushing our rules away.
     *
     * @wp-hook init
     * @return void
     */
    public function register_api()
    {
        add_rewrite_endpoint(
            $this->options['name'],
            $this->options['position']
        );
        $this->fix_failed_registration(
            $this->options['name'],
            $this->options['position']
        );
    }

    /**
     * Fix rules flushed by other peoples code.
     *
     * @wp-hook init
     * @param string $name
     * @param int    $position
     */
    protected function fix_failed_registration( $name, $position )
    {
        global $wp_rewrite;

        if ( empty ( $wp_rewrite->endpoints ) )
            return flush_rewrite_rules( FALSE );

        foreach ( $wp_rewrite->endpoints as $endpoint )
            if ( $endpoint[0] === $position && $endpoint[1] === $name )
                return;

        flush_rewrite_rules( FALSE );
    }

    /**
     * Set the endpoint variable to TRUE.
     *
     * If the endpoint was called without further parameters it does not
     * evaluate to TRUE otherwise.
     *
     * @wp-hook request
     * @param   array $vars
     * @return  array
     */
    public function set_query_var( Array $vars )
    {
        if ( ! empty ( $vars[ $this->options['name'] ] ) )
            return $vars;

        // When a static page was set as front page, the WordPress endpoint API
        // does some strange things. Let's fix that.
        if ( isset ( $vars[ $this->options['name'] ] )
            or ( isset ( $vars['pagename'] ) and $this->options['name'] === $vars['pagename'] )
            or ( isset ( $vars['page'] ) and $this->options['name'] === $vars['name'] )
            )
        {
            // In some cases WP misinterprets the request as a page request and
            // returns a 404.
            $vars['page'] = $vars['pagename'] = $vars['name'] = FALSE;
            $vars[ $this->options['name'] ] = TRUE;
        }
        return $vars;
    }

    /**
     * Prepare API requests and hand them over to the callback.
     *
     * @wp-hook template_redirect
     * @return  void
     */
    public function render()
    {
        $api = get_query_var( $this->options['name'] );
        $api = trim( $api, '/' );

        if ( '' === $api )
            return;

        $parts  = explode( '/', $api );
        $type   = array_shift( $parts );
        $values = $this->get_api_values( join( '/', $parts ) );
        $callback = $this->options['callback'];

        if ( is_string( $callback ) )
        {
            call_user_func( $callback, $type, $values );
        }
        elseif ( is_array( $callback ) )
        {
            if ( '__construct' === $callback[1] )
                new $callback[0]( $type, $values );
            elseif ( is_callable( $callback ) )
                call_user_func( $callback, $type, $values );
        }
        else
        {
            trigger_error(
                'Cannot call your callback: ' . var_export( $callback, TRUE ),
                E_USER_ERROR
            );
        }

        // Important. WordPress will render the main page if we leave this out.
        exit;
    }

    /**
     * Parse request URI into associative array.
     *
     * @wp-hook template_redirect
     * @param   string $request
     * @return  array
     */
    protected function get_api_values( $request )
    {
        $keys    = $values = array();
        $count   = 0;
        $request = trim( $request, '/' );
        $tok     = strtok( $request, '/' );

        while ( $tok !== FALSE )
        {
            0 === $count++ % 2 ? $keys[] = $tok : $values[] = $tok;
            $tok = strtok( '/' );
        }

        // fix odd requests
        if ( count( $keys ) !== count( $values ) )
            $values[] = '';

        return array_combine( $keys, $values );
    }
}

Widok

Teraz musimy coś zrobić z naszymi danymi. Możemy również wychwycić brakujące dane w przypadku niekompletnych żądań lub przekazać obsługę innym widokom lub kontrolerom podrzędnym.

Oto bardzo prosty przykład:

class T5_CRA_View_Demo
{
    protected $allowed_types = array (
            'plain',
            'html',
            'xml'
    );

    protected $default_values = array (
        'country' => 'Norway',
        'date'    => 1700,
        'max'     => 200
    );
    public function __construct( $type, $data )
    {
        if ( ! in_array( $type, $this->allowed_types ) )
            die( 'Your request is invalid. Please read our fantastic manual.' );

        $data = wp_parse_args( $data, $this->default_values );

        header( "Content-Type: text/$type;charset=utf-8" );
        $method = "render_$type";
        $this->$method( $data );
    }

    protected function render_plain( $data )
    {
        foreach ( $data as $key => $value )
            print "$key: $value\n";
    }
    protected function render_html( $data ) {}
    protected function render_xml( $data ) {}
}

Ważną częścią jest: widok nie wie nic o punkcie końcowym. Możesz go używać do obsługi całkowicie różnych żądań, na przykład żądań AJAX w wp-admin. Możesz podzielić widok na własny wzorzec MVC lub użyć prostej funkcji.

fuxia
źródło
2
Cyfra. Lubię ten rodzaj wzoru.
kingkool68