Utilisation de l'API de réécriture pour construire une URL RESTful


19

J'essaie de générer des règles de réécriture pour une API RESTful. Je veux juste voir s'il y a une meilleure façon de faire ce travail que d'avoir à écrire toutes les combinaisons de réécriture possibles.

Ok donc j'ai 4 variables de requête à prendre en compte dans l'URL

  • Indicateur
  • Pays
  • Réponse
  • Enquête

L'URL de base sera www.example.com/some-page/ L'ordre des 4 variables sera cohérent mais certaines variables de requête sont facultatives.

Donc j'aurais pu ...

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

ou ... (non / réponse /)

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

ou...

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

Existe-t-il une meilleure façon d'accomplir cela que de filtrer rewrite_rules_arrayet d'ajouter un tableau de mes règles de réécriture créées manuellement? Est-ce que add_rewrite_endpoint()rewrite_endpoint ou me serait utile add_rewrite_tag()?

Réponses:


18

Je pense que la meilleure option est un point final. Vous obtenez toutes les données sous la forme d'une chaîne simple, vous pouvez donc décider comment elles seront analysées, et vous n'avez pas à vous soucier des collisions avec d'autres règles de réécriture.

Une chose que j'ai apprise sur les points de terminaison: garder le travail principal aussi abstrait que possible, corriger les problèmes dans l'API WordPress de manière indépendante des données.

Je séparerais la logique en trois parties: un contrôleur sélectionne un modèle et une vue, un modèle pour gérer le point final et une ou plusieurs vues pour renvoyer des données utiles ou des messages d'erreur.

Le controlle

Commençons par le contrôleur. Cela ne fait pas grand-chose, j'utilise donc une fonction très simple ici:

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

Fondamentalement, il charge le modèle T5_CRA_Modelet remet certains paramètres… et tout le travail. Le contrôleur ne sait rien de la logique interne du modèle ou de la vue. Il colle simplement les deux ensemble. C'est la seule partie que vous ne pouvez pas réutiliser; c'est pourquoi je l'ai gardé séparé des autres parties.


Maintenant, nous avons besoin d'au moins deux classes: le modèle qui enregistre l'API et la vue pour créer une sortie.

Le modèle

Cette classe:

  • enregistrer le point de terminaison
  • cas de capture où le point final a été appelé sans aucun paramètre supplémentaire
  • remplir les règles de réécriture manquantes à cause de quelques bugs dans le code tiers
  • corriger un problème WordPress avec des pages de garde statiques et des points de terminaison pour EP_ROOT
  • analyser l'URI dans un tableau (cela pourrait également être séparé)
  • appeler le gestionnaire de rappel avec ces valeurs

J'espère que le code parle de lui-même. :)

Le modèle ne sait rien de la structure interne des données ni de la présentation. Vous pouvez donc l'utiliser pour enregistrer des centaines d'API sans changer une ligne.

<?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 vue

Maintenant, nous devons faire quelque chose avec nos données. Nous pouvons également récupérer les données manquantes pour les demandes incomplètes ou déléguer la gestion à d'autres vues ou sous-contrôleurs.

Voici un exemple très simple:

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 partie importante est: la vue ne sait rien du point final. Vous pouvez l'utiliser pour gérer des demandes complètement différentes, par exemple des demandes AJAX dans wp-admin. Vous pouvez diviser la vue en son propre modèle MVC ou utiliser simplement une fonction simple.


2
Creusez-le. J'aime ce type de motif.
kingkool68
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.