wake-up-neo.net

Verwenden der Rewrite-API zum Erstellen einer RESTful-URL

Ich versuche, Umschreibregeln für eine RESTful-API zu generieren. Ich möchte nur herausfinden, ob es einen besseren Weg gibt, dies zum Laufen zu bringen, als jede mögliche Umschreibekombination aufschreiben zu müssen.

Ok, also habe ich 4 Abfragevariablen in der URL zu berücksichtigen

  • Indikator
  • Land
  • Antwort
  • Umfrage

Die Basis-URL lautet www.example.com/some-page/ Die Reihenfolge der 4 Variablen ist konsistent, einige Abfragevariablen sind jedoch optional.

Also hätte ich ...

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

oder ... (nein/antwort /)

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

oder...

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

Gibt es eine bessere Möglichkeit, dies zu erreichen, als den rewrite_rules_array zu filtern und ein Array meiner manuell erstellten Umschreiberegeln hinzuzufügen? Würde add_rewrite_endpoint() rewrite_endpoint oder add_rewrite_tag() für mich von Nutzen sein?

19
kingkool68

Ich denke, die beste Option ist ein Endpunkt. Sie erhalten alle Daten als einfache Zeichenfolge, sodass Sie entscheiden können, wie sie analysiert werden sollen, und müssen sich keine Gedanken über Kollisionen mit anderen Umschreiberegeln machen.

Eine Sache, die ich über Endpunkte gelernt habe: Halte die Hauptarbeit so abstrakt wie möglich, behebe die Pannen in der WordPress-API auf datenunabhängige Weise.

Ich würde die Logik in drei Teile aufteilen: einen Controller ein Modell und eine Ansicht auswählen, einen Modell um den Endpunkt zu handhaben und einen oder mehrere Ansichten um einige nützliche Daten oder Fehlermeldungen zurückzugeben.

Der Controller

Beginnen wir mit der Steuerung. Es macht nicht viel, deshalb benutze ich hier eine sehr einfache Funktion:

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 );
}

Grundsätzlich lädt es das Modell T5_CRA_Model und übergibt einige Parameter ... und die ganze Arbeit. Der Controller weiß nichts über die innere Logik des Modells oder der Ansicht. Es hält einfach beide zusammen. Dies ist der einzige Teil, den Sie nicht wiederverwenden können. Deshalb habe ich es von den anderen Teilen getrennt.


Jetzt brauchen wir mindestens zwei Klassen: das model , das die API registriert, und das view , um eine Ausgabe zu erstellen.

Das Model

Diese Klasse wird:

  • registrieren Sie den Endpunkt
  • fälle abfangen, in denen der Endpunkt ohne zusätzliche Parameter aufgerufen wurde
  • füllen Sie Umschreiberegeln aus, die aufgrund von Fehlern im Code von Drittanbietern fehlen
  • behebung einer WordPress-Panne mit statischen Titelseiten und Endpunkten für EP_ROOT
  • parsen Sie den URI in ein Array (dies könnte auch getrennt werden)
  • rufen Sie den Callback-Handler mit diesen Werten auf

Ich hoffe der Code spricht für sich. :)

Das Modell weiß nichts über die innere Struktur der Daten oder über die Darstellung. Damit können Sie Hunderte von APIs registrieren, ohne eine Zeile zu ändern.

<?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 );
    }
}

Die Aussicht

Jetzt müssen wir etwas mit unseren Daten machen. Wir können auch fehlende Daten für unvollständige Anforderungen abfangen oder die Verarbeitung an andere Ansichten oder Sub-Controller delegieren.

Hier ist ein sehr einfaches Beispiel:

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 ) {}
}

Der wichtige Teil ist: Die Ansicht weiß nichts über den Endpunkt. Sie können es verwenden, um ganz andere Anforderungen zu verarbeiten, z. B. AJAX Anforderungen in wp-admin. Sie können die Ansicht in ein eigenes MVC-Muster aufteilen oder nur eine einfache Funktion verwenden.

18
fuxia