Автодокументация мобильных веб-сервисов на примере Yii

    Думаю, что многие, особенно небольшие, компании, при работе с одним и тем же фреймоворком постоянно пишут какие-то вещи/расширения и т.п., которые решают именно те задачи, с которыми они сталкиваются наиболее часто.

    В нашем случае этим фреймворком является Yii, а одной из самых популярных проблем была одновременная разработка web-сервиса для приложений iOS/Android.

    Сначала, как и всегда, просто разработчики договаривались между собой что и как, но если разработчик вдруг менялся — начинались проблемы. Далее — описание входных/выходных данных в wiki. При большом количестве мелких изменений возникала проблема синхронизации кода и форматов, описанных в wiki.

    Как мы решили проблему — ниже.

    Разработка контроллера web-сервиса

    Как правило, мобильный сервис — это отдельный модуль проекта. Сделали базовый модуль и базовый контроллер для работы с web-сервисом.

    Ключевые вещи — ниже.
    class VMJsonServiceController extends CController
    {
       public $documentationMode = false;
       protected $request = null;
    
       ...
       public function init()
        {
            if ($this->documentationMode) {
                return;
            }
    
            if (Yii::app()->request->isPostRequest) {
                $json = CJSON::decode(file_get_contents("php://input"), false);
                if (isset($json->request)) {
                    $this->request = $json->request;
                } else {
                    $this->respondWithError(VMServiceResponseCode::SERVICE_ERROR, 'There is no request node');
                }
            } else {
               ...
            }
        }
       ...
        public function checkInputParameters($params = array())
        {
            if ($this->documentationMode) {
                $exception = new VMDocumentationException();
                $exception->parameters = VMObjectUtils::fromArray($params);
                throw $exception;
            }
            $this->checkObjectParameter($params, $this->request);
        } 
       ...
    }
    


    Самая «соль» заключается именно в параметре $documentationMode, но об этом позже.
    Теперь рассмотрим пример контроллера уже с реального проекта, а не из общей библиотеки.
    class UsersController extends VMJsonServiceController
    {
       ...
       public function actionSignUp()
    	{
    		$this->checkInputParameters(array(
    			'user' => array(
    				'email' => 'test@test.com',
    				'password' => '12345',
    				'phone' => array('optional', 'value' => '+7 999 998 76 54'),
    				'photos' => array('array', 'value' => array(
    					'file' => base64_encode('Image'),
    				)
    			)
    		));
    		$email = $this->request->user->email;
    		...
           }
    	...
    }
    

    Метод checkInputParameters проверяет, что данные пришли в нужном для этого метода формате (email и password — обязательные, phone — необязательный, а photos — массив данных. Далее мы уверены, что все необходимые данные у нас есть и с ними можно работать.

    Теперь, собственно, о чем шла речь в начале статьи — о документации. В принципе, массив в методе checkInputPаrameters — и есть формат входных данных, и, собственно, на основе него мы и генерируем документацию, но постоянно генерировать её ручками — неинтересно и долго. Поэтому, заставим модуль генерировать документацию о себе самому.

    1. Делаем еще один контроллер
    При помощи расширения Metadata он обходит все контроллеры и Action'ы, а дальше рендерит наглядное представление параметров метода и ответы сервиса. Сделано это при помощи упоминавшегося метода checkInputParameters
    class VMDocumentationController extends CController
    {
       ...
       //Полный код можете посмотреть в репозитории, здесь покажу лишь один метод
       //На вход он принимает объект "контроллер" и название метода
       private function getDefinition($class, $method)
    	{
    		try {
    			$class->{$method}();
    		} catch (VMDocumentationException $e) {
    			return (object) array(
    				'parameters' => $e->parameters
    			);
    		}
    	}
       ...
    }
    


    2. Правим код базового модуля
    class VMJsonServiceModule extends CWebModule {
    	public function init() {
                    ...
    		$this->controllerMap = array(
    			'documentation' => array(
    				'class' => 'VMDocumentationController'
    			)
    		);
    	}
    } 
    


    Что нам это дает? А то, что по одному и тому же url программист iOS/Android имеет документацию для любого проекта, меняется только baseUrl.
    Как выглядит документация?
    Пара скриншотов ниже.

    В верхнем навбаре — список всех контроллеров модулей, в левом — список всех экшнов контроллера. Необязательные параметры можно удалить из запроса, кнопка Call — отправит запрос и покажет ответ сервера. То есть вы можете не писать ни строчки кода, но протестировать работу веб-сервиса на любых входных данных.





    Ну и напоследок — все исходные коды можете найти здесь. Местами код ужасен, местами очень ужасен, многое из того, что есть в нашей библиотеке кроме мобильного сервиса — нафиг никому не надо, но если будет что вдруг предложить — pull-request вам в помощь.

    Спасибо за внимание
    Метки:
    Поделиться публикацией
    Комментарии 3
    • 0
      swagger, не?
      • 0
        Честно говоря, о данном решении узнали совсем недавно, буквально пару недель назад, но нам не особо понравилось, во всяком случае их демо. То есть, например, я так и не разобрался как можно именно послать разные параметры запроса. Да и к дизайну Boostrrap'овскому уже привыкли
      • 0
        Swagger генеринует json по которому можно потом сделать свой swagger-ui c блекджеком и бутстрапом.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.