Александр Карпов
388 subscribers
26 photos
1 video
39 links
Блог PHP разработчика для шаринга личного опыта с друзьями, коллегами и всеми желающими.

Не стесняйтесь задавать вопросы в комментариях к постам, или в личку: @slayervc (UTC +8).
Download Telegram
Всем привет! Меня зовут Александр, я занимаюсь разработкой на PHP 8 лет, а проектированием инженерных систем и автоматизацией бизнес- и технологических процессов – уже больше 15 лет. За это время я наработал некоторое количество материалов и наблюдений как по архитектуре ПО, так и по написанию качественного кода, приобрел опыт менторства товарищей по команде, прошел под сотню техинтервью, получив десятки офферов. Успел поработать и в маленьких стартапах, и в крупнейших интернет-магазинах страны.

И чем больше проходило времени, тем сильнее давила на меня совесть (а иногда еще и коллеги 😁): накопленным опытом нужно делиться. Так что в итоге я решил попробовать себя в качестве автора своего блога, в котором планирую публиковать как сиюминутные мысли, возникающие в повседневной работе, так и более серьезные рассуждения на сложные темы. Также буду стараться делиться с читателем советами по прокачке в профессии и карьере и личным опытом прохождения техинтервью.
9👍5🔥4
В ходе совместной работы мне довольно часто приходится объяснять товарищам по команде то, как стоит на практике понимать и реализовывать фундаментальные понятия, такие как, например, принципы ООП, энциклопедические определения которых, не знаю как сейчас, но раньше знал любой десятиклассник, внимательно слушавший учителя информатики.

С одного из таких принципов мне и хотелось бы начать. Инкапсуляция. Все слышали, да не все понимают.)) Из года в год я вижу одни и те же ошибки на код-ревью у самых разных разработчиков.

Часть 1 из 3. Продолжение ниже.

Изображение: Kandinsky по промпту "Инкапсуляция".

#ООП #инкапсуляция #интерфейс_класса
👍3🔥21
Инкапсуляция. Продолжение. Часть 2 из 3.

Когда я начинаю объяснять инкапсуляцию, я даю два совета для ее практического понимания. Первый из них состоит в том, чтобы добавлять к слову «инкапсуляция» слова «за интерфейсом». Я инкапсулирую за интерфейсом. В этом месте нарушена инкапсуляция интерфейса.

Давайте разберемся поподробнее, зайдя немного издалека. Что такое класс в ООП? Класс – это нечто, что объединяет (инкапсулирует за интерфейсом) поведение и данные. Данные – это поля класса, а поведение – его методы. Кроме полей и методов класса есть еще кое-что: области видимости. Публичные поля и методы класса образуют его интерфейс, за которым инкапсулированы (скрыты!) его приватные поля и методы. То есть, изменяя области видимости полей и методов класса, мы изменяем его интерфейс и одновременно изменяем то, что именно будет инкапсулировано (скрыто) за этим интерфейсом.

И этот момент необходимо держать в голове при написании каждого класса. Какие поля и методы мы спрячем, а какие будут торчать наружу. Зависит от класса, правда? К теме интерфейса, думаю, я буду возвращаться в будущих постах еще не раз, это большая и важная тема. Сейчас я лишь хочу подчеркнуть, что каждый раз, когда вы пишите класс, или добавляете в него новый метод, необходимо помнить в том числе и о базовом принципе ООП – инкапсуляции. Не нарушает ли ваш новый публичный метод этот принцип? Точно ли вы хотите, чтобы вот эта сигнатура метода торчала наружу и любой желающий мог из любого места вызвать этот метод?
👍42🔥2
Инкапсуляция. Продолжение. Часть 3 из 3.

