Челендж на 2026

Александр Воробьев
На сайте с 03.02.2020
Offline
59
#261
ArbNet #:

А это как-то так, будет движком интерпретатором обработчиком %команда% будет?

$response-> по моему более лучший вариант для тебя

Это варианты для разных уровней.

Нужен и оправдан на проекте шаблонизатор. Значит используем шаблонизатор и шаблоны, соответственно html  с тегами шаблонизатора.  Шаблонизатор сам взаимодействует с билдером.

Если шаблонизатор на проекте избыточен, но нужно управление статическими файлами  -используем только билдер.

Если и билдер избыточен можем совсем по простому. Вот варианты обработчика маршурута (специально, для демонстрации обработчики в виде колбеков (конечно по хорошему они в виде классов реализуются, но можно и так:

// Уже доступно. Возвращается просто строка, готовый HTML. 
// Преобразуется в HTmlResponse который, при необходимости согласно логике, насыщается, например, заголовками
$router->get(
    '/var1',
    static fn() => <<<'HTML'
        <html><head></head><body><ul>
            <li><a href="/name/Alex">Hi Alex</a> Текстовый ответ. Имя можно менять</li>
            <li><a href="/json/Alex">Hi Alex</a> Json ответ. Имя можно менять</li>
        </ul></body></html>
        HTML,
);
// Уже доступно. Возвращается уже готовый HtmlResponse, если есть у нас такая необходимость
$router->get(
    '/var2',
    static fn() => new HtmlResponse()->setBody(<<<'HTML'
        <html><head></head><body><ul>
            <li><a href="/name/Alex">Hi Alex</a> Текстовый ответ. Имя можно менять</li>
            <li><a href="/json/Alex">Hi Alex</a> Json ответ. Имя можно менять</li>
        </ul></body></html>
        HTML),
);
// Над этим работаю.
$router->get(
    '/var3',
    static fn(HtmlBuilder $builder) => $builder->setBody(<<<'HTML'
        <ul>
            <li><a href="/name/Alex">Hi Alex</a> Текстовый ответ. Имя можно менять</li>
            <li><a href="/json/Alex">Hi Alex</a> Json ответ. Имя можно менять</li>
        </ul>
        HTML)->build(),
);
// Это уже с модулем шаблонизации. HTML где то лежит/формируется согласно правил шаблонизатора
$router->get(
    '/var4',
    static fn(Templator $templator) => $templator->build(),
);

т.е. имеем четыре зарегистрированных url которые будут отдавать одинаковую страницу, но формировать ее разными способами

Александр Воробьев
На сайте с 03.02.2020
Offline
59
#262

--- del

S3
На сайте с 29.03.2012
Offline
379
#263
Александр Воробьев #:

Для пользователя (тот что на уровне шаблона уже) это будет выглядеть так

Конечный синтаксис еще не утвержден, но примерно как то так

Поздравляю, ты переизобрел jinja2. Именно так и работает пайтоновский шаблонизатор. Там очень удобная кастоизация, позволяющая создавать что угодно.
Делаешь базовый шаблон вида:
<!DOCTYPE html>
<html lang="ru">
  <head>
    {% include "head.html" %}
  </head>
  <body>
    {% include "navbar-main.html" %}

    <main class="container-fluid py-4">
      {% block content %}{% endblock %}
    </main>

    {% include "footer.html" %}
    {% include "cookie-consent.html" %}
    
    <script>
      (function () {
        const csrfToken = "{{ csrf_token | default('') }}";
        if (!csrfToken) return;
        const forms = document.querySelectorAll("form");
        forms.forEach((form) => {
          if (form.querySelector('input[name="csrf_token"]')) return;
          const input = document.createElement("input");
          input.type = "hidden";
          input.name = "csrf_token";
          input.value = csrfToken;
          form.appendChild(input);
        });
      })();
    
</script>
    <script src="/static/js/cookie-consent.js"></script>
  </body>
</html>


А потом просто наследуешься от него и создаешь кастомные страницы, переопределяя нужные тебе блоки таким образом - 

{% extends "base.html" %}

{% block content %}
<link rel="stylesheet" href="{{ url_for('static', path='/css/errors.css') }}">
<div class="container">
    <div class="row justify-content-center align-items-center" style="min-height: 70vh;">
        <div class="col-md-8 text-center">
            <div class="error-content">
                <h1 class="display-1 fw-bold text-danger">403</h1>
                <h2 class="mb-4">Access Forbidden</h2>
                <p class="lead text-muted mb-4">
                    {{ error_message or "You don't have permission to access this resource." }}
                </p>
                <div class="mb-4">
                    <i class="bi bi-shield-exclamation text-danger" style="font-size: 5rem;"></i>
                </div>
                <div class="alert alert-warning mx-auto" style="max-width: 600px;">
                    <i class="bi bi-info-circle"></i>
                    <strong>Need access?</strong> Please contact the administrator or try logging in with appropriate credentials.
                </div>
                <div class="d-flex justify-content-center gap-3 mt-4">
                    <a href="{{ url_for('index', lang=language) }}" class="btn btn-primary btn-lg">
                        <i class="bi bi-house-door"></i> Go to Home
                    </a>
                    {% if not is_authenticated %}
                    <a href="{{ url_for('user_login', lang=language) }}" class="btn btn-success btn-lg">
                        <i class="bi bi-box-arrow-in-right"></i> Login
                    </a>
                    {% endif %}
                    <button onclick="window.history.back()" class="btn btn-outline-secondary btn-lg">
                        <i class="bi bi-arrow-left"></i> Go Back
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

Отличная получилсь дискуссия, пока меня упекли(за дело) в баню. Зарекаюсь с экономистами дальше дискутировать)))

