问题描述

我正在尝试为 RESTful API 生成重写规则。我只想看看是否有更好的方法来做这个工作,而不是写出每一个可能的重写组合。

好,所以我有 4 个查询变量在 URL 中

  • 指示符

  • 国家

  • 响应

  • 调查

基本 URL 将为 www.example.com/some-page/4 个变量的顺序是一致的,但一些查询变量是可选的。

所以我可以有…

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

或…(no /response /)

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

要么…

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

有没有更好的方法来完成这个比过滤 rewrite_rules_array 和添加一个我的重写规则手动创建的数组? add_rewrite_endpoint() rewrite_endpoint 或 add_rewrite_tag()对我有用吗?

最佳解决方案

我认为最好的选择是端点。您可以将所有数据作为一个简单的字符串,因此您可以决定如何解析它们,您不必担心与其他重写规则的冲突。

有一件事我了解端点:保持主要工作尽可能抽象,以数据不可知的方式修复 WordPress 的 API 中的故障。

我将逻辑分为三部分:控制器选择模型和视图,处理端点的模型和一个或多个视图以返回一些有用的数据或错误消息。

控制器

我们从控制器开始吧。它没有太多,所以我在这里使用一个非常简单的功能:

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

基本上,它加载模型 T5_CRA_Model 并交出一些参数… 和所有的工作。控制器不了解模型或视图的内部逻辑。它只是粘在一起。这是唯一不可重用的部分; 这就是为什么我把它与其他部分分开。


现在我们至少需要两个类:注册 API 的模型和创建输出的视图。

该模型

这个课程会:

  • 注册端点

  • 捕获没有任何附加参数调用端点的情况

  • 填写由于第三方代码中的一些错误而丢失的重写规则

  • EP_ROOT 的静态前端和端点修复 WordPress 毛刺

  • 将 URI 解析成数组 (这也可以分开)

  • 使用这些值调用回调处理程序

我希望代码自己说。 🙂

该模型不了解数据的内部结构或演示文稿。所以你可以使用它来注册数百个 API 而不改变一行。

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

风景

现在我们要做一些我们的数据。我们还可以捕获不完整请求的丢失数据,或将处理委托给其他视图或 sub-controllers 。

这是一个非常简单的例子:

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: $valuen";
    }
    protected function render_html( $data ) {}
    protected function render_xml( $data ) {}
}

重要的部分是:视图不知道端点的任何内容。您可以使用它来处理完全不同的请求,例如 wp-admin 中的 AJAX 请求。您可以将视图分割为自己的 MVC 模式,或仅使用简单的功能。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。