Второй совет, который я даю, состоит в том, чтобы представлять, что мы пишем не на интерпретируемом, а на компилируемом языке. Представьте, что ваш класс сразу после того, как вы закончите над ним работу, будет скомпилирован в некую библиотеку (DLL, например). Ваши коллеги и любой, кто будет работать с этим классом после вас, не смогут увидеть его код. То есть они не будут ничего знать о том, как работает ваш класс и что именно он делает. Все, что они будут видеть – это его интерфейс (за которым вы инкапсулировали все внутренности вашего класса). Когда вы начинаете мыслить подобным образом, вы начинаете более ответственно и внимательно относиться к интерфейсу вашего класса. Ведь интерфейс – это лицо, документация и «пульт с кнопками» вашего класса. Что увидят другие разработчики, глядя на ваш интерфейс? Поймут ли они, что делает ваш класс, для чего он предназначен? Смогут ли они с первой попытки воспользоваться вашим классом правильно, то есть так, как вы изначально задумали? Смогут ли они воспользоваться вашим классом неправильно, например, вызвав в самом начале какой-то метод, который должен вызываться только в середине? Задавая себе подобные вопросы, вы думаете об инкапсуляции (не только о ней, но и о ней тоже).

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

В заключении хотел бы добавить еще пару мыслей из повседневной практики ревью и программирования. Первое: если вы создали класс и не задумываясь, автоматически сделали геттеры и сеттеры для всех его полей, лучше просто сделайте все поля публичными. С точки зрения инкапсуляции это одно и то же. Из этого вытекает второе: если у вас есть какое-то приватное поле, которое Шторм вам подсвечивает как неиспользуемое (пока не используемое) это не значит, что нужно автоматически открывать его наружу сеттером и геттером. Не нужно, если вы так считаете. Ведь это ваш класс, и вы знаете, зачем там это поле и должно ли оно торчать наружу. Третье: когда вы пользуетесь своим классом в клиентском коде, обращайте внимание на то, каким получается этот самый клиентский код, использующий ваш класс. Нет ли в этом коде какой-то технической логики (лишних циклов или подготовки данных, необходимых классу), которую вы хотели бы скрыть, инкапсулировать за интерфейсом класса, сделав клиентский код более легким и читаемым? Каждый класс, который вы пишите, нужно рассматривать именно из клиентского кода (если слова «клиентский код», вдруг, вызывают у вас затруднение, срочно погуглите). Именно во время работы с классом в клиентском коде вы имеете наилучшую возможность понять, не размотали ли вы случайно какие-то кишки класса наружу. Может лучше их спрятать (инкапсулировать) за интерфейсом?
👏61👍1🔥1
Собрав обратную связь после первого поста, решил взять паузу (удлиненную праздниками 🥂) на ее осмысление. Оказалось, что изначально задумывавшаяся направленность канала не слишком удачна. Точнее, тот материал, которым я планировал делиться, не слишком полезен, попадая как бы в середину. Более опытных ребят мне удивить, рассказав им что-то новое, практически нечем, а тех, кто мог бы еще много чему поучиться, не слишком занимают рассуждения о тонкостях красивого кода и хорошей архитектуры проекта, так как пристегнуть им эти рассуждения не к чему: волнуют более приземленные и насущные вопросы.

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

Попробую писать в таком ключе, надеюсь окажется полезным. Как всегда, ваша обратная связь в этом деле для меня главный ориентир, так что всегда буду вам благодарен за любые комментарии, как отправленные в личку, так и оставленные публично.
3👍1🔥1
Нужна ли рефлексия? И если нужна, то где? Случалось несколько раз спорить на эту тему с теми, кто считает, что использование рефлексии усложняет проект и делает его менее читаемым для джунов и даже миддлов. Но, пардон, что тут усложнять? Если речь идет о необходимости изменить/прочитать значение приватного свойства, то делов на три строчки кода. Где это нужно? Чаще всего – при ручной гидрации. Где нужна ручная гидрация? В проектах, следующих DDD, у вас ее будет не меньше половины (а если у вас ее меньше, то скорее всего зря вы притащили в свой проект DDD 😈).

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

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

#рефлексия #гидрация
Продолжая тему предыдущего 👆 поста: еще один пример менее частого, но все же встречающегося простого и полезного использования рефлексии – это работа с кастомными атрибутами. Из моей личной практики и отношения к коду кастомные атрибуты наиболее уместны в слое контроллеров (а в доменном слое ИМО неуместны вообще никакие атрибуты, ибо нечего засорять метаданными тонюсенький слой бизнес-логики).

В частности, мне приходилось активно пользоваться атрибутами собственного производства при реализации ABAC (альтернативы RBAC) и при написании API шлюзов. И там и там очень удобно было размечать каждый эндпоинт собственными атрибутами, передавая через них аргументы в более глубокие слои приложения, вызывавшиеся симфонийским kernel.controller лисенером.

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

Получилось в этом посте спонтанно затронуть огромную и интересную тему ABAC. Дам ка я, пожалуй, ссылочку на статью, с которой в эту тему можно начать погружаться.

Ну а если (не дай Бог, но мало ли 🤷‍♂️) у кого-то вызвало замешательство упоминание kernel.controller, то вот вам ссылочка на доку по встроенным симфонийским событиям.

#атрибуты #рефлексия #контроллер
Быть или казаться?

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

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

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

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

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

#собеседования
👍2
Обязательно ли понимание индексов баз данных для карьерного роста разработчика?

Отвечу так: я дошел до позиций уровня senior без этого. Конечно, глядя из сегодняшнего дня на себя, первый раз получившего заветную лычку сеньора, я могу сказать, что я вообще ничего тогда не понимал ни то, что в БД, но и в написании кода. 😁

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

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

Вот такой видосик попался мне в свое время на глаза: https://www.youtube.com/watch?v=RUF3n_EIcy8

В нем автор очень доходчиво объясняет:

- общий принцип работы B-TREE индекса;

- как правильно строить составные индексы (об этом нередко спрашивают на собесах);

- что такое селективность и как она влияет на скорость работы индекса;

- как базово пользоваться EXPLAIN в MySQL.

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

#SQL_индексы #собеседования
👍4
Channel name was changed to «Александр Карпов»
Channel photo updated
Instanceof и принцип подстановки Барбары Лисков.

Наткнулся сегодня в очередной раз на instanceof в чужом коде и попытался вспомнить: был ли у меня хоть один случай на ревью, когда instanceof не нарушал принципа подстановки Барбары Лисков. Похоже, что не было.

Задумался над тем, почему так происходит, а заодно взял из любопытства один из своих типичных проектов и посмотрел, где я сам использую этот оператор. Случаи, которые я нашел в своем коде разберу в следующих постах.
Instanceof и принцип подстановки Барбары Лисков.

Итак, первый случай из моего кода. Написание кастомного Symfony валидатора согласно симфонийской же доке (ссылка на пример кода от Symfony). Что мы здесь имеем? А имеем мы здесь самый частый случай грубого нарушения принципа Лисков, который мне встречается на ревью: сначала указываем в сигнатуре метода определенный тип аргумента и тут же в первой строчке метода заужаем этот тип с помощью instanceof. Конкретно этот случай с Симфони еще чуть более простителен от того, что корень проблемы здесь в том, как спроектированы интерфейсы.

Бывает и более грубый случай, когда сигнатура метода не диктуется разработчику никаким родительским типом (классом или интерфейсом), а он зачем-то все равно указывает широкий тип аргумента метода, а затем в первой же строчке ограничивает его с помощью instanceof. Такую ошибку исправить легко: просто указать более точный тип аргумента в сигнатуре метода и убрать instaceof. А как быть с предыдущем случаем из Symfony, когда проблема навязана нам интерфейсом?

Давайте попробуем разобраться с симфонийским валидатором, и понять, почему он так сделан, и как можно было бы уйти в нем от нарушения принципа Лисков. В рассматриваемом решении мы видим разделение ответственности между двумя классами. Класс Constraint несет в себе метаданные, то есть, с помощью этого класса мы помечаем, какие поля каким образом мы хотим валидировать. Второй класс – ConstraintValidator (буду называть его просто Валидатор) выполняет собственно валидацию. Таким образом созданы две точки расширения: теперь можно произвольно развивать дерево метаданных и дерево валидаторов.

Проблема состоит в том, что по условиям задачи у нас есть четкая связка: один Constraint должен всегда соответствовать одному Валидатору. Лично мне не раз приходилось сталкиваться с такой проблемой при проектировании своих решений. Сначала мы описываем контракты в базовом типе, с одной стороны задавая рамки для будущих реализаций, а с другой – оставляя достаточно свободы «будущему себе» и своим коллегам. В то же время, нам как-то нужно добавить осведомленность о том, что каждый потомок одного супертипа (Валидатора) работает только с ограниченным набором (в данном случае только с одним из) потомков другого супертипа (Constraint).

Казалось бы, уже в самой этой формулировке прописано нарушение принципа Лисков, и нам нужно возвращаться к началу, чтобы по-другому спроектировать наше решение. Но давайте вспомним, как звучит принцип Лисков. Упрощенно он звучит так: если в сигнатуре метода объявлен аргумент определенного типа, то наш метод обязан корректно работать с любым подтипом того типа, который объявлен в сигнатуре. То есть, применительно к нашему примеру, любой Валидатор должен работать с любым Constraint, не ставя приложение раком не приводя к аварийному завершению программы. Есть очень простой и проверенный способ добиться этого, причем в симфонийском валидаторе он реализован, но почему-то не применен.

Класс Constraint содержит метод validatedBy(), который должен возвращать имя класса Валидатора, который работает с этим Constraint. В Симфони этот метод используется в клиентском коде, в самом, так сказать, «головном» валидаторе, который благодаря этому методу сопоставляет списки Constraint и Валидаторов между собой. А instanceof здесь служит эдакой защитой от дурака.

Так вот, чтобы убрать в этом случае instanceof и начать соблюдать принцип Лисков, было бы правильно в самом ConstraintValidator вызывать метод Constraint::validatedBy() и, в случае если нам подсунули не тот Constraint, просто не выполнять валидацию. Таким образом наш валидатор становится способен работать с любым Constraint. Ведь принцип Лисков ничего не говорит о том, как именно наш метод должен обрабатывать любой подтип 🦊.

#SOLID #Liskov #интерфейс_класса
👍1
Стоит ли расстраиваться, получив отказ после техинтервью? Я считаю, нужно радоваться. Давайте разберемся, какие могут быть причины отказа.

1. Специалист, проводивший техинтервью (сеньор, тимлид, техлид, начальник разработки, СТО) решил, что ваших навыков недостаточно.

2. Кто-то в менеджменте решил, что вы не вышли рылом не подходите по софтам, фактам биографии, цвету глаз, возрасту, полу, манере шутить и т. п. и зарубил вашу кандидатуру несмотря на успешное прохождение техинтервью.

3. Вас в целом неплохо оценили, и были бы готовы сделать вам оффер, но на «ваше» место нашелся внутренний (да, так тоже бывает) или внешний более подходящий кандидат.

4. Вас оценили неплохо, но ваши зп ожидания оказались выше их (работодателя) оценки (или возможностей) и с вами решили не торговаться, чтобы «не снизить вашу мотивацию».

5. Вас оценили хорошо, но побоялись, что ваш часовой пояс сильно отличается от часового пояса команды. Живя в UTC +8, я получил за свою практику несколько единиц таких отказов.

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

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

