• Есть ли толковые аналоги планировщика заданий AT?

    ghost404
    @ghost404 Автор вопроса
    Запуск раз в час нарушает бизнес правила. Если новость должна быть опубликована в 11:34, то публикация ее в 12:00 или даже 13:00 некорректно.
    Новости я привел только как пример. Задач которые нужно выполнять в определенное время гораздо больше.

    Чтоб соблюсти бизнес требованию, крон должен дергать систему каждую минут. И одним UPDATE SET здесь тоже не обойтись так как мне нужно пушить доменные события по публикации и т.д. То есть мне нужно выгружать все новости которые нужно опубликовать и публиковать их по одной.

    Я не просто так сказал что крон не подходит.
  • Время кеширование запросов влияет на бизнес логики. Что делать?

    ghost404
    @ghost404 Автор вопроса
    Илья Толлю: В общем-то да. Проблема скорее архитектурная и я пытаюсь понять как ее решить/обойти.
    Как вы наверное и сами понимаете REST и ES здесь вообще не при делах.

    Как написал entermix, можно по крону проверять дату публикации новости и изменять флаг published, а в запросе проверять поле published, а не date_publication. Это приводит к некоторым осложнениям которые я описывал в другом комментарии.

    Если говорить о CQRS и EDA, то из крона мы можем дергать cli команду которая отправляет запрос (Query) на получение списка опубликованных новостей по дате публикации. Далее, для каждой полученной новости отправлять команду (Command) на публикацию новости. Обработчик команды выставляет флаг published и новость бросает доменное событие, что она опубликована. По событию из новости мы можем сбросить кеш запроса на выборку опубликованных новостей на фронте.

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

    В идеале нам нужно выполнить команду обновления статуса публикации новости один раз, в определенную дату и время. То есть, по идее, нужно что-то типа никсовой команды at. Проблема в том что:
    • управлять расписанием мы должны через скрипты
    • можно пользоваться напрямую командой at, но я не знаю на сколько она надежна, как она реагирует на падение сервера и как организовывать ее работу если у нас несколько пишущих серверов
    • если реализовывать своими средствами, то хранилище расписание должно быть быстрым и надежным (Redis и MySQL для этого не очень подходят)
    • реализация через cron не решает проблемы запуска приложения вхолостую


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

    ghost404
    @ghost404 Автор вопроса
    Илья Толлю: CQRS/ES, EDA и REST не имеют ни какого отношения к описанному мною вопросу. Неважно откуда будет выполнятся запрос, из Query в контексте CQRS, в обработчике событий, из контроллера или из репозитория. Проблема в том, как корректно сформировать параметры запроса.
  • Время кеширование запросов влияет на бизнес логики. Что делать?

    ghost404
    @ghost404 Автор вопроса
    dinegnet: так я и не кеширую в СУБД. Кеширую результаты запроса в Redis. Исправил вопрос уже.
    Вы слышали про многоуровневое кеширование?
  • Время кеширование запросов влияет на бизнес логики. Что делать?

    ghost404
    @ghost404 Автор вопроса
    dinegnet: проблема в том что запросы не кешируются. Об этом я и написал. Неважно что ты используешь в качестве системы кеширования, она не будет работать если хешсумма запроса меняется каждую минуту.
  • Время кеширование запросов влияет на бизнес логики. Что делать?

    ghost404
    @ghost404 Автор вопроса
    Так я и не в MySQL кеширую, а в Redis (каюсь, не написал это в вопросе). А в качестве ключа используется хеш запроса
  • Время кеширование запросов влияет на бизнес логики. Что делать?

    ghost404
    @ghost404 Автор вопроса
    Просто добавьте поле published (bool), например, и публикуйте отложенные новости используя планировщик.

    это очень плохой подход так-как в этом случае нам нужно дергать по крону базу каждую минуту чтоб проверить и обновить свойство published. И вариант дергать раз в 10 минут тоже не подходит, так-как где-то кеш 10 минут, а где-то меньше, а где-то его вообще нет.
    В общем это решение из серии костыля.
  • Как написать шаблон Bootstrap Collapse для Twig?

    ghost404
    @ghost404 Автор вопроса
    Сергей Новиков: тогда получается так
    {% include 'collapse.twig' with {type: 'list_news', title: 'Новости', data: {list: list_news}} %}
    {% include 'collapse.twig' with {type: 'list_links', title: 'Мы в соцсетях', data: [link1, link2]} %}
    {% include 'collapse.twig' with {type: 'help', title: 'Подсказка', data: 'help text'} %}


    {# collapse.twig #}
    <div class="card">
        <div class="card-header">{{ title }}</div>
        <div class="collapse>
            {% if type == 'list_news' %}
                {% for news in data %}
                    <a href="{{ path('path_to_news', {news: news.id}) }}">
                        <img src="{{ news.cover }}" alt="{{ news.title }}">
                        {{ news.date | date('j F')
                        {{ news.text }}
                    </a>
                {% endfor %}
            {% elseif type == 'list_links' %}
                {% for link in data %}
                    <a href="{{ link.path }}">{{ link.text }}</a>
                {% endfor %}
            {% elseif type == 'help' %}
                {{ data }}
            {% endif %}
        </div>
    </div>


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

    {# collapse.twig #}
    <div class="card">
        <div class="card-header">{{ title }}</div>
        <div class="collapse>
            {% include 'collapse/' ~ type ~ '.twig' with {data: data} %}
        </div>
    </div>


    и в результате мы получаем шаблон в шаблоне. ни чем не лучше чем тоже самое через макросы. даже хуже потому что нужно плодить файлы.
    А самая главная проблема заключается в том что шаблон представление данных должен определятся в шаблоне вызова шаблона collapse
  • Как написать шаблон Bootstrap Collapse для Twig?

    ghost404
    @ghost404 Автор вопроса
    Сергей Новиков: если я буду передавать объект то я не смогу сделать универсальный шаблон для collapse.
    Мне придется писать collapse шаблон для конкретного набора данных, а для другого набора данных шаблон будет другим.
    {% include 'collapse_list_news.twig' with {body: list_news} %}
    {% include 'collapse_list_links.twig' with {body: [link1, link2]} %}
    {% include 'collapse_help.twig' with {body: 'help text'} %}

    я хочу сделать универсальную обертку для collapse
  • Как написать шаблон Bootstrap Collapse для Twig?

    ghost404
    @ghost404 Автор вопроса
    Сергей Новиков: и дураку понятно что там HTML
    я спрашиваю как запихнуть шаблон в переменную

    если просто пытаться запихнуть шаблон в переменную то получится это
    {% set body = '<nav class="list-group mod-aside-list-40">' %}
    {% for item in list %}
        {% set body = body ~ '<a href="' ~ path('path_to_item', {item: item.id}) ~ '" class="list-group-item" data-filter="2">' ~ item.title ~ '</a>' %}
    {% endfor %}
    {% set body = body ~ '</nav>' %}
    
    {% include 'collapse.twig' with body only %}
  • Как написать шаблон Bootstrap Collapse для Twig?

    ghost404
    @ghost404 Автор вопроса
    а body то как формировать?
  • Стоит ли использовать типы данных ENUM & SET в БД MySQL?

    это можно упростить
    public static function getValues()
        {
            $values = array();
            foreach (static::$VALUES as $value) $values[$value] = $value;
            return $values;
        }

    до 1 строчки
    public static function getValues()
        {
            return array_combine(static::$VALUES, static::$VALUES);
        }
  • Как уточнить/конкретизировать типа параметра метода при имплементации интерфейса в PHP?

    ghost404
    @ghost404 Автор вопроса
    Алексей Уколов: хм. тогда получается такой интерфейс:

    interface UploadableInterface
    {
        public function getOldImages();
    }


    и реализация внутри сущности:
    class Cover implements CoverInterface, OgImageInterface, UploadableInterface
    {
        // ..
    
        public function getOldImages()
        {
            $return = [];
            foreach ($entity->getOldCovers() as $filename) {
                $return[] = $this->getCoverDownloadPath().$filename;
            }
            foreach ($entity->getOldOgImages() as $filename) {
                $return[] = $this->getOgImageDownloadPath().$filename;
            }
            return $return
        }
    }


    ну и удаление тогда будет таким:
    public function removeOldFiles(UploadableInterface $entity)
    {
        foreach ($entity->getOldImages() as $filename) {
            $this->fs->remove($this->root.$filename); // удаление файла обложки
        }
    }


    спасибо за мысль. я еще над ней подумаю
  • Как уточнить/конкретизировать типа параметра метода при имплементации интерфейса в PHP?

    ghost404
    @ghost404 Автор вопроса
    Алексей Уколов: То есть я ни как не могу отказаться от интерфейсов CoverInterface, PromoCoverInterface, OgImageInterface и FileInterface и менять в них по сути нечего.

    код везде очень похожий, но все таки он разный
  • Как уточнить/конкретизировать типа параметра метода при имплементации интерфейса в PHP?

    ghost404
    @ghost404 Автор вопроса
    Алексей Уколов: 2 вариант я упомянул в конце своего вопроса. На мой взгляд это выглядит как костыль:

    не знаю вставится ли код
    class Cover implements UploaderInterface
    {
        public function moveTmpFile($entity)
        {
            assert($entity instanceof CoverInterface, sprintf('Entity "%s" must implement interface "%s"', get_class($entity), CoverInterface::class));
            // ..
        }
    }


    Поясню по поводу пункта 1.
    Есть проект. В проекте есть модели/сущности/entities. Сущности могут иметь обложку (Cover), могут иметь промо обложку (PromoCover), могут иметь og:image (OgImage) и просто файл картинки (File). То есть имеется несколько моделей у которых есть обложка, но нет og:image. Есть несколько моделей у которых есть и обложка и промо обложка, но нет og:image и тд.

    Это все разные поля, разные переменные и они существуют отдельно. Я просто не могу выделить общую часть и вынести ее в отдельный интерфейс.

    Общий функционал я вынес в отдельные интерфейсы для стандартизации и не только.
    Например я не тестирую методы сущности каждую в отдельности, а тестирую через интерфейс. Тоесть пишу тест для интерфейса и в dataProvider передаю список моделей которые реализуют этот интерфейс.
    Еще эти интерфейсы испульзуются для чистки мусора. Например, при смене обложки для сущности на новую, старая картинка уже не нужна и её можно удалить. Она автоматически попадает в список устаревших при вызове метода setCover и доступна в из метода getOldCovers (ниже код). И при сохранении сущности она удаляется. Так вот, я создаю отдельного слушателя событий (Doctrine event subscriber если быть точнее) который слушает событие удаление сущности и при получении события он извлекает сущность и проверяет реализует ли она интерфейс CoverInterface и соответсвенно удаляет старую картинку. Этому обработчику событий не важен тип сущности, ему важно что бы сущность реализовывала интерфейс CoverInterface и только, а таких сущностей несколько.

    Есть еще пара функций опирающихся на интерфейс, но я их сейчас описывать не буду.

    Пример метода setCover()
    public function setCover($cover)
    {
        if ($this->cover) {
            $this->old_covers[] = $this->cover; // список старых обложек
        }
        $this->cover = $cover; // сохраняем новую обложку
        $this->cover_changed = true; // обложка была изменена
        return $this;
    }


    Ну и пример метода removeOldFiles()
    public function removeOldFiles(CoverInterface $entity)
    {
        $root = $this->root.$entity->getCoverDownloadPath(); // путь к дирректории с обложками
        foreach ($entity->getOldCovers() as $filename) {
            $this->fs->remove($root.$filename); // удаление файла обложки
        }
    }
  • Как уточнить/конкретизировать типа параметра метода при имплементации интерфейса в PHP?

    ghost404
    @ghost404 Автор вопроса
    Множественное наследование здесь ни как не поможет. Методы с одинаковым именем и разными аргументами. Читайте внимательней вопрос. И класс OgImage не должен реализовывать интерфейс OgImageInterface. Он его принимает в качестве аргумента методов
  • Как через API Github получить всех пользователей, оставивших комментарии?

    Evgeniy Burmakin: все для всех и в одном запросе это сильно дофига. Если у тебя маленький проект то можно перебрать, а для больших это очень большой объем данных. Вполне логично что нет такого API метода
  • Работа без высшего образования, это реально?

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