Usando a API de reconfiguração para construir uma URL RESTful

19

Estou tentando gerar regras de reescrita para uma API RESTful. Eu só quero ver se existe uma maneira melhor de fazer isso funcionar do que ter que escrever todas as combinações possíveis de reescrita.

Ok, então eu tenho 4 variáveis ​​de consulta para contabilizar no URL

  • Indicador
  • País
  • Resposta
  • Pesquisa

O URL base será www.example.com/some-page/ A ordem das 4 variáveis ​​será consistente, mas algumas variáveis ​​de consulta são opcionais.

Então eu poderia ter ...

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

ou ... (não / resposta /)

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

ou...

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

Existe uma maneira melhor de fazer isso do que filtrar rewrite_rules_arraye adicionar uma matriz de minhas regras de reescrita criadas manualmente? Seria add_rewrite_endpoint()rewrite_endpoint ou add_rewrite_tag()ser qualquer utilidade para mim?

kingkool68
fonte

Respostas:

18

Eu acho que a melhor opção é um ponto final. Você obtém todos os dados como uma sequência simples, para poder decidir como serão analisados ​​e não precisa se preocupar com colisões com outras regras de reescrita.

Uma coisa que aprendi sobre os pontos de extremidade: mantenha o trabalho principal o mais abstrato possível, corrija as falhas na API do WordPress de uma maneira independente de dados.

Eu separaria a lógica em três partes: um controlador seleciona um modelo e uma vista, um modelo para lidar com o terminal e uma ou mais vistas para retornar alguns dados úteis ou mensagens de erro.

O controlador

Vamos começar com o controlador. Não faz muito, então eu uso uma função muito simples aqui:

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

Basicamente, ele carrega o modelo T5_CRA_Modele entrega alguns parâmetros ... e todo o trabalho. O controlador não sabe nada sobre a lógica interna do modelo ou da vista. Apenas junta os dois juntos. Esta é a única parte que você não pode reutilizar; é por isso que eu o mantive separado das outras partes.


Agora precisamos de pelo menos duas classes: o modelo que registra a API e a visualização para criar saída.

O modelo

Esta classe irá:

  • registrar o terminal
  • capturar casos em que o terminal foi chamado sem nenhum parâmetro adicional
  • preencha regras de reescrita ausentes devido a alguns erros no código de terceiros
  • corrija uma falha do WordPress com páginas iniciais e pontos finais estáticos para EP_ROOT
  • analisar o URI em uma matriz (isso também pode ser separado)
  • chame o manipulador de retorno de chamada com esses valores

Espero que o código fale por si. :)

O modelo não sabe nada sobre a estrutura interna dos dados ou sobre a apresentação. Portanto, você pode usá-lo para registrar centenas de APIs sem alterar uma linha.

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

A vista

Agora temos que fazer algo com nossos dados. Também podemos capturar dados ausentes para solicitações incompletas ou delegar o tratamento a outras visualizações ou subcontroladores.

Aqui está um exemplo muito simples:

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

A parte importante é: a visualização não sabe nada sobre o terminal. Você pode usá-lo para lidar com solicitações completamente diferentes, por exemplo, solicitações AJAX no wp-admin. Você pode dividir a visualização em seu próprio padrão MVC ou usar apenas uma função simples.

fuxia
fonte
2
Cave. Eu gosto desse tipo de padrão.
kingkool68