Задать вопрос
  • Какие есть высокоуровневые компонентные javascript фреймворки?

    Если я правильно понял вашу фразу "связано (полу-)автоматически с бэкендом", то вы имеете ввиду SSR (Server Side Rendering)

    Если бы я не увидел у вас ссылку на Quarkus (то бишь Java), я бы посоветовал вам не терять время на изучение чего-то странного и непопулярного, и приступить непосредственно к изучению React и его фреймворка для SSR Next.js. Я не люблю ни то, ни другое, но это, к сожалению, индустриальный стандарт, и очень желательно их знать.

    Но так как я увидел у вас бэкенд на Java и желание всё же иметь ту самую "полу-автоматическую связь с бэкендом", то можете поступить радикальным образом: взять какую-то UI библиотеку из веб-компонентов (например Shoelace), или более традиционные библиотеки UI, основанные на tailwindCSS (например, daisyUI).
    Весь HTML рисовать на бэкенде, а оживлять это всё с помощью библиотеки HTMX. Это позволит вам всю логику и стейт держать на бэкенде, и, в то же время, иметь очень хорошую интерактивность. https://www.youtube.com/watch?v=vgAKrggD0vQ&t=173s
    Ответ написан
  • Как организовать отправку сообщений в множество микросервисных нод?

    Берете какой-нибудь брокер сообщений.
    Например, Rabbitmq.
    Там создаёте exchange с типом fanout. Туда будете слать сообщения из сервиса-отправителя.
    Каждый сервис-получатель будет подключаться к этому exchange и для каждого будет создаваться своя очередь, куда будут попадать все сообщения. И каждый сервис спокойно сам вычитает свою очередь. И даже если он упадёт, то дочитает их после того, как подымется.
    https://youtu.be/P_EtQ-s-hiU?si=tavSDSzTnGpqQrAY
    Ответ написан
    2 комментария
  • Как получить группы, в которых состоит пользователь?

    Вот ответ для вашего конкретного случая в Windows.

    package main
    
    import (
    	"fmt"
    	"os/exec"
    	"strings"
    )
    
    func getGroups() ([]string, error) {
    	var groups []string
    
    	patch := "/groups"
    	cmd := exec.Command("whoami", patch)
    	output, err := cmd.Output()
    	if err != nil {
    		return nil, err
    	}
    
    	lines := strings.Split(string(output), "\n")
    	for _, line := range lines {
    		if strings.HasPrefix(line, "S-1-5-21") {
    			groups = append(groups, line)
    		}
    	}
    
    	return groups, nil
    }
    
    func main() {
    	groups, err := getGroups()
    	if err != nil {
    		fmt.Printf("Ошибка получения групп пользователя: %v\n", err)
    		return
    	}
    
    	fmt.Println("Группы пользователя:", groups)
    }
    Ответ написан
    21 комментарий
  • Как прописать путь к конфигу?

    Если есть какая-то определённая директория, в которой лежат конфиги (допустим /config/config.json), то самым простым и прямолинейным способом, без всяких зависимостей и ухищрений, будет создать в самой директории файл /config/config.go, который будет предоставлять простенький пакет config, и задачей которого будет тупо загружать конфиг из файла в своей директории в структурку с конфигом. И в остальных пакетах будете просто импортировать этот пакет и получать готовые данные.

    Или вот так

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"log"
    	"os"
    	"path/filepath"
    )
    
    type Config struct {
    	Env string `json:"env"`
    }
    
    func main() {
    	// Получаем текущую директорию
    	currentDir, err := os.Getwd()
    	if err != nil {
    		panic(err)
    	}
    
    	// Получаем путь к файлу относительно текущей директории
    	filePath := filepath.Join(currentDir, "config/config.json")
    
    	file, err := os.ReadFile(filePath)
    	if err != nil {
    		log.Fatal(err)
    	}
    	var config Config
    	json.Unmarshal(file, &config)
    
    	fmt.Println(fmt.Sprintf("ENV: %s", config.Env))
    }
    Ответ написан
    Комментировать
  • Как правильно вызвать метод в шаблоне Go?

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

    Вот вам простейший пример:

    index.html
    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>Document</title>
        </head>
        <body>
            <!--Вот то, что я предложил -->
            <h1>Page number {{ .Number }}</h1>
            {{ range .Posts }}
            <h2>{{ .Title }}</h2>
            <p>{{ .Body }}</p>
            {{ end }}
    
            <!--А вот то, что вы хотели -->
            <h1>Page number {{ .Number }}</h1>
            {{ range .Posts.GetSublistByPagenumber .Number }}
            <h2>{{ .Title }}</h2>
            <p>{{ .Body }}</p>
            {{ end }}
        </body>
    </html>


    main.go
    package main
    
    import (
    	"html/template"
    	"log"
    	"os"
    )
    
    // Структура поста
    type Post struct {
    	ID          int
    	Author      string
    	Title       string
    	Body        string
    	PostingTime string
    	UpdateTime  string
    	Tags        string
    	Lock        int
    }
    
    type Posts []Post
    
    func (posts Posts) GetSublistByPagenumber(pageNumber int) Posts {
    
    	return Posts{
    		Post{
    			ID:          1,
    			Author:      "Vitaliy",
    			Title:       "First post",
    			Body:        "Hello, world!",
    			PostingTime: "2021-10-10 10:00:00",
    			UpdateTime:  "2021-10-10 10:00:00",
    			Tags:        "first, post",
    			Lock:        0,
    		},
    		Post{
    			ID:          2,
    			Author:      "Alex",
    			Title:       "Second post",
    			Body:        "Hello, world!",
    			PostingTime: "2021-10-10 10:00:00",
    			UpdateTime:  "2021-10-10 10:00:00",
    			Tags:        "second, post",
    			Lock:        0,
    		},
    	}
    }
    
    // Структура с уже готовыми заполненными данными без всяких методов
    type PostTemplate struct {
    	Number int
    	Posts  Posts
    }
    
    func main() {
    	posts := Posts{}
    
    	// Подготавливаем список заранее
    	sublist := posts.GetSublistByPagenumber(1)
    
    	postVars := PostTemplate{
    		Number: 1,
    		Posts:  sublist,
    	}
    
    	tmpl, err := template.ParseFiles("index.html")
    	if err != nil {
    		log.Println(err.Error())
    		return
    	}
    
    	w := os.Stdout
    
    	err = tmpl.Execute(w, postVars)
    	if err != nil {
    		log.Println(err.Error())
    	}
    
    }
    Ответ написан
  • Как спроектировать роуты?

    Не стоит выносить модерацию на уровень API. API так и должно быть обычным CRUD (create, read, update, delete). А далее нужно внутри построить систему доступа, которая будет определять, какой пользователь что может делать с конкретной сущностью. Потому что у вас могут появиться какие-то другие пользователи, кроме модераторов которые должны будут иметь немного другие полномочия, и т.д.
    Для гугления: Access Control

    1. RBAC (Role-based access control). Самая простая система. Вы назначаете каждому пользователю роль или набор ролей, и при проверке, может ли данный пользователь что-то там сделать, просто проверяется, имеет ли он какую-то роль. Сделайте этот список ролей пользователя доступным в тех частях проекта, где надо проверять доступ, и спокойно себе там и проверять. Эта система очень часто прекрасно работает в простых проектах, но она не очень гибкая, и часто приходится писать много дополнительных условий, добавлять много ролей в каждое из условий. Похоже, вы пытаетесь как раз что-то подобное организовать, и вас это обилие условий раздражает.

    2. Разрешения (permissions). Это слегка доработанный RBAC, где вы создаете список разрешений, которые описывают что конкретно надо разрешить, и добавляете к этому разрешению список ролей, которым это позволено. Такая система - то что нужно для простых проектов. Потому что вы не заблудитесь в дебрях условий со списком ролей, а будете проверять разрешения, которые легко читать, легко понимать. Система доступа становится глобальной, вы можете безболезненно добавлять новые роли и вносить их в списки разрешений, не меняя основной код программы. Список разрешений может храниться даже в базе данных, и тогда вы налету сможете ограничивать какие-то роли.

    Например, самый простейший код на Javascript:
    const permissions = {
      "view answers": ["ROLE_GUEST", "ROLE_USER"],
      "add answers": ["ROLE_USER"],
      "delete answers": ["ROLE_MODERATOR"],
      "delete own answers": ["ROLE_USER"],
      "edit answers": ["ROLE_MODERATOR"],
      "edit own answers": ["ROLE_USER"],
      "change status": ["ROLE_MODERATOR"],
    };
    
    const user1 = {
      roles: ["ROLE_USER"],
    };
    
    const user2 = {
      roles: ["ROLE_USER", "ROLE_MODERATOR"],
    };
    
    function checkPermission(user, action) {
      const activeRoles = permissions[action].filter((role) =>
        user.roles.includes(role),
      );
      return activeRoles.length > 0;
    }
    
    console.log(checkPermission(user1, "view answers")); // true
    console.log(checkPermission(user1, "delete answers")); // false
    console.log(checkPermission(user1, "delete own answers")); // true
    console.log(checkPermission(user2, "delete answers")); // true
    console.log(checkPermission(user2, "delete own answers")); // true


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

    3. ABAC (Attribute-based access control), ReBAC (Relationship-based access control), PBAC (Policy-Based Access Control) - это уже более сложные, но в тоже время мощные и гибкие системы контроля доступа, и я оставляю их здесь для общего развития. Реализовать тот же ABAC не так уж и сложно, но за такую систему многие организации могут выложить кругленькую сумму.
    Ответ написан
    Комментировать
  • Чем отличается функция от конструктора и где применять то или это?

    К большому моему (личному) сожалению, Javascript - это язык, в котором возможно и разрешено вообще всё. Язык постоянно дополняется из "хотелок" пользователей.

    И вот в один момент разработчики спецификации ECMA-script решили, а давайте мы всё же оправдаем первые 4 буквы в названии языка, а именно "Java", и дадим пользователям сахарок, нарисованный над нашим прототипным наследованием, чтобы они могли везде писать class, extends и implements, как и все остальные ООП-динозавры. И сделали это.

    И с этим решением в наш прекрасный язык пришло понимание конструктора, как метода, создающего экземпляр класса. Конструктор очень сильно помогает с инкапсуляцией. В языках, имеющих настоящий ООП, есть приватные поля, которые не могут быть инициализированы прямым присваиванием в какой-то внешней функции, создающей объект, потому что к ним нет прямого доступа, и для такого придётся писать публичные сеттеры. А это плохо, потому что кто угодно сможет менять при помощи этих сеттеров то состояние, которое менять не надо.
    А вот из конструктора к приватным полям прямой доступ имеется. В Javascript приватные поля появились, кстати, вот буквально совсем недавно.

    class ClassWithPrivate {
      #privateField;
      publicField;
    
      constructor() {
        this.#privateField = "Доступ только изнутри класса";
      }
    }
    
    const instance = new ClassWithPrivate();
    
    instance.publicField = "Доступ извне класса";
    instance.#privateField; // Ошибка: SyntaxError: Private field '#privateField' must be declared in an enclosing class


    Еще одной особенностью конструктора является то, что он активно используется при наследовании (крестится и плюётся через левое плечо от отвращения). Если в дочернем классе не описан свой конструктор, то будет использован конструктор родителя. Это упрощает ООП.

    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} бежит со скоростью ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} стоит.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} прячется!`);
      }
    
      stop() {
        super.stop(); // вызываем родительский метод stop
        this.hide(); // и затем hide
      }
    }
    
    let rabbit = new Rabbit("Белый кролик"); // используется конструктор родителя
    
    rabbit.run(5); // Белый кролик бежит со скоростью 5.
    rabbit.stop(); // Белый кролик стоит. Белый кролик прячется!


    Таким образом, если вдарились в классический ООП, пользуйтесь везде конструктором, потому что ООП развивается десятилетиями, люди прошли сквозь страдания и выработали определённые "лучшие практики" для ООП.
    Если же вы используете объекты в основном как очень простые структуры без классической инкапсуляции с приватными полями, не используете наследование, а весь ваш код написан, в основном, на функциях, то делайте, ради бога, что вам вздумается, и отдельная функция-фабрика для создания объектов - это тоже вариант.
    Ответ написан
    Комментировать
  • Как сделать так, чтобы при нажатии не исчезало?

    Метод называется checkbox hack.
    Мы на самом деле используем checkbox и его превосходное свойство работать даже при нажатии на метку, которая с ним ассоциирована. Таким образом мы скрываем сам нативный checkbox, а его метку стилизуем так, как нам надо, хоть в виде кнопки, хоть в виде чего угодно. И вместо :active у кнопки проверяем :checked у чекбокса
    https://css-tricks.com/the-checkbox-hack/

    А вот ещё набор идей
    https://codepen.io/
    В поиске наберите "toggle-switch"

    И вот что-то похожее на ваш случай
    Ответ написан
    7 комментариев
  • Как заставить предупреждение срабатывать только после нажатия "Да/Нет" в диалоговом окне?

    Первое, что вам надо сделать - изучить самые базовые основы Javascript и DOM, и как это всё работает.
    Вы должны понимать, что Javascript - это не PHP. PHP работает только в один поток и выполняет операции исключительно одну за другой по порядку. В Javascript же есть возможность выполнять код асинхронно с использованием такого инструмента, как Event Loop. Ещё обязательно изучить, что такое Promise и как подсластить его использование при помощи сахарка async - await

    Т.е. ваш обычный код, который выполняется по порядку, может выполняться как бы "одновременно" с кодом, который выполняется при нажатии на кнопку. Т.е. пока ваша кнопка ждёт события click, чтобы выполнить какой-то код по нажатию, остальная программа спокойно себе исполняется по порядку. Таким образом, пользователь еще не успел кликнуть, но ваша программа уже создала диалог при помощи функции Myconfirmselect, и спокойно себе приступила к следующей операции. А этой следующей операцией как раз и является тот самый alert("!"). Вот он и появляется на экране.

    Т.е. нам надо как бы подсунуть этот alert внутрь функции Myconfirmselect. Как это сделать? В Javascript функции - это такие же объекты. Т.е. функцию можно назначить какой-то переменной и передать эту переменную в другую функцию. Вот мы и передаём наш callback в функцию Myconfirmselect просто в качестве аргумента, и вызываем этот аргумент как функцию уже в том месте, где обрабатывается событие click на кнопку.

    P.S. В коде постоянно происходит выборка одного и того же элемента. Это не очень хорошо, потому что такая выборка не бесплатна, все операции с DOM - довольно медленные, поэтому я позволил себе переписать часть кода, не меняя самой его структуры, чтобы вам было понятнее. Выбрав один раз наш конкретный dialog нет смысла выбирать его постоянно еще и еще раз. Запихнём его в переменную и будем с ним работать. То же самое касается и textarea и div

    <html>
        <body onload="Load()">
            <dialog></dialog>
            <script>
                function Load() {
                    setTimeout(() => {
                        // Создаём колбэк, функцию, которая будет вызываться только после нажатия на кнопку
                        const callback = function () {
                            alert("!");
                        };
    
                        Myconfirmselect(
                            "Готовы?" +
                                "¤" +
                                "Да" +
                                "¥" +
                                "alert('Да');" +
                                "º" +
                                "Нет" +
                                "¥" +
                                "alert('Нет');",
                            callback, // Передаём колбэк в функцию создания диалога
                        );
                    }, 500);
                }
    
                function Myconfirmselect(VarInput, callback) {
                    const dialog = document.querySelector("dialog");
                    dialog.innerHTML += `
       <div>
        <textarea></textarea>
       </div>
       `;
    
                    const textarea = dialog.querySelector("textarea");
                    const div = dialog.querySelector("div");
                    dialog.style.textAlign = "center";
                    textarea.style.width = "100%";
                    textarea.style.textAlign = "center";
                    textarea.style.resize = "none";
                    textarea.readOnly = true;
                    textarea.value = VarInput.split("¤")[0];
                    for (
                        let i = 0;
                        i < VarInput.split("¤")[1].split("º").length;
                        i++
                    ) {
                        const VarButton = document.createElement("button");
                        VarButton.textContent = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[0];
                        VarButton.id = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[1];
                        VarButton.style.marginLeft = "10px";
                        VarButton.style.marginTop = "10px";
                        VarButton.addEventListener("click", () => {
                            eval(event.target.id);
                            // Вызов того самого callback после нажатия на кнопку
                            callback();
                            //ButtonClosealert();
                        });
                        div.appendChild(VarButton);
                    }
                    function ButtonClosealert() {
                        dialog.style.animation = "AnimFormHide 0.1s both";
                        setTimeout(() => {
                            dialog.style.textAlign = "initial";
                            const div = dialog.querySelector("div");
                            dialog.removeChild(div);
                            dialog.close();
                        }, 100);
                    }
                    dialog.style.animation = "AnimFormShow 0.25s both";
                    dialog.showModal();
                }
            </script>
            <style>
                @keyframes AnimFormShow {
                    0% {
                        transform: translateY(-150%);
                    }
                }
    
                @keyframes AnimFormHide {
                    100% {
                        transform: translateY(-150%);
                        display: none;
                    }
                }
    
                dialog,
                #DialogCycle {
                    border: 2px solid black;
                    border-radius: 15px;
                }
    
                dialog div,
                #DialogCycle div {
                    position: initial;
                }
    
                dialog::backdrop,
                #DialogCycle::backdrop {
                    background: rgba(0, 0, 0, 0.6);
                }
            </style>
        </body>
    </html>
    Ответ написан
    Комментировать
  • Какие минусы у данного решения?

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

    https://silktide.com/toolbar/screen-reader-simulator/

    https://chromewebstore.google.com/detail/web-disab...

    Ваша цель:
    - сделать так, чтобы устройства для слепых "screen reader" адекватно сообщали пользователю, что от него требуется.
    - чтобы контраст был адекватным, чтобы и слабовидящие могли пользоваться
    Ответ написан
    2 комментария
  • Slog. Нужно ли предавать логгер через параметры или объявить его на уровне модуля и обращаться к нему?

    Насчёт параллельных обращений. slog очень гибкий и продуманный логгер. Не стоит беспокоиться насчёт параллельных обращений, там всё отлично с этим.

    Насчёт того, использовать ли логгер глобально...
    На этот вопрос трудно ответить, не зная, какой у вас проект.

    Лично я предпочитаю пользоваться принципом "Бритвы Оккама" и не вводить новых сущностей без необходимости.

    Сначала надо задаться вопросом: а нужны ли мне разные варианты логгера в одном и том же окружении? Что я такого буду логгировать в разные места и разным образом, чтобы мне надо было иметь кучу разных логгеров и передавать их как зависимость в каждую функцию или в каждый ваш синглтончик. И если не нужны, то зачем городить огород?

    Тем более, что даже для тестов вы можете написать для slog какой-то особый хэндлер, который будет работать так, как надо в тестах.

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

    Вот вам крайне увлекательное и крайне полезное видео, как из глобального slog можно сделать просто монстра логгирования на все случаи жизни.
    https://www.youtube.com/watch?v=p9XiOOU52Qw

    Вот пример того, как я настраивал логгер в одном из маленьких проектов. Он определяет по конфигу, с какого уровня надо логгировать, в каком формате, и в какой поток (stdout, stderr, файл)

    package app
    
    import (
    	"fmt"
    	"io"
    	"log/slog"
    	"os"
    )
    
    // SetupLogger создаёт логгер на основании конфига
    // Конфигурируем дефолтный slog и возвращаем функцию закрытия файла логов, 
    // если был выбран файл как место логгирования.
    func SetupLogger(cfg config.Config) func() error {
    	closer := func() error { return nil }
    
    	level := cfg.LogLevel
    
    	var w io.Writer
    
    	switch cfg.LogOutput {
    	case config.LogOutputStderr:
    		w = os.Stderr
    	case config.LogOutputStdout:
    		w = os.Stdout
    	case config.LogOutputFile:
    		w, closer = getFileWriter(cfg)
    	}
    
    	var handler slog.Handler
    	switch cfg.LogFormat {
    	case config.LogFormatText:
    		handler = slog.Handler(slog.NewTextHandler(w, &slog.HandlerOptions{Level: level}))
    	case config.LogFormatJSON:
    		handler = slog.Handler(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level}))
    	}
    
    	slog.SetDefault(slog.New(handler))
    
    	return closer
    }
    
    // getFileWriter возвращает файл, куда писать логи и функцию закрытия этого файла
    func getFileWriter(cfg config.Config) (*os.File, func() error) {
    	logDir := fmt.Sprintf("%s/%s", cfg.VarDir, cfg.LogDir)
    	err := os.MkdirAll(logDir, 0755)
    	if err != nil {
    		panic(fmt.Sprintf("error creating directory: %s;  %v", logDir, err))
    	}
    
    	filename := fmt.Sprintf("%s/%s.log", logDir, cfg.AppEnv)
    	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    	if err != nil {
    		panic(fmt.Sprintf("error opening file: %s %v", filename, err))
    	}
    	return f, f.Close
    }


    package main
    
    import "app"
    
    func main() {
    
            // .....
    
    	closeLogFile := app.SetupLogger(cfg)
    	defer closeLogFile()
    
            // ...
    }
    Ответ написан
    5 комментариев
  • Как в js, scss, html реализовать tooltip который показывается если навести на объект ниже?

    А зачем такие сложности с такой простой задачей, как тултипы? Их ведь можно реализовать полностью на CSS, просто добавив нужному элементу, например, атрибут "tooltip"



    Анимацию уже сами добавите, если очень нужна

    Если же очень хотите именно при наведении на следующий элемент что-то делать, то могу просто предложить идею селектора, который позволяет таким образом выбирать



    .custom-tooltip:has(+ *:hover) означает, что выбрать надо те элементы .custom-tooltip, у которых на следующем за ними элементе наведена мышь.
    "+" выбор непосредственно следующего элемента
    "+ *"- выбор любого непосредственного следующего элемента
    "+ *:hover" - выбор любого непосредственного следующего элемента, на который наведена мышь.
    has  выбирает элементы .custom-tooltip, с которым связаны элементы, выбранные при помощи уточняющего селектора в скобках.
    Ответ написан
    1 комментарий
  • Как избежать race condition при вставки новой записи в бд SQL, PHP?

    Создайте в таблице какой-то уникальный индекс, основанный на ваших данных, и второй раз одни и те же данные просто не получится вставить.
    Ответ написан
    2 комментария
  • Как обрабатывать данные, сохраняя очередность?

    Пусть хук записывает всю необходимую информацию и, главное, время в специальную таблицу в базе данных. Это всё, что будет делать хук. Таким образом он будет работать всегда быстро и всегда за одно и то же очень малое время.

    Далее по крону или каким-то другим образом совершенно другой скрипт будет выбирать самую старую запись из этой таблицы и обрабатывать её, совершая необходимые действия. Далее эту запись либо удаляем либо помечаем обработанной и берём следующую запись
    Ответ написан
    4 комментария
  • Почему не рендерится ответ fetch в виде DOM?

     Если вам нужно динамическое обновление частей страницы, и вы не можете это сделать сами, то вам не стоит изобретать велосипед, а воспользоваться готовыми библиотеками для этого.

    1. Вы можете вообще взять под контроль весь рендеринг DOM при помощи React, Vue и т.д. Но это вам надо будет серьёзно погрузиться в изучение этих фреймворков, и в классическом варианте вы потеряете поисковую оптимизацию.

    2. Воспользоваться библиотеками, которые как раз для этого предназначены.

    - Для Symfony есть прекраснейший компонент Live Components. Вы пишете всё в Twig, вставляя там особые атрибуты, и компонент возьмёт на себя всю работу по замене частей страницы нужными компонентами.

    - Либо, если вам не понравится почему-то Live Components, можете воспользоваться Htmx

    В результате вам даже не нужно будет особо знать JavaScript, всё будет автоматом работать

    P.S. Если вы уже пользуетесь фреймворком, то пользуйтесь его плюшками, сильно упрощающими жизнь. Я имею ввиду, что не стоит использовать голый curl с простынёй настроек, когда в наличии имеется превосходный HTTP Client https://symfony.com/doc/current/http_client.html
    А для сериализации/десериализации из JSON в PHP и обратно, лучше всего использовать Serializer component https://symfony.com/doc/current/components/seriali...
    Ответ написан
    Комментировать
  • Как отследить нажатие по определенной границе в canvas?

    У canvas есть прекрасный метод isPointInPath

    Документация https://developer.mozilla.org/en-US/docs/Web/API/C...

    Ответ написан
    Комментировать
  • Почему PhpStorm показывает ошибку в строке с SQL запросом?

    Ну так измените ваш диалект SQL. Phpstorm просто не знает, каким именно SQL вы пользуетесь (PostgreSQL, MySQL, SQLite).

    На скрине во всплывашке прямо внизу слева есть синяя ссылка "Change dialect to"

    К тому же, по-моему, если в проекте подключить базу в окне с базами данных, то он должен сам подхватить диалект оттуда
    Ответ написан
    3 комментария
  • Почему такой неадекватный расход памяти?

    У утечки памяти может быть миллион разных причин. Искать их, просто читая код - это искать иголку в стоге сена.

    Поэтому, умные люди придумали профайлинг. Мы собираем статистику и пытаемся её отобразить в более-менее наглядном виде.

    Например, есть вот такой инструмент
    https://github.com/arnaud-lb/php-memory-profiler
    Readme очень подробный, и можно, в принципе, им и обойтись.

    Есть ещё вот такие видео, в которых всё более комплексно и подробно

    https://www.youtube.com/watch?v=NNMp-97rk9c&t=219s

    https://www.youtube.com/watch?v=56I5C0NYjv8&t=293s

    Удачи в поисках!
    Ответ написан
    1 комментарий
  • Нужны ли везде дивы и как должна выглядеть верстка?

    Бри́тва О́ккама (иногда ле́звие О́ккама) — методологический принцип, в кратком виде гласящий: «Не следует множить сущее без необходимости» (либо «Не следует привлекать новые сущности без крайней на то необходимости»).

    Это главный принцип и верстки, и программирования.

    1. Оборачивайте элемент в div (или другой контейнер), если вы точно знаете, зачем вы это делаете, и без этого нельзя обойтись.

    2. Не вставляйте везде бездумно div. Почитайте про семантическую вёрстку, и используйте разные элементы-обёртки для разных типов контента.

    https://htmlacademy.ru/blog/html/semantics
    Ответ написан
    1 комментарий