• Архитектура Entities в Doctrine, Symfony 4 - кто может помочь?

    ghost404
    @ghost404
    PHP Developer
    Есть такая штука как предметная область (Domain). Предметная область состоит из моделей (в Doctrine это Entity), сервисов предметной области и много чего ещё.

    На сколько я понимаю из ваших комментариев, у вас есть 3 интерфейса (UI) которые работают с единой предметной областью. В этом случае не нужно дублировать бизнес-логику под каждый интерфейс. Правильней выделить бизнес-логику в отдельный субъект и реиспользовать его в ваших приложениях с интерфейсами.
    Можно организовать код вашей бизнес-логики в самостоятельный модуль, вынести в пакете Composer и оформить как Symfony Bundle, что вы и сделали.

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

    ghost404
    @ghost404
    PHP Developer
    У меня есть опыт такого подхода.

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

    Не думаю что у вас есть в это особая необходимость. Но вы можете разработать какие-то свои компоненты и вынести их в отдельные репозитории и устанавливать как зависимости. Можно даже сделать их open source и сообщество может вам помогать в их разработке и сопровождении.

    Как и сказал BoShurik, бандл это уровень интеграции компанента в фрайморк. Выделяя компонент в отдельный репозиторий вам нужно и бандл выделять в отдельный репозиторий. Так у вас будет 2 отдельных git репозитория.

    Таким образом у вас будет следующие git репозитоии:
    - ядро проекта
    - компонент блога
    - бандл блога
    - компонент мероприятий
    - бандл мероприятий
    - компонент деятелей
    - бандл деятелей
    - компонент аккаунта
    - бандл аккаунта
    и др. х2

    Разделение проекта на много независимых репозиториев это не есть плохо. Как и разделение проекта на микросервисы. Но у этого подхода есть существенный недостаток. Часто нужно внедрять новые фичи или править баги которые требуют внесения изменений в несколько репозиториев одновременно. Это доставляет массу неудобств. Особенно когда нужно проходить через этап релиза. Протестировать в проекте работу правок можно только после внесения их в бандл и релизе его, а зарелизить бандл можно только после его тестирования, а тестирование бандла требует тестирования и релиза компонента, а делать релиз компонента не убедившись как оно будет работать в конечном продукте нельзя. В общем релизный ад. Я проходил через него и не советую другим. Это осмысленно только для больших проектов с большой командой разработчиков.

    Как и сказал BoShurik, разделяйте проект на независимые модули и организуйте модули в папки и неймспейсы. Я пользуюсь этим методом уже несколько лет. Полет нормальный.

    App/Blog/Controller
    App/Blog/Entity
    App/Events/Controller
    App/Events/Entity


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

    App/Blog/AppBlogBundle
    App/Events/AppEventsBundle


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

    Касательно того как всё устроено конкретно в Symfony. Я довольно давно изучал этот вопрос и могу ошибаться в деталях.
    В Symfony когда-то использовался git submodules для компонентов. Но от этого решения отказались. Оно не очень удобное, особенно для конечного пользователя. В плане разработки это чуть удобнее чем отдельные пакеты на мой взгляд.
    Сейчас всё работает иначе. Есть основной репозиторий symfony/symfony. Все компоненты находятся в нем. Всё правки вносятся в него. В репозиториях компонентов никакой активности нет. При коммитете или пуше в ветки symfony/symfony запускается хук который на отдельном сервере SensioLabs запускается специальная утилита которая копирует все коммиты сделанные в компонентах из symfony/symfony в репозитории соответствующих компонентов. Мне как-то попадался сайт SensioLabs на котором можно было скачать эту утилиту.
    Суть в том, что это не репозиторий Symfony состоит из репозиториев компонентов. Это компоненты Symfony по сути выдранные куски кода из основного репозитория.

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

    ghost404
    @ghost404
    PHP Developer
    Для начала рекомендую глянуть библиотеку для реализации доменных событий
    https://github.com/gpslab/domain-event

    Еще рекомендую определится с терминологией. "Покупка лучшего фильма", "Лучшие фильмы" и "Покупка фильма" это не домены, а ограниченные контексты (Bounded Context).

    Не может быть агрегата уровнем выше. У вас неправильное понимание термина Агрегат. И с доменными событиями тоже самое. Доменные события должны бросаться в сущностях, а не сервисах прикладного уровня.

    Далее. Вы списываете баланс со счета пользователя, потом выдаёте ему доступ к фильму и потом бросаете событие и все это делаете последовательно, да ещё и заварачиваете в БД транзакцию, хотя это бизнес транзакция.

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

    И все же вернёмся к вашей проблеме.
    > пользователь может подписаться на услугу "Дайте мне доступ к лучшему фильму недели"

    Вы явно описали понятие Услуга. И у вас есть две, скажем так, услуги:
    - Лучшие фильмы недели
    - Покупка одного фильма

    Отсюда у вас появляется:
    interface Service
    {
        public function price(): Money;
    
        // ...
    }
    
    class OneFilmService implements Service
    {
        public function __construct(Film $film)
        {
            // ...
        }
    
       // ...
    }
    
    class BestFilmsService implements Service
    {
        // ...
    }
    
    class User
    {
       // ...
    
        public function buyService(
            AccountRepository $repository,
            Service $service
        ) {
            // получаем текущий счёт пользователя
            // и выполняем покупку
            $repository
                ->get($this->id)
                ->buy($service->price())
            ;
    
            // добавляем пользователю
            // преобретенную услугу
            $this->services[] = $service;
    
            // бросаем доменное событие
            $this->raise(new ServicePurchased(
                $this->id,
                $service->id()
            );
        }
    }

    Простой и наглядный способ покупки услуги.

    Получение денег от клиента по средствам СМС инкапсулированно в UserAccount.

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

    То есть, пользователь покупает/заказывает услугу и возможно даже видит ее в своём личном кабинете, но активация услуги и соответственно доступ к ней выполняется только после поступления средств от клиента через СМС или ещё как.
    Ответ написан
  • Как написать шаблон Bootstrap Collapse для Twig?

    ghost404
    @ghost404 Автор вопроса
    PHP Developer
    Что получается у меня на макросах.

    {# collapse.html.twig #}
    
    {% macro tablist(cards, id) %}
        <div class="card-collapse" id="{{ id }}" role="tablist" aria-multiselectable="true">
            {% for card in cards %}
                {{ card }}
            {% endfor %}
        </div>
    {% endmacro %}
    
    {% macro card(title, extended, parent, target, body) %}
        <div class="card{% if extended %} extended{% endif %}">
            <div class="card-header card-header-collapsed">
                <a
                    class="collapsed"
                    data-toggle="collapse"
                    data-parent="#{{ parent }}"
                    href="#{{ target }}"
                    aria-expanded="{% if extended %}true{% else %}false{% endif %}"
                    aria-controls="collapseTree"
                >
                    {{ title }}
                    <span class=" pull-xs-right">
                        <i class="zmdi zmdi-chevron-down zmdi-hc-fw check"></i>
                        <i class="zmdi zmdi-chevron-up zmdi-hc-fw uncheck"></i>
                    </span>
                </a>
            </div>
            <div class="collapse{% if extended %} in fade{% endif %} hasAccordion" id="{{ target }}"  role="tabpanel">
                {{ body }}
            </div>
        </div>
    {% endmacro %}


    {# game.html.twig #}
    
    {% macro form(form) %}
        <div class="card-block">
            {{ form_row(form) }}
        </div>
        <div class="card-block clearfix">
            <button type="submit" class="btn btn-link card-link pull-xs-right">Применить</button>
        </div>
    {% endmacro %}
    
    {% macro login() %}
        <div class="card-block bg-warning">
            Для того чтобы сохранять свои результаты в игре, войдите в систему или зарегистрируйтесь!
        </div>
        <div class="card-footer">
            <button class="btn btn-link m-y-0 p-a-0 card-link" data-toggle="modal" data-target="#drawer-log-in">Войти</button>
            <button class="btn btn-link m-y-0 p-a-0 card-link" data-toggle="modal" data-target="#drawer-log-in">Регистрация</button>
        </div>
    {% endmacro %}
    
    {# вывод блока collapse #}
    {% import _self as body %}
    {% import '::collapse.html.twig' as collapse %}
    {{ collapse.tablist([
        collapse.card(
            'Группа',
            form.groups.vars.value is not empty,
            'accordion',
            'collapseJanr',
            body.form(form.groups)
        ),
        collapse.card(
            'Фильтр по названию',
            form.query.vars.value is not empty,
            'accordion',
            'filterByName',
            body.form(form.query)
        ),
        collapse.card(
            'Тип игры',
            form.content_types.vars.value is not empty,
            'accordion',
            'collapseTwo',
            body.form(form.content_types)
        ),
        collapse.card(
            'Возраст',
            form.age_ratings.vars.value is not empty,
            'accordion',
            'collapseOne',
            body.form(form.age_ratings)
        ),
        collapse.card(
            'Совет',
            true,
            'accordion',
            'collapseHelp',
            body.login()
        ),
    ], 'accordion') }}
    Ответ написан
  • Как документировать тип переменной в классе-наследнике в PHP?

    ghost404
    @ghost404
    PHP Developer
    еще вариант с повторным определением свойства с корректный phpDoc
    class SomeCollection extends IterableCollection
    {
        /**
         * @var SomeClass[]
         */
        protected $items = [];
    
        public function doSomeWork()
        {
            $this->items[0]->someMethod();
        }
        ...
    }
    Ответ написан
  • Работа без высшего образования, это реально?

    ghost404
    @ghost404
    PHP Developer
    Скажу по своему опыту.
    Фрилансить начал еще в школе. Поступал в вышку на вечерку потому что родители сказали что не будут меня обеспечивать и мне пришлось идти работать. Опыта у меня было мало, но я без особых проблем нашел себе работу программиста. К окончанию вуза у меня была не только корочка, но и большой практический опыт. Опыт работы в крупной социальной сети и зп на порядок выше чем у любого выпускника вуза.

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

    ghost404
    @ghost404
    PHP Developer
    Не понял о чем вы и о каком фреймворке идет речь

    на вскидку
    class Product extends \Model
    {
        public function getCategory()
        {
            return $this->getDbConnection('connection_second')
                ->getModel('Category')
                ->find($this->cat_id);
        }
    }

    получние категории продукта
    Product::on('connection_first')->find($product_id)->getCategory();
    Ответ написан