Что касается причины №2, то могу вас заверить со всей уверенностью, что получение такого отказа – это ваша удача. Это все равно что в момент заключения сделки по покупке машины увидеть, что она не заводится, да и арки-то у нее ржавые, как сразу не заметил? Ведь хорошо же, что эти изъяны обнаружились до того, как машина стала вашей, правда? Так же и в случае с потенциальной новой работой: если неадекватный менеджмент проявил себя до того, как вы туда попали, то вам повезло.

Лично мне пару раз случалось попадать в ситуации, когда техинтервью проводит замечательнейший человек: и по части инженерных взглядов во всем мы с ним сходимся, и по софтам он просто душка, и то, как он описывает свое видение организации процессов, заставляет лишь удовлетворенно кивать. И вот ты принимаешь оффер, и интервьюер-душка куда-то исчезает. А ведь была договоренность, что я буду работать напрямую с ним 🤬! И вместо чудесного лида над тобой оказывается не пойми кто. Поэтому. Если неадекватный менеджмент выявился на стадии интервью, то это просто замечательно. Пусть даже он выявился тем, что отказал вам в вакансии по обидному для вас поводу. Хотя, на самом деле, вам в лицо это вряд ли скажут и узнать об отказе по причине №2 из списка выше можно только проявив настойчивость и имея возможность напрямую пообщаться с кем-то внутри компании.

#собеседования
❤‍🔥1👍1
Стоит ли расстраиваться, получив отказ после техинтервью? Продолжение предыдущего поста 👆

Ну и, наконец, причина №1. На самом деле этот пункт можно разделить на несколько подпунктов:

1. Вы согласны с оценкой интервьюера.

2. Интервьюер был в целом адекватный, но вы не согласны с его оценкой.

3. Интервьюер был неадекватный. Например, спрашивал вас, за что отвечает PSR-4, или на лайв-кодинге объявил вам, что ваш код не будет работать, потому что вы «не сделали фабрику», или получив три ответа на свой вопрос, объясняющих тему с разных сторон, объявил вам, что не один из ваших ответов не совпадает с «ответом из гугла».

Первый случай я всегда воспринимаю как возможность научиться новому. Ведь мне выпало знакомство с человеком, который в какой-то области или сразу в нескольких опытнее меня. У такого человека обязательно нужно просить совета, что почитать, где поучиться. Все самые полезные источники, по которым я учился, я получил именно на непройденных мной техинтервью. Иногда удается прямо в процессе интервью попросить интервьюера объяснить правильный ответ на его вопрос, что тоже может оказаться очень ценным. С грустью вынужден констатировать, что таких интервьюеров в моей практике было хорошо, если десятая часть, но они были! И именно им я во многом обязан своим ростом. Так что, получение такого отказа – это тоже повод для радости.

Что касается варианта №3 с неадекватным лидом, проводящим интервью, думаю, вы уже сами все поняли. См. в предыдущем посте про неадекватный менеджмент.

Остается случай, когда интервьюер вроде вел себя адекватно, но в целом вы не сошлись именно по техчасти. Чаще всего это лиды с большим стажем (намеренно пишу «стажем», а не «опытом»), чье мышление давно и прочно легло в определенное русло, покидать которое им крайне некомфортно. Нисколько не осуждая таких людей, я тем не менее, спрашиваю себя: насколько комфортно было бы мне работать с таким лидом?

Резюмируя, могу лишь повторить, что наше эмоциональное начало может понуждать нас расстраиваться в ответ на отказ после техинтервью. Но рациональное начало в нас (мы же все-таки инженеры 😎) может лишь радоваться такому исходу.

#собеседования
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥1
Классы коллекций как альтернатива plural types

Часто приходится объяснять эту несложную тему как в небольших докладиках, делаемых на команду, так и индивидуально.

Поскольку в PHP нет поддержки множественных типов, мы не можем написать такой код:

function foo(SomeClass[] $objects): ResultDTO[]
{
//...
}


А это в свою очередь означает, что мы не можем явно контролировать тип каждого элемента массива, передаваемого в функцию, или возвращаемого из него. Конечно, мы можем написать что-то вроде:

