Utilizzo dell'API di riscrittura per costruire un URL RESTful


19

Sto cercando di generare regole di riscrittura per un'API RESTful. Voglio solo vedere se esiste un modo migliore per farlo funzionare piuttosto che dover scrivere ogni possibile combinazione di riscrittura.

Ok, quindi ho 4 variabili di query da tenere in considerazione nell'URL

  • Indicatore
  • Nazione
  • Risposta
  • Sondaggio

L'URL di base sarà www.example.com/some-page/ L'ordine delle 4 variabili sarà coerente ma alcune variabili di query sono facoltative.

Quindi avrei potuto ...

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

oppure ... (no / response /)

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

o...

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

Esiste un modo migliore per ottenere questo risultato che filtrare rewrite_rules_arraye aggiungere una matrice delle mie regole di riscrittura create manualmente? Sarebbe add_rewrite_endpoint()rewrite_endpoint o add_rewrite_tag()essere qualsiasi utilità per me?

Risposte:


18

Penso che l'opzione migliore sia un endpoint. Ottieni tutti i dati come una semplice stringa, quindi puoi decidere come verranno analizzati e non devi preoccuparti delle collisioni con altre regole di riscrittura.

Una cosa che ho imparato sugli endpoint: mantenere il lavoro principale il più astratto possibile, correggere i problemi nell'API di WordPress in modo agnostico sui dati.

Vorrei separare la logica in tre parti: un controller seleziona un modello e una vista, un modello per gestire l'endpoint e una o più viste per restituire alcuni dati utili o messaggi di errore.

Il controller

Cominciamo con il controller. Non fa molto, quindi uso una funzione molto semplice qui:

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

Fondamentalmente, carica il modello T5_CRA_Modele consegna alcuni parametri ... e tutto il lavoro. Il controller non sa nulla della logica interna del modello o della vista. Attacca entrambi insieme. Questa è l'unica parte che non puoi riutilizzare; ecco perché l'ho tenuto separato dalle altre parti.


Ora abbiamo bisogno di almeno due classi: il modello che registra l'API e la vista per creare l'output.

Il modello

Questa classe dovrà:

  • registra l'endpoint
  • individuare i casi in cui è stato chiamato l'endpoint senza parametri aggiuntivi
  • riempire le regole di riscrittura che mancano a causa di alcuni bug nel codice di terze parti
  • correggere un glitch di WordPress con prime pagine statiche ed endpoint per EP_ROOT
  • analizzare l'URI in un array (anche questo potrebbe essere separato)
  • chiama il gestore di callback con quei valori

Spero che il codice parli da solo. :)

Il modello non sa nulla della struttura interna dei dati o della presentazione. Quindi puoi usarlo per registrare centinaia di API senza cambiare una riga.

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

La vista

Ora dobbiamo fare qualcosa con i nostri dati. Possiamo anche acquisire dati mancanti per richieste incomplete o delegare il trattamento ad altre viste o sub-controller.

Ecco un esempio molto semplice:

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

La parte importante è: la vista non sa nulla dell'endpoint. Puoi usarlo per gestire richieste completamente diverse, ad esempio richieste AJAX in wp-admin. È possibile dividere la vista nel proprio modello MVC o utilizzare solo una semplice funzione.


2
Scavalo. Mi piace questo tipo di motivo.
kingkool68,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.