Александр,   Алексей, MrPi(Сорри, не знаю настоящего имени) - спасибо! Впервые за долгое время   - конструктивный разговор, Даже вставки флудеров не испортили.

Дискуссия кстати заставила копнуть разницу между Симфони и  Фастапи. Стал понимать ваши трудности. Мне кажется в ФА более логичная структура и правильный рендер аутпута. 
Если в симфони профайлер рендерит через тэги, то Фастапи использует шаблоны и готовый контекст.
Это гораздо быстрее.
Ну и роутинг удобнее. Причем можно создавать роуты, которые одновременно будут уметь работать как с REST API,  так и с HTML шаблонами.
В чистом виде DRY

@router.get("/my-quizzes", name="my_quiz_list")
async def my_quiz_list(
    request: Request,
    session: AsyncSession = Depends(get_async_session),
    current_user: dict = Depends(get_current_user_db),
    context: dict = Depends(page_context)
):
    # 1. Получаем «универсальные» данные
    data = await get_my_quizzes_data(session, current_user.get('user_id'))
    
    # 2. Проверяем, что хочет клиент (Content Negotiation)
    accept_header = request.headers.get("accept", "")
    
    if "application/json" in accept_header:
        # Отдаем чистый JSON для API
        return data

    # 3. Если это браузер, подмешиваем данные в контекст Jinja2
    full_context = {**context, **data}
    full_context["current_user"] = current_user
    full_context["page_title"] = "My Quizzes"
    
    return templates.TemplateResponse("my_quizzes.html", {"request": request, **full_context})
Александр Воробьев
На сайте с 03.02.2020
Offline
59
#264
Sly32 #:
Поздравляю, ты переизобрел jinja2. Именно так и работает пайтоновский шаблонизатор.

А они все плюс минус одинаково. Блейд так же примерно. На самом деле я хотел пойти несколько иным путем, но именно тут была реплика, что разработчикам было бы удобнее видеть нечто привычное.  я подумал подумал и ....  но повторюсь пока это проект.   У меня база шаблонизатора уже пережила три варианта, и лишь когда осознал направление куда двигаться вернулся к фреймворку - там не хватало например механизмов интеграции модулей удобной  (а шаблонизатор это модуль) :)

Единственное у меня в шаблонах будет только то, что внутри body, остальное декларативно описывается и передается (под капотом) генератору... (посмотрим что из этого выйдет в итоге :) - но хочу попробовать)

Александр Воробьев
На сайте с 03.02.2020
Offline
59
#265
Sly32 #:
Дискуссия кстати заставила копнуть разницу между Симфони и  Фастапи.

В плане роутинга мне больше нравится подход как в ларавель. В симфони с его стремлением везде использовать атрибуты (а ранее в phpDoc) мне совсем не заходит. (именно по этому я и сделал больше похожим на лару).