function foo(array $objects): array
{
if (!empty(array_filter($objects, fn ($o) => !$o instanceof SomeClass))) {
throw new \InvalidArgumentException();
}

//...
}


Но при таком подходе мы засоряем код и не решаем проблему контроля типа элементов возвращаемого из функции массива.

И тут нам на помощь приходят классы коллекций:

class UserCollection
{
/** @var User[] */
private array $elements;

public function __construct(User ...$elements)
{
$this->elements = $elements;
}

/**
* @return User[]
*/
public function getElements(): array
{
return $this->elements;
}
}


☘️ За счет спред-оператора в конструкторе гарантируется, что все элементы коллекции будут одного типа.

☘️ Класс коллекции удобно расширять при необходимости методами типа addElement(), deleteElement() и т. п., добавляя синтаксического сахара и очищая клиентский код.

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

☘️ Класс коллекции можно научить мерджиться, интерсектиться, выделять подмножества и т. д.

Лично я очень обширно использую коллекции в своем коде, самые толстые из которых включают сотни строк технической логики, разгружая тем самым клиентский код. И вам рекомендую!)

#чистый_код
👍4🔥32
Instanceof и принцип подстановки Барбары Лисков

Это второй пост, в котором я разбираю случаи применения instanceof в своем коде на предмет нарушения принципа Лисков.

Первый пост

Нашел-таки у себя сразу два случая нарушения принципа Лисков оператором instanceof, и сегодня разберу первый из них. Имеем вот такой метод в контроллере:

public function obligationCallback(ObligationResponse ...$obligations): void
{
$this->filterObligations = [];
foreach ($obligations as $obligation) {
if (!$obligation instanceof FilterObligationResponse) {
throw new UnhandledObligationException(self::class . ' . can not handle ' . $obligation::class);
}

$this->filterObligations[] = $obligation;
}
}


Этот код - часть системы ABAC (альтернативы RBAC), о которой я уже писал в предыдущих постах. Коротко, зачем этот код нужен:

1️⃣ В обработчике kernel.controller_arguments (то есть, до вызова нашего контроллера) делается запрос в микросервис ABAC для принятия решения, имеет ли право юзер совершить запрашиваемое действие.

2️⃣ Микросервис ABAC помимо решения о доступе может прислать такую штуку, как обязательство (Obligation). Если такое случилось, то наше приложение обязано выполнить это обязательство, либо отказать в доступе.

3️⃣ За выполнение этого самого обязательства и отвечает метод obligationCallback() из примера выше.

Проблема состоит в том, что при проектировании системы ABAC я, естественно, заложил абстрактный класс ObligationResponse как точку расширения для создания в будущем любых обязательств.

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

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

По сути, в такой ситуации вопрос состоит лишь в том, как именно должно падать наше приложение, ведь не упасть оно не может. Сейчас оно падает с нарушением принципа Лисков 😂. Чтобы это поправить, достаточно в сигнатуре метода obligationCallback() из примера выше сузить тип аргумента до FilterObligationResponse (к счастью, сигнатуру нам здесь не диктует родительский класс или интерфейс).

Далее, в клиентском коде, вызов obligationCallback() следует обернуть в try-catch на отлов \TypeError и логировать ситуацию с явным указанием того, что микросервис ABAC прислал обязательство, которое контроллер не может обработать.

Вы можете спросить: а что, блин, изменилось и на хрен этот принцип Лисков нужен? Изменяется в такой ситуации то, что вы (и я в данном случае тоже, ведь у себя же нашел косяк 😱) набиваете руку и учитесь соблюдать SOLID без дополнительных энергозатрат, на автомате. А вот о пользе принципа Лисков я порассуждаю уже в другом посте.😄

#SOLID #Liskov #интерфейс_класса
Please open Telegram to view this post
VIEW IN TELEGRAM
1