За последние 24 часа нас посетили 9082 программиста и 451 робот. Сейчас ищут 238 программистов ...

Конвертировать любой отчёт в PDF (или CSV)

Тема в разделе "Решения, алгоритмы", создана пользователем artoodetoo, 25 сен 2019.

Метки:
  1. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.398
    Симпатии:
    1.046
    Адрес:
    там-сям
    Проблема:
    Есть большое количество API маршрутов, возвращающих данные в JSON. Эти данные, как правило, представимы в табличном виде. То есть там массив записей. Для части маршрутов создали двойников чтобы вместо JSON выгружался pdf файл. Типа был `/api/reports/yield` добавили `/api/reports/yield/pdf. И не могли не заметить, что получается много копипасты. Поэтому...

    Хотелка:
    Добавить что-то в URL как признак "надо в PDF" и автоматически получить этот самый вариант в PDF — для любого GET-запроса к API. В перспективе наверное захочется и другие "рендереры" прикрутить по аналогии с PDF.
    Да, можно не через URL, а через заголовок запроса типа `Accept` рулить типом, но думаю это не принципиално для задачи.

    В принципе у меня уже есть решение, даже два, но хочется обсудить, может я не вижу чего-то очевидного. Может уже были обсуждения.
     
  2. runcore

    runcore Старожил

    С нами с:
    12 окт 2012
    Сообщения:
    3.625
    Симпатии:
    158
    /api/reports/yield.pdf
    ?
     
  3. Roman __construct

    [ БАН ]

    С нами с:
    27 апр 2019
    Сообщения:
    1.273
    Симпатии:
    113
    Не видя кода сложно понять откуда именно там "много копипасты"

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

    Параметры запроса я оставил бы в роутах.

    В общем, не совсем понятно в чем именно вопрос.
     
  4. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    7.847
    Симпатии:
    1.543
    @artoodetoo, может в middleware вынести преобразование в PDF? И от маршрута выбирать класс-преобразователь, ну т.е. "стратегия" получится.
     
    #4 mkramer, 26 сен 2019
    Последнее редактирование: 26 сен 2019
    Roman __construct нравится это.
  5. Roman __construct

    [ БАН ]

    С нами с:
    27 апр 2019
    Сообщения:
    1.273
    Симпатии:
    113
    mkramer, а что за "стратегия"? Где почитать?
     
  6. romach

    romach Старожил

    С нами с:
    26 окт 2013
    Сообщения:
    2.904
    Симпатии:
    717
    попробуй сделать after middleware, в ней разобрать request/response и подменить ответ по необходимости.
     
    artoodetoo и Roman __construct нравится это.
  7. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    7.847
    Симпатии:
    1.543
    artoodetoo и Roman __construct нравится это.
  8. Roman __construct

    [ БАН ]

    С нами с:
    27 апр 2019
    Сообщения:
    1.273
    Симпатии:
    113
    вот дико плюсую)) тоже хотел написать)
     
  9. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.398
    Симпатии:
    1.046
    Адрес:
    там-сям
    Всем спасибо, укрепили мою уверенность в выбранных методах.

    Расказываю про свои два решения. Вначале я подумал что "универсальный роут" подойдёт идеально. Вот так это выглядит:

    Решение 1:
    маршрут
    PHP:
    1. Route::middleware(['auth:api'])->group(function () {
    2.     // Route::
    3.     // Route::
    4.     // Route::
    5.  
    6.     // важно, что это стоит в конце!
    7.     Route::get('{uri}/pdf', 'DefaultController@getPdfVersion')->where('uri', '^.*');
    8. });
    и контроллер. Фишка в том, что я заново формирую объект request уже с моим URI, обращаюсь к ядру и прошу его вернуть response — как это происходит при обычной обработке http запроса.

    PHP:
    1. class DefaultController extends Controller
    2. {
    3.     public function getPdfVersion(string $uri)
    4.     {
    5.         /** @var \Illuminate\Http\Request $newRequest */
    6.         $newRequest = Request::create("api/{$uri}", 'GET', $_GET, $_COOKIE, $_FILES, $_SERVER, null);
    7.         /** @var \Illuminate\Http\Response $response */
    8.         $response = $GLOBALS['kernel']->handle($newRequest);
    9.  
    10.         if ($response->getStatusCode() == 200 && $response->headers->get('Content-Type') === 'application/json') {
    11.  
    12.               // ... пропускаю магию, специфичную для конкретного проекта ...
    13.               // беру json_decode($response->getContent(), true) , прогоняю через шаблон, сохраняю в файл pdf ...
    14.  
    15.               return response()->download(
    16.                 storage_path("app/{$fileName}"),
    17.                 $fileName,
    18.                 ['Content-Type' => 'application/pdf']
    19.             )
    20.                 ->deleteFileAfterSend();
    21.         }
    22.  
    23.         return $response;
    24.     }
    25. }

    --- Добавлено ---

    Но потом я решил переписать на middleware.

    Решение 2.
    Да, это after-процессинг. Сначала выполняется весь стек мидваров, маршрут и контроллер, затем на обратном проходе я получаю доступ к объекту response и заменяю его.
    Убрал маршрут и контроллер, теперь признаком является ключ ?render=pdf, что кагбэ подразумевает и другие варианты представления в перспективе.

    PHP:
    1. class ConvertContentMiddleware
    2. {
    3.     /**
    4.      * Handle an incoming request.
    5.      *
    6.      * @param  \Illuminate\Http\Request  $request
    7.      * @param  \Closure  $next
    8.      * @return mixed
    9.      */
    10.     public function handle($request, Closure $next)
    11.     {
    12.         $response = $next($request);
    13.  
    14.         if ($request->has('render')) {
    15.  
    16.             switch ($request->render) {
    17.                 case 'pdf':
    18.                     // ... пропускаю магию, специфичную для конкретного проекта ...
    19.                     // беру json_decode($response->getContent(), true) , прогоняю через шаблон, сохраняю в файл pdf ...
    20.                     break;
    21.  
    22.                 default:
    23.                     throw new \Exception('Unknown renderer ' . $request->render);
    24.             }
    25.  
    26.             return response()->download(
    27.                 storage_path("app/{$fileName}"),
    28.                 $fileName,
    29.                 ['Content-Type' => 'application/pdf']
    30.             )
    31.                 ->deleteFileAfterSend();
    32.         }
    33.  
    34.         return $response;
    35.     }
    36. }
     
    Roman __construct и romach нравится это.