Задать вопрос
  • Как правильно вызвать метод в шаблоне 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 комментарий
  • В чем может быть причина сильного увеличения длительности ответа запросов при установке Load Balancer на 2 сервера со стаком MySQL, PHP, Nginx?

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

    Рекомендую вам найти слабое звено, начиная с самого конца:

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

    2. Когда тестовое окружение будет так же тормозить, начните искать слабое звено. Рекомендую начать с конца.

    3. Замените весь ваш прекрасный PHP код на один единственный файл index.php с Hello World. Если проблема уйдёт, значит виновата ваша программа. Чтобы найти место уже в программе, рекомендую запустить Prometheus + Grafana, установить библиотеку в ваш PHP, и писать в мониторинг длительность каждой сложной операции (длительность всего запроса, длительность запросов к базе, длительность запросов во внешние API, длительность каких-то подозрительных циклов, алгоритмов и т.д.) Так вы сможете найти точное место в программе, где происходит затуп. Это надо будет сделать и на проде после решения проблемы, чтобы вы всегда знали, что происходит с вашим проектом.

    4. Если проблема не в программе, проверьте конфиг PHP. Может быть, у вас там тупо мало воркеров включено, может быть не включён OPcache и т.д. Почитайте, как правильно настраивать PHP на проде.

    5. Если проблема не в этом, то... https://youtu.be/bq3HksAwb2Q?si=NsBZeYeHsq69JW6o
    Ответ написан
    Комментировать
  • Как должен выглядеть идеальный контроллер?

    Если хотите идеал, то он должен соответствовать следующим пунктам:

    1. Сериализация/десериализация - это дорогостоящее мероприятие, поэтому оно должно делаться только в двух местах: прямо на входе и прямо на выходе. Вход - это ваш контроллер, Выход - это другой сервис, куда вы передаёте данные, или база данных (тут тоже происходит сериализация, либо явно, либо в ORM). Во всех остальных слоях инфообмен должен совершаться уже при помощи объектов PHP либо нативных типов. Это экономит ресурсы. При передаче между слоями приложения объектов вместо значений либо ассоциативных массивов вы сразу будете видеть очепятки, IDE вам прекрасно поможет при помощи автодополнения, объекты могут иметь какие-то полезные методы.

    2. Очень желательно в каждом из слоёв иметь собственный класс, отвечающий за данные. Например, нам в слой API приходит JSON-чик с новым пользователем.
    - Сериализуем JSON в DTO UserInAPI, сразу валидируем всё то, что мы можем валидировать без слоя бизнес-логики, и либо отдаём клиенту ошибку, либо передаём сам объект UserInAPI в следующий слой: слой бизнес-логики
    - В слое бизнес логики, получаем DTO UserInAPI на входе, преобразуем его в свой бизнес-объект UserInBusiness, валидируем его уже с точки зрения бизнеса, и либо возвращаем ошибку в слой API, либо совершаем над ним действия, и передаём объект класса UserInBusiness в слой работы с базой
    - В слое работы с базой данных получаем на входе объект UserInBusiness, преобразуем его уже в сущность базы данных UserInDB, валидируем всё на предмет корректности данных для базы, и либо возвращаем ошибку в бизнес, либо сохраняем сущность класса UserInDB в базу.

    Зачем такие сложности, вы спросите? А просто обратите внимание на то, что скорость изменения кода в разных слоях разная.
    - API вообще должен меняться раз в сто лет, чтобы не злить клиентов. Поэтому DTO класс UserInAPI будет стабильным и редко будет меняться.
    - Бизнес-логика меняется очень часто. У класса UserInBusiness постоянно будут добавляться поля и методы, тут жизнь будет кипеть.
    - Слой базы данных будет меняться реже, чем слой бизнеса, но чаще, чем слой API, потому что нам нужны будут новые поля в базе, новые таблицы и связанные таблицы.
    - И если мы один тип сущности протащим во все слои, то эта сущность обрастёт таким количеством различной хрени, что нам плохо станет, когда будем на неё смотреть. Либо она обрастёт кучей декораторов в каждом из слоёв. Поэтому лучше всё разделить.

    3. Теперь внимание, казалось бы, что мы слишком сильно связываем наши слои, и нижестоящие слои знают что-то о вышестоящих, а это неправильно. Ведь мы передаём объект UserInAPI в слой бизнеса, т.е. слой бизнеса должен уметь работать с этим объектом. И так же слой базы должен уметь работать с объектом бизнеса UserInBusiness. Как же быть? А очень просто. На входе слоёв использовать интерфейсы. Т.е. в слое бизнеса мы будем принимать не сам класс UserInAPI, а объект, имплементирующий интерфейс UserIncoming, который объявим в бизнес слое, и заставим слой API сделать так, чтобы его класс UserInAPI имплементировал этот интерфейс. Таким образом слой бизнеса ничего не будет знать о слое API, а будет ждать на входе данные по контракту, описанному в интерфейсе. Бизнесу плевать на конкретную реализацию, ему нужны только методы getUsername, getEmail из интерфейса. А какой класс ему их предоставит - пофигу. Таким образом мы практически полностью разделяем слои и в два счёта сможем поменять слой API, где у нас HTTP контроллеры, на слой RabbitMQ, SOAP, Grpc и т.д.
    Ответ написан
    6 комментариев
  • Как правильно делать большие сайты? обязательно ли писать каждую страницу по отдельности?

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

    Итого, есть три пути:

    1. Беспроигрышный. Это Wordpress. Берете, делаете, все счастливы. Туториалов миллиарды, плагинов ещё больше, всё будет зашибись. Проблема одна: если надо будет тонко настраивать, то надо будет хоть немного изучить PHP.

    2. Когда надо быстро что-то добавить, даже с мобилы или с компьютера девушки, и чтобы оно сразу отобразилось на сайте, то либо смотрим в сторону п.1, либо если не хотите изучать PHP, выбираете себе CMS на базе Javascript. Есть даже так называемые headless cms, которые генерируют вам только JSON, а вы у себя на фронтенде сами его обрабатываете и рисуете всё на основе полученных данных.

    Это самый сложный вариант из трёх

    Вот список некоторых из таких CMS
    https://jamstack.org/headless-cms/

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

    Сразу посоветую вам Astro https://astro.build/

    Прелесть Astro в том, что новый контент вы можете писать в Markdown. Это такой простейший язык разметки текстовых документов. Вы сможете его выучить за час, не больше. Там всё настолько интуитивно и просто, что больше времени вам не понадобится. При этом формат мощный, убедиться в этом можно, посмотрев почти любой репозиторий на Гитхабе. Практически каждый из них имеет файл README.MD, где как раз всё написано в Markdown (MD)

    В Astro вам надо будет создать несколько шаблонов astro (html). Шаблон хедера, футера, сайдбара, основного контента, разных типов страниц (статья, список статей, и т.д.). И в этих шаблонах будет указано, где нужно выводить сгенерированный из Markdown контент. И далее новые странички вы будете добавлять путём добавления в соответствующие папки файликов с Markdown разметкой. Это очень просто, быстро и не напряжно.

    После добавления этих файликов вы запустите генерацию сайта, и Astro сгенерирует вам кучу готовых html страничек, которые вы просто зальёте на сервак. Профит!

    Берите youtube, набирайте там "Astro markdown", и изучайте туториалы. Процесс всего этого чрезвычайно прост, вы много времени не потеряете, зато сэкономите себе массу времени на создании новых материалов на сайте

    Можете посмотреть и другие генераторы статических сайтов тут https://jamstack.org/generators/
    Ответ написан
    Комментировать
  • Не находит пакет gitlab-ee на Ubunty. Что делать?

    Не мучайтесь. Берите докер.

    Вот обычная команда для запуска контейнера. Контейнер запустится в фоне и будет автоматически перезапускаться при остановке или перезагрузке хоста.

    docker run --detach \ # Запускаем контейнер в фоновом режиме
      --publish 443:443 --publish 80:80 --publish 22:22 \ # Открываем порты. Можете поставить те, что вам нужны, если эти уже используются где-то
      --name gitlab \ # Имя контейнера
      --restart always \ # Перезапускать контейнер при его остановке или перезагрузке
      --volume gitlab_config:/etc/gitlab \ # Подключаем том для конфигурации
      --volume gitlab_logs:/var/log/gitlab \ # Подключаем том для логов
      --volume gitlab_data:/var/opt/gitlab \ # Подключаем том для данных
      --shm-size 2gb \ # Устанавливаем размер разделяемой памяти
      gitlab/gitlab-ee:latest


    Или же можете использовать docker-compose.yml файл и запускать, находясь в этой же дириктории, через:

    docker compose up -d

    version: '3.6'
    services:
      web:
        image: 'gitlab/gitlab-ee:latest'
        container_name: gitlab
        restart: always
        ports:
          - '80:80'
          - '443:443'
          - '22:22'
        volumes:
          - 'gitlab_config:/etc/gitlab'
          - 'gitlab_logs:/var/log/gitlab'
          - 'gitlab_data:/var/opt/gitlab'
        shm_size: '2gb'
    
    volumes:
      gitlab_config: {}
      gitlab_logs: {}
      gitlab_data: {}


    Если докер у вас свежий и будет ругаться на наличие 'version', то просто удалите строчку с version

    Чтобы узнать начальный пароль, выполните:

    docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
    Ответ написан
    4 комментария