В моем понимании создавая контроллер я не знаю как и зачем он будет использоваться. И на какие маршруты отвечать. Это не ответственность кода контроллера. Это ответственность системы маршрутизации. Если у меня на проекте возникла причина поменять маршруты. (ну условно изменит /api/ на /my_best_api/ - как очень упрощенный частный случай) - то я недолжен лезть в код и проходить все контроллеры.

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

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class BlogController extends AbstractController
{
    #[Route('/api/blog_create', name: 'api_blog_create')]
    #[Route('/blog/create', name: 'blog_create')]
    public function new(Request $request): Response
    {
        if ($request->getPreferredFormat() === 'json') {
            return $this->json([
                'status' => 'success',
                'message' => 'Это ответ в формате JSON для API',
                'data' => ['id' => 123]
            ]);
        }

        return $this->render('blog/new.html.twig', [
            'message' => 'Это HTML-страница'
        ]);
    }
}

Но!  На мой взгляд этот подход не правильный.  Контероллер должен быть тонким.  что в твоем, что в моем коде есть лишнее условие, которое решаемо на уровне роутинга. Тут, имхо, нарушение и солид (надо дополнить апи, но трогаем класс, который не имеет отношения к апи), SoC - смешиваем логику ответа условно для непосредственного отображения человеку, и обработки программно,  пожалуй KISS. Потом rest обычно подразумевает более широкий спектр статусов ответов...  ну понято, что ты привел просто пример, но все же нужная ли это фича? :)

S3
На сайте с 29.03.2012
Offline
379
#266
Александр Воробьев #:
В моем понимании создавая контроллер я не знаю как и зачем он будет использоваться. И на какие маршруты отвечать. Это не ответственность кода контроллера. Это ответственность системы маршрутизации.
Именно так это и работает в Фастапи. Роутер просто вызывает обработчик и дальше отдает ответ, в зависимости от потребности или JSON  или HTML(После рендера контекста)
Александр Воробьев #:
что в твоем, что в моем коде есть лишнее условие, которое решаемо на уровне роутинга. Тут, имхо, нарушение и солид
Нет, ты что то путаешь. Все разделено. Роутинг принимает реквест -делает запрос в менеджер, который в свою очередь может делать запрос в БД, например или еще какая-то логика и отдает назад ответ.
Александр Воробьев #:
Потом rest обычно подразумевает более широкий спектр статусов ответов...  ну понято, что ты привел просто пример, но все же нужная ли это фича? :)

Я пока думаю над этим, нашел эту фичу именно читая этот топик. Раньше я всегда отдельно писал АПИ для возврата JSON, отдельно  для браузера

У меня будут еще мобильные приложения и наличие такого ответа сильно упрощает код

Александр Воробьев
На сайте с 03.02.2020
Offline
59
#267
Sly32 #:
Нет, ты что то путаешь. Все разделено. Роутинг принимает реквест -делает запрос в менеджер, который в свою очередь может делать запрос в БД, например или еще какая-то логика и отдает назад ответ.

Я про условие "если хочет джсон то .... иначе...."

Sly32 #:

Я пока думаю над этим, нашел эту фичу именно читая этот топик. Раньше я всегда отдельно писал АПИ для возврата JSON, отдельно  для браузера

У меня будут еще мобильные приложения и наличие такого ответа сильно упрощает код

Ну собственно пример выше :), на ларавель тоже можно.

Единственное НО. Обычно для апи и веба разные миддлвары подключают. (настройка на уровне маршрутов и групп маршрутов). В контроллер мы попадаем уже через все эти миддлвары. В АПИ, как правило, не нужны сессии и SCRF токен, но нужен JWT...  и там тоже тогда надо проверять с чем работаем?  Или речь всегда о стейтлес работе?

S3
На сайте с 29.03.2012
Offline
379
#268
Александр Воробьев #:
Обычно для апи и веба разные миддлвары подключают. (настройка на уровне маршрутов и групп маршрутов). В контроллер мы попадаем уже через все эти миддлвары.
В Фастапи/джанго немного другая философия. Миддлвари используются  как прослойка на верхнем уровне, если нужно чем-то насытить респонс - для этого есть зависимости(Depends) которые насыщают респонс нужными данными непосредственно.
Да, у меня четкое разделение и все запросы в апи идут через JWT токен. По факту, существующий фронт - временное решение чтобы не заморачиваться, он все равно передет на Реакт. Так что или писать отдельную апи или проверять запрос и в зависимости от него отдавать результат.

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий