Ответы пользователя по тегу PHP
  • Symfony 3 как при вызове класса передать в него параметры без new?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Для SF 3.4 https://symfony.com/doc/3.4/service_container/fact...

    // src/AppBundle/Service/MpdfFactory.php
    namespace AppBundle\Service;
    
    use Mpdf\Mpdf;
    
    class MpdfFactory
    {
        public static function createMpdf(): Mpdf
        {
            return new Mpdf(['tempDir' => self::getTmpDir(), 'utf-8', 'A4-P']);
        }
    }

    # app/config/services.yml
    services:
        Mpdf\Mpdf:
            # call the static method that creates the object
            factory: ['AppBundle\Service\MpdfFactory', 'createMpdf']
            # define the class of the created object
            class: Mpdf\Mpdf

    Далее просто в конструкторе сервиса указываете \Mpdf\Mpdf.
    Ответ написан
  • Как запустить LEMP на wsl2?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Пробовал WSL 2 с Docker, всё дико лагает если проект лежит в Windows. Пример, получить список команд через bin/console в Symfony нужно ждать секунд 5.

    Желательно проект хранить внутри WSL и открывать его через сетевую папку \\wsl$ в PHPStorm либо с помощью VSCode. По скорости все почти на уровне нативной Linux-системы. Из проблем: мне не удалось завести xDebug в PHPStorm через WSL с Docker. Для меня оказалось проще накатить Ubuntu второй системой.

    Если хотите шаринг папок с Windows, то WSL 1 пока подходит лучше. Производительность, конечно, не такая как на нативной системе, но работать можно.
    Ответ написан
  • Где лучше посмотреть реальные кейсы использования RabbitMQ с примерами кода, в идеале на GO или PHP?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Реальные кейсы использования можно посмотреть только на "реальных" проектах. Никто не выкладывает коммерческие проекты на всеобщее обозрение. Мы используем RabbitMQ с компонентом Symfony Messenger для следующих сценариев.

    • SMS/Email сообщения
    • Кропы фото. Сначала фотку загружаем в сторадж GCS. Затем отправляем сообщение в RabbitMQ, где мы из GCS скачиваем на локальный диск, кропим и грузим обратно GCS.
    • Удаление старых файлов: фото, pdf-ки и тд при изменении профайла или при перегенерации инвойса
    • Тяжелые операции, которые могут блокировать респонс. Различные обращения к сторонним API: создание профиля в платежной системе, в salesforce при регистрации.

    Вот простой пример. Мы создаем сущность миссии и рассылаем уведомления менеджерам и part-time employee, находящимся недалеко от места создания миссии. Далее у нас есть 2 воркера (async_new_mission_area_notification_to_manager и async_new_mission_notification_to_part_time_employee), которые слушают это событие.

    namespace App\Domain\Mission;
    
    class Mission implements ToArrayTransformable, RaiseEventsInterface
    {
        public function __construct(/* ... */)
        {
            // ...
    
            $this->initUuid();
    
            $this->raise(new MissionCreatedEvent($this->getUuidString()));
        }
    }
    
    namespace App\Application\Bus\MessageHandler\Event\Mission;
    
    class SendNewMissionAreaNotificationToManager implements MessageSubscriberInterface
    {
        private MissionRepositoryInterface $missionRepository;
    
        public function __construct(MissionRepositoryInterface $missionRepository)
        {
            $this->missionRepository = $missionRepository;
        }
    
        public function __invoke(MissionCreatedEvent $event): void
        {
            $mission = $this->missionRepository->getOneByCriteria(new MatchingUuidCriteria($event->getMissionUuid()));
    
            if (!$mission instanceof Mission) {
                return;
            }
    
            // Здесь уже ищем нужных менеджеров и рассылаем им уведомления
        }
    
        public static function getHandledMessages(): iterable
        {
            yield MissionCreatedEvent::class => [
                'from_transport' => 'async_new_mission_area_notification_to_manager',
            ];
        }
    }
    
    namespace App\Application\Bus\MessageHandler\Event\Mission;
    
    class SendNewMissionNotificationToPartTimeEmployeeHandler implements MessageSubscriberInterface
    {
        private MissionRepositoryInterface $missionRepository;
    
        public function __construct(MissionRepositoryInterface $missionRepository)
        {
            $this->missionRepository = $missionRepository;
        }
    
        public function __invoke(MissionCreatedEvent $event): void
        {
            $mission = $this->missionRepository->getOneByCriteria(new MatchingUuidCriteria($event->getMissionUuid()));
    
            if (!$mission instanceof Mission) {
                return;
            }
    
            // Рассылаем уведомления подходящим part-time employee
        }
    
        public static function getHandledMessages(): iterable
        {
            yield MissionCreatedEvent::class => [
                'from_transport' => 'async_new_mission_notification_to_part_time_employee',
            ];
        }
    }

    Если не заходит Symfony Messenger, то могу посоветовать SwarrotBundle с odolbeau/rabbit-mq-admin-toolkit.
    Ответ написан
  • Правильно ли использовать UTC везде на сервере?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Уже поздно отвечать, но напишу как мы работаем с датами в API на Symfony. Вдруг пригодится кому-нибудь.

    Даты храним в UTC в БД и таймзона для PHP выставлена в UTC.
    На фронт отдаем даты в ISO8601 - 2020-05-03T13:00:00.000Z и он спокойно с помощью moment.js переводит в текущее время юзера.

    Фронт присылает даты со смещением 2020-05-03T15:00:00+02:00, мы это перегоняем в UTC и сохраняем в БД.

    В сущности юзера храним таймзону (например, Europe/Paris) и юзеры в профиле могут её сменить. В twig-шаблонах для Email и SMS используем фильтры, которые переводят в таймзону юзера:

    {{ mission.range.start | to_user_timezone(employee) }}

    В MySQL используем функцию CONVERT_TZ в запросах, где нам нужно подбирать миссию в зависимости от расписания юзера. Юзер у себя в профиле указывает время когда он доступен, например, 15:00-20:00.
    Ответ написан
  • Правильно ли выбрасывать исключения в бизнес логике?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Да, правильно.

    Исключения должны бросаться, когда обработка сценария невозможна по каким-то причинам: прилетел некорректный объект или сама сущность не может быть изменена и тд.

    Пример кода на моем проекте:
    namespace App\Domain\Mission;
    
    class MissionOffer
    {
        // ...
    
        public function __construct(Employee $employee, Mission $mission)
        {
            if ($employee->worksInShop($mission->getShop())) {
                throw new MissionOfferCannotBeCreatedForEmployeeException(
                    $employee->getUuidString(),
                    $mission->getUuidString()
                );
            }
    
            $this->initUuid();
    
            // ...
        }
    
        // ...
    
        private function accept(): void
        {
            if (self::STATUS_OFFERED !== $this->status) {
                throw new MissionOfferCannotBeAcceptedException();
            }
    
            $this->status = self::STATUS_ACCEPTED;
            $this->acceptedAt = new DateTime();
        }
    
        public function refuse(): void
        {
            if (self::STATUS_OFFERED !== $this->status) {
                throw new MissionOfferCannotBeRefusedException();
            }
    
            $this->status = self::STATUS_REFUSED;
            $this->refusedAt = new DateTime();
    
            foreach ($this->offerSlots as $offerSlot) {
                if ($offerSlot->isPending()) {
                    $offerSlot->refuse();
                }
            }
        }
    }

    Эти исключения ловятся в одном месте - exception subscriber'е. В зависимости от эксепшена возвращается статус код + тело ответа.
    Ответ написан
  • Видеокурсы PHP С Русскими субтитрами?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Я php начал изучать (в 2015) с книги Кевина Янка "PHP и MySQL. От новичка к профессионалу". Может она сейчас немного устарела, но там сразу автор учит как разбивать код на контроллер и вьюху, а не мешать всю логику в одну кучу.
    Далее можно уже начать читать Мэт Зандстра "PHP. Объекты, шаблоны и методики программирования"

    Из видеоуроков на русском (c субтитрами) могу посоветовать hexlet. Насколько помню там какая-то часть курсов бесплатна, а так стоимость $24.
    Ответ написан
  • Как сохранять изображения на сервере?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    1. Используем абстракцию над хранилищем:
    В файле config/filesystems.php в массиве disks добавляем новый диск, например:
    'avatars' => [
        'driver' => 'local',
        'root' => storage_path('app/public/avatars'),
        'url' => env('APP_URL') . '/storage/avatars',
        'visibility' => 'public',
        'permissions' => [
            'file' => [
                'public' => 0644,
                'private' => 0600,
            ],
            'dir' => [
                'public' => 0775,
                'private' => 0700,
            ],
        ],
    ],

    2. Сохраняем файл так:
    Storage::disk('avatars')
         ->putFileAs('2019/03/17', $request->file('photo'), 'unique_file_name.png');

    3. В базе храним путь 2019/03/17/unique_file_name.png

    4. url картинки можно сгенерить так:
    Storage::disk('avatars')->url('2019/03/17/unique_file_name.png');
    Ответ написан
  • Зачем нужен PHPDoc в php-7?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Когда необходимо указать:
    • Тип коллекции
    • Миксированный тип
    • Бросаемые исключения

    /**
     * @return User[]
     * @throws BadRequestHttpException
     */
    public function getUsers()
    {
    
    }
    Ответ написан