Ответы пользователя по тегу ООП
  • Как подкорректировать Router?

    @EvgeniiR
    https://github.com/EvgeniiR
    private $classConfig;

    Использовать типизованные параметры

    $this->classConfig = new Config;

    Использовать DI и интерфейсы, ниже подробнее.

    $arr = $this->classConfig->getConfigs('routes'); // Вывод роутеров (Ниже пример)
        if ($arr) {
          foreach ($arr as $key => $value) {
                	$route = '#^' . $key . '$#';
                	$this->routes[$route] = $value;
            	}
        }

    Плохо. Почитайте про GRASP паттерны, особенно Information expert и, на будущее - Coupling/Cohesion(совсем-совсем на будущее, хотя можете глянуть докладик - тык, думаю сойдёт).
    Без регулярных выражений можно обойтись.
    Информация должна быть там где данные, не нужно доставать данные из какого-то объекта чтобы принять решение на основе их - попросите объект с данными принять решение.

    Как вариант - вместо этого должен инжектиться (по интерфейсу) какой-нибудь ControllerInfoProvider, или RoutesProvider, с методом вроде getControllerInfo($requestData);, который вернёт экземпляр DTO вроде
    /**
     * @psalm-immutable
     */
    class ControllerInfo {
        public string $controllerClass;
    
        public string $controllerMethod;
    
        /**
         * @var string[]
         * @psalm-var class-string[]
         */
        public array $beforeControllerMiddlewares;
    
        /**
         * @param string[] $beforeControllerMiddlewares
         * @psalm-param class-string[] $beforeControllerMiddlewares
         */
        public function __construct(string $controllerClass, string $controllerMethod, array $beforeControllerMiddlewares = [])
        {
            $this->controllerClass = $controllerClass;
            $this->controllerMethod = $controllerMethod;
            $this->beforeControllerMiddlewares = $beforeControllerMiddlewares;
        }
    }

    Ну и ещё можно postControllerMiddlewares, или, с более понятным названием вроде ResposeAwareMiddlewares, потому что миддлвари это хорошо.
    Ещё там аннотации для статического анализатора psalm, потому что ловить ошибки на этапе разработки запустив ./vendor/bin/psalm лучше чем ловить их после запуска кода, особенно в продакшене.

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

    $url = trim($_SERVER['REQUEST_URI'], '/');
    foreach ($this->routes as $route => $params) {
    if (preg_match($route, $url, $matches)) {
    $this->params = $params;
    if (isset($this->params['folder'])) {
    $this->params['folder'] = '\\' . ucfirst($this->params['folder']);
    }
    return true;
    }
    }
    return false;

    Не нужно в Роутере глобальные переменные парсить. Пусть до роутера это дело обработает какой-нибудь RequestParser, и создаст DTO-объект запроса, можно по psr-7, можно без него, чтобы проще было. Это не критично.

    По поводу параметров в контроллере - стоит обратить внимание на argument resolvers, и не заниматься этим в роутере. Вещь отличная и удобная, свои аргумент резолверы тоже пишутся просто.

    echo "<b>" . ucfirst($this->params['controller']) . "Controller</b> or <b>" . $action . "</b> not found.";
            return false;

    Не роутера это дело, в stdout писать. Пусть кинет исключение, а ответ пользователю сформируется уровнем выше.

    $path = 'app\controllers' . $this->params['folder']

    Роутер не должен определять структуру директорий. Используйте полное имя в конфиге роутов, т.е. SomeController::class.
    Сваливать все контроллеры в одну папочку занятие сомнительное.

    Почему (зачем) предпочитаю хранить роутеры в файлах (в базе данных), если можно автоматизировать

    Как удобнее так и храните. Конфиг, база, аннотации, php массив - без разницы

    Нужно ли корректировать код, указанный выше или и так сойдет?

    Чем больше сделаете из описанного выше - тем лучше.

    Что лучше: загружать контроллер из роутера или загружать класс контроллер (проверки...), который будет загружать необходимый контроллер?

    Получили ControllerInfo, обратились в ControllersRegistry->getControllerClass(), получили инстанс контроллера.
    Далее либо вызвали нужный метод, либо, если всё же сделали - попросили аргумент резолвер распарсить и инициализировать аргументы:
    $args = $this->argumentsResolver->resolve($controller, $method, $request);

    Вызвали миддлвари, что-то вроде этого: https://github.com/thephpleague/tactician/blob/mas... , вызвали контроллер:
    $response = $controller->{$action}(... $args)
    Ответ написан
    Комментировать
  • Есть ли в PHP общий класс от которого наследуются все классы?

    @EvgeniiR
    https://github.com/EvgeniiR
    Хочу здесь принимать объекты конкретного типа, а в интерфейсе нельзя указать общий тип для всех классов

    Вы нарушаете Liscow Substitution Principle когда в наследниках требуете более конкретный тип чем в родителе, не нужно так делать.

    Для экземпляров любых классов есть тайп хинт object, но он вам не поможет по описанной выше причине.

    Смысла в общем интерфейсе для всех репозиториев нет.
    Ответ написан
    5 комментариев
  • Зачем нужно ООП?

    @EvgeniiR
    https://github.com/EvgeniiR
    Разберитесь с разницей между ООП и процедурным программированием для начала.
    ООП в формулировке "Инкапсуляция, Наследование и Полиморфизм" может и не нужно.
    Объектно-ориентированный дизайн как инструмент декомпозиции нужен чтобы контролировать сложность системы.

    И вообще, вы хотите чтобы вам тут в двух словах разобрали тему многих книг и публикаций. Так не бывает.

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

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

    Впрочем, если вам этот ответ что-то даст:
    Цель ООП - управление сложностью ПО.
    Ответ написан
    1 комментарий
  • Как правильно задавать свойства классам php?

    @EvgeniiR
    https://github.com/EvgeniiR
    Правильно - задавать свойства в конструкторе класса. А публичные поля или методы get/set это нарушение инкапсуляции.
    Экземпляр класса должен полноценно работать и иметь в себе все необходимые для работы данные сразу после его создания.

    Есть исключения когда мы имитирируем структуры данных через классы-DTO(В PHP нету встроенных типов структур/data-классов), но заполнение и там должно быть через конструктор.

    Странно, здесь же на тостере Когда использовать static метода? например, пишут что правильная работа с ооп, это как раз задавать параметры через методы, а не через конструктор.

    На Тостере много чего пишут, не стоит всему верить, точнее стоит ничему не верить, а анализировать :)
    В плане каких-то конкретных терминов часто можно узнать очень много интересных вещей загуглив первоисточник термина/историю появления(в т.ч. причины).

    Постарайтесь определить цели и понять как эти варианты помогают вам эти цели достичь. А "правильных" вариантов без заданной цели не может быть.
    В будущем в этом плане стоит смотреть на концепты Coupling/Cohesion, и почему они важны(книжки Clean Architecture, Clean Code и т.п., так же принцип Low Coupling + High Cohesion входит в паттерны GRASP).

    По теме - задавая параметры через set() методы мы:
    - Подразумеваем что все пользователи нашего класса знают какие у него параметры, и более того - что они означают. Это создаёт большую нагрузку на пользователей класса, усложняет клиентский код.
    - Позволяем в любом месте где используется класс поменять что-то внутри, и неожиданно обнаружить ошибки в других местах системы. А ещё настройки могут конфликтовать друг с другом, и одни свойства зависеть от других. Установка одного поля может требовать установки другово, и такие вещи стоит указывать на уровне интерфейса, то есть сделать вместо условных setStartDate() и setEndDate() метод setDatePeriod(DatePeriod period), даже если внутри класса это два поля.

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

    И ещё - есть разница между методами которые меняют состояние класса, и конкретно "сеттерами". Семантика важна. То что внутри класса что-то меняется нас не интересует. Нам важно лишь чтобы выполнилось то, что мы ожидаем от класса. Если мы хотим обновить статью, мы вызовем метод updateArticle(), или какой-нибудь changeTitle(), например. Если мы хотим кофе мы скажем машинке makeEspresso(), а не будем указывать количество зёрен, упростив таким образом клиентский код.
    Ответ написан
    1 комментарий
  • Как правильно получить составные данные из базы и сложить их в одну сущность?

    @EvgeniiR
    https://github.com/EvgeniiR
    Немножко не ответ
    Для класса PostsModel:
    Автор Поста является потомком Поста.
    Пост для автора - Родитель.
    Пост для даты поста - Родитель.

    *Тут была реплика про уровень комьюнити тостера, и даже "Кураторов Тега", которые позволяют себе нести вышепроцитированную чушь, но я решил её опустить, т.к. разумный человек и так поймёт со временем*

    Совет - развивайте критическое мышление и фильтруйте информацию, не верьте всему в интернете, даже если у этого 100500 плюсов на каком-нибудь ресурсе типа Хабра/SO/Medium/Тостер и т.п.


    Алексей Коновалов,
    нам нужна одна точка получения данных.

    Нам не нужна "единая точка получения данных". У вас есть ваши модели(сущности, доменные модели) - это компонент вашей системы отвечающий за часть бизнес-логики.
    Так же у вас есть представления данных. Представлений данных может быть множество. Не нужно реиспользовать одну и ту же модельку для логики/записи и для чтения(представления, UI).
    Выбрали данные из БД(raw SQL/Eloquent/DQL смапленный на DTO, что угодно)
    ->заполнили ими структуру которая сформирована исходя из нужд клиентского модуля(Frontend`а вашего),
    -> привели к нужному формату(json etc.) и отдали на фронтенд

    Он работает с базой данных и делает все, что связано с постами. И тут как раз проблема.

    Очень хорошо что вы подметили что это проблема, серьёзно. Именно по этому "Active Record" - антипаттерн.
    Некоторые, к большому сожалению, годами к этому не могут придти, того больше - отстаивают каждое архитектурное решение какого-нибудь Laravel не с позиции инженера, а с позиции религиозного фанатика.

    Модели для записи ничего про HTTP, Request и представления знать не должны. Модели на чтение по сути просто структуры данных.
    Ответ написан
    Комментировать
  • Как наследоваться от 2 абстрактных классов С#?

    @EvgeniiR
    https://github.com/EvgeniiR
    Стоит задача описать 3 абстрактный класс который наследует в себе 2 класса

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

    Если вам кто-то даёт такие задачки под видом "задачек по ООП" - лесом его надо слать. Все эти наследования Customer от User, User от Human на практике в 99% случаев не нужны, т.к если условно модуль хочет себе Покупателя(Customer), ему нет разницы, Человек(Human) там, или робот и т.д, а любое наследование это жесткие иерархии классов в коде, лишняя связность(чит. Coupling) и лишние зависимости между компонентами, что делает код более сложным для понимания и изменения.

    Зачем в данном случае, наземно-воздушную MaskCar наследовать от StandartCar и StandartPlane? Всегда ли она будет вести себя так же как её родители, не будет ли нарушений принципа подстановки(см. Liscov Substitution Principle)?
    Если MaskCar планируется изменять независимо от родителей, даже если по каким-то причинам на текущий момент она реализует те же методы, нет никаких проблем определить те же методы в новом классе заново, и при необходимости реализовывать интерфейсы Car и Plane(ICar и IPlane в вашем случае).
    Если нужно поведение родителей, в большинстве случаев наследование можно без проблем заменить композицией, если речь идёт о C#, можно также очень удобно решать такие проблемы через делегирование(https://metanit.com/sharp/tutorial/3.13.php)
    Ответ написан
    Комментировать
  • Задачи по ооп для C#?

    @EvgeniiR
    https://github.com/EvgeniiR
    Ответ написан
    Комментировать
  • Как использовать функцию в роли класса (объекта)?

    @EvgeniiR
    https://github.com/EvgeniiR
    class App {
        public function getLocale();
    }
    
    function app {
        return new App(); // в вашем примере тут синглтон
    }


    Воспользуйтесь переходом к определению в вашей IDE, или поставьте PhpStorm если в вашей среде разработки нет такой возможности, чтобы отследить где объявляется эта функция

    p.s. но вообще, очень так себе подход, тут и статика, и глобальное состояние. Глобальный App в php только в Yii фреймворке я видел, да и те одумались и вроде выпилят его в 3 версии(если она будет, конечно)
    Ответ написан
    2 комментария
  • Как спроектировать агрегатор?

    @EvgeniiR
    https://github.com/EvgeniiR
    1. Сделать отдельную сущность - Рейтинг Организации, и иметь общую айдишку с организацией. Там уже статический конструктор инициализирующий рейтинг по количеству специализаций.
    Судя по описанию, ни имя, ни телефоны, ни адреса для расчета рейтинга не нужны, а значит смысла ложить его в ту же сущность нет.
    Думаю самый оптимальный вариант.

    2. Сущность можно при желании положить в поле в корне агрегата.
    Ответ написан
    Комментировать
  • Что такое property injection?

    @EvgeniiR
    https://github.com/EvgeniiR
    Изменение публичное переменной в которой лежит внедряемый объект. Точнее замена этого объекта.
    a = new A();
    a.logger = new Logger();


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

    @EvgeniiR
    https://github.com/EvgeniiR
    Роберт Мартин, "Чистая Архитектура", "Чистый код", "Идеальный программист"
    Макконнелл, "Совершенный код".

    Далее по ситуации, Фаулер, Эванс, Кент Бек и т.п.

    Заменять чтение книг собиранием по крупицам информации в интернете ни в коем случае не советую.
    Ответ написан
    28 комментариев
  • Пихать много логики в модель - это Laravel way?

    @EvgeniiR
    https://github.com/EvgeniiR
    Фреймворк -это инструмент для решения ваших задач. Не вы должны подстраиваться под фреймворк, нужно подбирать инструмент подходящий для ваших целей.
    По хорошему вся ваша бизнес-логика вообще никак не должна зависеть от фреймворка, и в этом плане у Yii и Laravel всё не очень хорошо, но Ларавель хоть компонентный, и кастомизировать можно, если понимать что делаешь.
    Ответ написан
    2 комментария
  • Зачем использовать private static в методах PHP?

    @EvgeniiR
    https://github.com/EvgeniiR
    Вы можете обращаться к приватным статическим методам из публичных статических методов.
    С не статическими приватными методами это было бы не возможно, т.к. отсутствует экземпляр класса
    Ответ написан
    Комментировать
  • Что использовать throw + try/catch или if + return?

    @EvgeniiR
    https://github.com/EvgeniiR
    Вопрос: насколько употребимо в данной ситуации (для проверки заполненности полей объекта Person) применение механизма исключений?
    Может быть стоило использовать if и return, к примеру?

    Ни то ни другое.
    Стоило бы использовать Notification.
    Тут статья про сравнение с механизмом исключений
    Ответ написан
    Комментировать