Zend Rest Route - как создать иерархические URL?

Я занимаюсь разработкой API с использованием Zend Framework 1.12.3. Я использую Zend_Rest_Route, но я хотел бы иметь иерархические URL:

Я подумываю об использовании этого подхода, поскольку мне нужно было бы назначать определенные предметы определенным профессорам, и я верю, что эта схема решает ее аккуратно.

Тем не менее, мне трудно получить иерархические URL-адреса. Я уже попробовал:

  1. Zend_Controller_Router_Route с цепочками, в файле config.ini, но так как необходимо указать и контроллер, и действие, при доступе к http://api.example.com/professors/:professorId/subjects всегда указывалось на одно и то же действие (т. е. каким бы ни был метод вызова - POST, PUT, GET, DELETE - он всегда указывает на действие, указанное в файле config.ini). Например, если бы я указал getAction в файле конфигурации, используя цепочки, он всегда вызывал бы getAction, независимо от того, какой метод я использовал. В настоящее время при вызове POST он фактически вызывает функцию postAction() (аналогично происходит для PUT, GET, DELETE, PATCH, HEAD и OPTIONS). Мой файл контроллера выглядит так:

    class V1_ProfessorsController extends REST_Controller
    {
            public function optionsAction()
            {
                    // code goes here
            }
    
            public function headAction()
            {
                    // code goes here
            }
    
            public function indexAction()
            {
                    // code goes here - list of resources
            }
    
            public function getAction()
            {
                    // code goes here
            }
    
            public function postAction()
            {
                    // code goes here
            }
    
            public function putAction()
            {
                    // code goes here
            }
    
            public function patchAction()
            {
                    // code goes here
            }
    
            public function deleteAction()
            {
                    // code goes here
            }
    
    }
    
  2. Создание подкласса Zend_Rest_Route и переопределение функции match(), как указано здесь. Дело в том, что хотя это работает при звонке http://api.example.com/professors/:professorId/subjects, он по-прежнему использует тот же ProfessorsController, который используется при вызове http://api.example.com/professors, Я не уверен в этом, но я считаю, что было бы лучше иметь свой собственный контроллер (например, ProfessorsSubjectsController).

Также у меня есть вопрос. Как должны работать иерархические маршруты? Было бы лучше иметь разные контроллеры для разных ресурсов / подресурсов? Например, наличие ProfessorsController для http://api.example.com/professors/:professorId и ProfessorsSubjectsController для http://api.example.com/professors/:professorId/subjects/:subjectId?

1 ответ

Решение

Я нашел решение где-то, что я немного изменил. Это пользовательский маршрутный класс, который делает то, что, я думаю, мы оба хотим, чтобы он делал.

<?php 

require_once "modules.inc";

class Rest_Controller_Route extends Zend_Controller_Router_Route
{

/**
 * @var Zend_Controller_Front
 */
protected $_front;

protected $_actionKey     = 'action';

/**
 * Prepares the route for mapping by splitting (exploding) it
 * to a corresponding atomic parts. These parts are assigned
 * a position which is later used for matching and preparing values.
 *
 * @param Zend_Controller_Front $front Front Controller object
 * @param string $route Map used to match with later submitted URL path
 * @param array $defaults Defaults for map variables with keys as variable names
 * @param array $reqs Regular expression requirements for variables (keys as variable names)
 * @param Zend_Translate $translator Translator to use for this instance
 */
public function __construct(Zend_Controller_Front $front, $route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null)
{
    $this->_front      = $front;
    $this->_dispatcher = $front->getDispatcher();

    parent::__construct($route, $defaults, $reqs, $translator, $locale);
}



/**
 * Matches a user submitted path with parts defined by a map. Assigns and
 * returns an array of variables on a successful match.
 *
 * @param string $path Path used to match against this routing map
 * @return array|false An array of assigned values or a false on a mismatch
 */
public function match($path, $partial = false)
{

    $return = parent::match($path, $partial);

    // add the RESTful action mapping
    if ($return) {
        $request = $this->_front->getRequest();
        $path   = $request->getPathInfo();
        $params = $request->getParams();

        $path   = trim($path, '/');

        if ($path != '') {
            $path = explode('/', $path);
        }

        $lastParam = array_pop($path);

        // Determine Action
        $requestMethod = strtolower($request->getMethod());
        if ($requestMethod == 'head') {
            if (is_numeric($lastParam)) {
                $return[$this->_actionKey] = 'head';
                $return["id"] = $lastParam;
            }
        } else if ($requestMethod != 'get') {
            if ($request->getParam('_method')) {
                $return[$this->_actionKey] = strtolower($request->getParam('_method'));
            } elseif ( $request->getHeader('X-HTTP-Method-Override') ) {
                $return[$this->_actionKey] = strtolower($request->getHeader('X-HTTP-Method-Override'));
            } else {
                $return[$this->_actionKey] = $requestMethod;
            }

            // Map PUT, DELETE and POST to actual create/update/delete actions
            // based on parameter count (posting to resource or collection)
            switch( $return[$this->_actionKey] ){
                case 'post':
                    $return[$this->_actionKey] = 'post';
                    break;
                case 'put':
                    $return[$this->_actionKey] = 'put';
                    $return["id"] = $lastParam;
                    break;
                case 'delete':
                    $return[$this->_actionKey] = 'delete';
                    $return["id"] = $lastParam;
                    break;
            }
        } else {
            // if the last argument in the path is a numeric value, consider this request a GET of an item
            if (is_numeric($lastParam)) {
                $return[$this->_actionKey] = 'get';
                $return["id"] = $lastParam;
            } else {
                if (isset($data[0]) && is_numeric($data[0])) {
                    $return[$this->_actionKey] = 'get';
                    $return["id"] = $lastParam;
                } else {
                    $return[$this->_actionKey] = 'index';
                }
            }
        }
    }

    return $return;

}

}

Чтобы использовать это, создайте все свои маршруты, как это, в вашем bootstrap или index.php, два примера:

$route = new Rest_Controller_Route($front, 'customers/*', array('controller' => 'customers'));
$router->addRoute('customers', $route);

$route = new Rest_Controller_Route($front, 'customers/:customer_id/documents/*', array('controller' => 'customers-documents'));
$router->addRoute('customersdocuments', $route);

Это работает как очарование для меня. Ты считаешь, что это не мое окончательное решение, поэтому могут быть драконы, которых я не обнаружил, так что будь в курсе.:)

Другие вопросы по тегам