Uso de la API de reescritura para construir una URL RESTful

19

Estoy tratando de generar reglas de reescritura para una API RESTful. Solo quiero ver si hay una mejor manera de hacer que esto funcione que tener que escribir todas las combinaciones posibles de reescritura.

Ok, tengo 4 variables de consulta para tener en cuenta en la URL

  • Indicador
  • País
  • Respuesta
  • Encuesta

La URL base será www.example.com/some-page/ El orden de las 4 variables será coherente, pero algunas variables de consulta son opcionales.

Entonces podría haber ...

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

o ... (no / respuesta /)

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

o...

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

¿Hay una mejor manera de lograr esto que filtrar rewrite_rules_arrayy agregar una matriz de mis reglas de reescritura creadas manualmente? ¿ add_rewrite_endpoint()Rewrite_endpoint o add_rewrite_tag()sería de alguna utilidad para mí?

kingkool68
fuente

Respuestas:

18

Creo que la mejor opción es un punto final. Obtiene todos los datos como una cadena simple, por lo que puede decidir cómo se analizará y no tiene que preocuparse por las colisiones con otras reglas de reescritura.

Una cosa que aprendí sobre los puntos finales: mantener el trabajo principal lo más abstracto posible, corregir los problemas técnicos en la API de WordPress de una manera independiente de los datos.

Separaría la lógica en tres partes: un controlador selecciona un modelo y una vista, un modelo para manejar el punto final y una o más vistas para devolver algunos datos útiles o mensajes de error.

El controlador

Comencemos con el controlador. No hace mucho, así que uso una función muy simple aquí:

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

Básicamente, carga el modelo T5_CRA_Modely entrega algunos parámetros ... y todo el trabajo. El controlador no sabe nada sobre la lógica interna del modelo o la vista. Simplemente los une a ambos. Esta es la única parte que no puede reutilizar; por eso lo mantuve separado de las otras partes.


Ahora necesitamos al menos dos clases: el modelo que registra la API y la vista para crear resultados.

El modelo

Esta clase:

  • registrar el punto final
  • detectar casos en los que se llamó al punto final sin ningún parámetro adicional
  • completar las reglas de reescritura que faltan debido a algunos errores en el código de terceros
  • arregle un problema técnico de WordPress con portadas y puntos finales estáticos para EP_ROOT
  • analizar el URI en una matriz (esto también podría separarse)
  • llame al controlador de devolución de llamada con esos valores

Espero que el código hable por sí mismo. :)

El modelo no sabe nada sobre la estructura interna de los datos o sobre la presentación. Por lo tanto, puede usarlo para registrar cientos de API sin cambiar una línea.

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

Ahora tenemos que hacer algo con nuestros datos. También podemos capturar datos faltantes para solicitudes incompletas o delegar el manejo a otras vistas o subcontroladores.

Aquí hay un ejemplo muy 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 parte importante es: la vista no sabe nada sobre el punto final. Puede usarlo para manejar solicitudes completamente diferentes, por ejemplo, solicitudes AJAX en wp-admin. Puede dividir la vista en su propio patrón MVC o usar solo una función simple.

fuxia
fuente
2
Dígito. Me gusta este tipo de patrón.
kingkool68