Статический анализ. Psalm.
Если в 2021 вы еще не используете статические анализаторы, то срочно, нет, немедленно начинайте это делать :) Они решают просто миллион проблем, позволяют выполнять и контролировать десятки вопросов, но обо всём по порядку.
Что делают эти ваши анализаторы?
Анализируют текст программы и проверяют её на типичные (и не очень ошибки). При этом сам код не запускается (!). Примеры проверок, которые при этом выполняются:
• код не содержит синтаксических ошибок
• все классы, методы, функции, константы существуют
• переменные существуют
• в PHPDoc подсказки соответствуют действительности.
• проверяются неиспользуемые аргументы, переменные и свойства
• проверка типов данных
Круто, с чего начать использование Psalm?
После установки с помощью composer достаточно запустить команду
И вот момент истины, на нашем существующем проекте мы запускаем
Заботливые разработчики psalm сделали команду
Чтобы обновить состояние baseline достаточно выполнить:
#PHP #psalm #junior #source
Если в 2021 вы еще не используете статические анализаторы, то срочно, нет, немедленно начинайте это делать :) Они решают просто миллион проблем, позволяют выполнять и контролировать десятки вопросов, но обо всём по порядку.
Что делают эти ваши анализаторы?
Анализируют текст программы и проверяют её на типичные (и не очень ошибки). При этом сам код не запускается (!). Примеры проверок, которые при этом выполняются:
• код не содержит синтаксических ошибок
• все классы, методы, функции, константы существуют
• переменные существуют
• в PHPDoc подсказки соответствуют действительности.
• проверяются неиспользуемые аргументы, переменные и свойства
• проверка типов данных
Круто, с чего начать использование Psalm?
После установки с помощью composer достаточно запустить команду
./vendor/bin/psalm --init
Которая cгенерирует следующий файл. И вот момент истины, на нашем существующем проекте мы запускаем
./vendor/bin/psalm
И видим 1125 ошибок, которые очень лень править. У многих на этом заканчивается внедрение статического анализатора :) Но выход есть.Заботливые разработчики psalm сделали команду
./vendor/bin/psalm --set-baseline=your-baseline.xml
Которая запомнит текущие ошибки в вашем проекте и запишет их в указанный файл. Дальше вы можете спокойно запускать проверку и контролировать новый код, который пишете в вашем проекте. Чтобы обновить состояние baseline достаточно выполнить:
./vendor/bin/psalm --update-baseline
Конечно не стоит забывать и о старых ошибках, которые теперь можно постепенно исправлять не мешая основной разработке.#PHP #psalm #junior #source
👍3
Релиз PhpStorm 2021.1
8 апреля состоялся долгожданный релиз версии 2021.1. В ребята в очередной раз порадовали большим набором крутых фич:
• Куча подсказок и автозамен для упрощения и повышения читабельности кода
• Code with me - для совместной удаленной разработки и парного программирования (плагин, правда, был доступен ранее)
• Подлатали HTTP-Клиент
• Добавили превью PHP и HTML-файлов прямо в редакторе(!)
С полным перечнем изменений можно ознакомиться здесь или посмотреть видос.
#PHP #tools #junior
8 апреля состоялся долгожданный релиз версии 2021.1. В ребята в очередной раз порадовали большим набором крутых фич:
• Куча подсказок и автозамен для упрощения и повышения читабельности кода
• Code with me - для совместной удаленной разработки и парного программирования (плагин, правда, был доступен ранее)
• Подлатали HTTP-Клиент
• Добавили превью PHP и HTML-файлов прямо в редакторе(!)
С полным перечнем изменений можно ознакомиться здесь или посмотреть видос.
#PHP #tools #junior
Дженерики (Generics, templates) уже сегодня.
Наверное один из самых старых обсуждаемых и популярных RFC в PHP. На данный момент на уровне языка реализовать их трудно, как бы сильно разработчикам не хотелось получить этот инструмент.
Однако, используя Psalm (или PHPStan) в ваших проектах, вы можете делать это уже сейчас.
В чем смысл их использования?
Один из частых кейсов, когда нам нужно создать коллекцию объектов. Но создавая её таким образом, мы не можем гарантировать, что внутри все объекты будут типа
1. Забивает болт.
2. Внутри всех
3. Добавляет явную проверку
Для таких случаев у Psalm есть @template. Вот так может выглядеть ваша коллекция.
Теперь каждая коллекция ожидает аргумент своего типа. Это избавляет вас от лишних проверок, необходимости явно указывать
В случае несоблюдения этих контрактов, статический анализатор выкинет ERROR. Если вы пользуетесь PHPStorm, то он обязательно вам подскажет об ошибке и без запуска psalm.
Короче, этот инструмент отлично работает и давно используется многими разработчиками, так что не нужно ждать, а можно использовать его уже сейчас ;)
#PHP #middle #psalm #source
Наверное один из самых старых обсуждаемых и популярных RFC в PHP. На данный момент на уровне языка реализовать их трудно, как бы сильно разработчикам не хотелось получить этот инструмент.
Однако, используя Psalm (или PHPStan) в ваших проектах, вы можете делать это уже сейчас.
В чем смысл их использования?
Один из частых кейсов, когда нам нужно создать коллекцию объектов. Но создавая её таким образом, мы не можем гарантировать, что внутри все объекты будут типа
User. Что делает разработчик в таком случае?1. Забивает болт.
2. Внутри всех
foreach приходится добавлять явную аннотацию /** @var User $user */ и нужно следить не только за типами, но и за аннотациями.3. Добавляет явную проверку
$user instanceof User.Для таких случаев у Psalm есть @template. Вот так может выглядеть ваша коллекция.
@template T — всё равно что согласно RFC написать Collection<T>. Это означает, что любая ссылка на докблок T, должна рассматриваться как параметр этого типа. @psalm-param class-string<T> $type — указываем, что параметры должны быть строкой класса определенного типа.Теперь каждая коллекция ожидает аргумент своего типа. Это избавляет вас от лишних проверок, необходимости явно указывать
@var или создавать классы коллекций для каждого типа объекта.В случае несоблюдения этих контрактов, статический анализатор выкинет ERROR. Если вы пользуетесь PHPStorm, то он обязательно вам подскажет об ошибке и без запуска psalm.
Короче, этот инструмент отлично работает и давно используется многими разработчиками, так что не нужно ждать, а можно использовать его уже сейчас ;)
#PHP #middle #psalm #source
Object Mother pattern
Материнский объект — это класс, используемый при тестировании, который помогает создавать примеры объектов, которые вы используете для тестирования (М. Фаулер, 2006)
Этот шаблон помогает создавать определенные объекты, которые можно повторно использовать в нескольких тестах, что позволяет делать ваш код более читабельным.
По факту это броское название для фабрики, которая не только возвращает объект, но и задаёт его состояние.
❗️Конечно, к его созданию использованию нужно подходить с умом. Помните, что создавая методы под каждое состояние, можно очень сильно раздуть класс и превратить его в так называемый
Не смотря на определенные минусы данного подхода он может дать быстрый буст при внедрении тестов в ваш проект, или быстро описать давно устоявшиеся сущности.
❔Есть ли более крутая альтернатива? Да —
#PHP #testing #unit #source #junior
Материнский объект — это класс, используемый при тестировании, который помогает создавать примеры объектов, которые вы используете для тестирования (М. Фаулер, 2006)
Этот шаблон помогает создавать определенные объекты, которые можно повторно использовать в нескольких тестах, что позволяет делать ваш код более читабельным.
По факту это броское название для фабрики, которая не только возвращает объект, но и задаёт его состояние.
❗️Конечно, к его созданию использованию нужно подходить с умом. Помните, что создавая методы под каждое состояние, можно очень сильно раздуть класс и превратить его в так называемый
GodClass. Его будет сложно поддерживать и придётся страдать, если изменится поведение основной сущности.Не смотря на определенные минусы данного подхода он может дать быстрый буст при внедрении тестов в ваш проект, или быстро описать давно устоявшиеся сущности.
❔Есть ли более крутая альтернатива? Да —
Test Data Builders (пост будет чуть позже), но на старте она будет требовать больше усилий и времени. #PHP #testing #unit #source #junior
Оптимизация LIMIT / OFFSET
Также рассмотрим тему в рамках синхронизации (т.к. проблема стара и бояниста как этот мир) и в преддверии статейки про Cursor API.
❓Что плохого в LIMIT и OFFSET?
На малых объёмах данных никто и ничего не почувствует. Но долгоживущие проекты, в которые постоянно складываются данные рано или поздно сталкиваются с тем, что всё больше запросов появляется в slow.log, а запрос обычной постраничной пагинации отрабатывает 3-5 секунд(!).
Всё дело в том, что выполняя запрос типа
Рисунок наглядно изображает такую ситуацию. База читает первые 10 записей, после этого вставляется новая запись, которая смещает все прочитанные записи на 1. Затем база берет новую страницу из 10 следующих записей и начинает не с 11-й, как должна, а с 10-й, дублируя эту запись. Есть и другие аномалии, связанные с использованием этого выражения, но эта — самая распространенная.
⁉️О Боже, что же делать?!
Вместо комбинации OFFSET и LIMIT стоит использовать конструкцию
Конечно, у этого подхода могут есть свои ограничения: проблемно сортировать, нет нормальной возможности читать страницы непоследовательно и т.д. Однако он отлично подойдет для бесконечного скроллинга, или если вам нужно отдать большие объемы данных по API.
#mysql #junior #source
Также рассмотрим тему в рамках синхронизации (т.к. проблема стара и бояниста как этот мир) и в преддверии статейки про Cursor API.
❓Что плохого в LIMIT и OFFSET?
На малых объёмах данных никто и ничего не почувствует. Но долгоживущие проекты, в которые постоянно складываются данные рано или поздно сталкиваются с тем, что всё больше запросов появляется в slow.log, а запрос обычной постраничной пагинации отрабатывает 3-5 секунд(!).
Всё дело в том, что выполняя запрос типа
mysql> SELECT * FROM table_name ORDER BY id LIMIT 10 OFFSET 8000001;
Mysql сначала пройдется по первым 8000001 записям (которые нам не нужны) и только потом выберет нужные 10. И это еще не всё! Если между чтением двух страниц данных с диска другая операция вставит новую запись, что произойдет в этом случае?Рисунок наглядно изображает такую ситуацию. База читает первые 10 записей, после этого вставляется новая запись, которая смещает все прочитанные записи на 1. Затем база берет новую страницу из 10 следующих записей и начинает не с 11-й, как должна, а с 10-й, дублируя эту запись. Есть и другие аномалии, связанные с использованием этого выражения, но эта — самая распространенная.
⁉️О Боже, что же делать?!
Вместо комбинации OFFSET и LIMIT стоит использовать конструкцию
mysql> SELECT * FROM table_name WHERE id > 8000000 LIMIT 10;
Вот что мы можем выиграть по времени. Конечно, у этого подхода могут есть свои ограничения: проблемно сортировать, нет нормальной возможности читать страницы непоследовательно и т.д. Однако он отлично подойдет для бесконечного скроллинга, или если вам нужно отдать большие объемы данных по API.
#mysql #junior #source
👍1
Принцип подстановки Барбары Лисков
❓Почему у многих возникают проблемы с этим принципом? Если взять не заумное , а более простое объяснение, то оно звучит так:
«Наследующий класс должен дополнять, а не замещать поведение базового класса».
Звучит логично и понятно, расходимся. но блин, как этого добиться? Почему-то многие просто пропускают мимо ушей следующие строки, которые как раз отлично объясняют что нужно делать.
Рассмотрим выражение "Предусловия не могут быть усилены в подклассе"
❗️Другими словами дочерние классы не должны создавать больше предусловий, чем это определено в базовом классе, для выполнения некоторого бизнесового поведения. Вот несложный пример.
❌ Добавление второго условия как раз является усилением. Так делать не надо!
• Этот пример показывает, как расширение допускается, потому что метод Bar->process() принимает все типы параметров, которые принимает родительский метод.
• В этом примере дочерний класс может принимать более широкий спектр объектов, которые могут быть наследованы от
Таким образом, мы не добавляем дополнительных проверок, не делаем условия жестче и наш дочерний класс уже ведёт себя более предсказуемо.
В следующих постах мы также рассмотрим постуловия и ковариантность ;)
#PHP #middle #source
❓Почему у многих возникают проблемы с этим принципом? Если взять не заумное , а более простое объяснение, то оно звучит так:
«Наследующий класс должен дополнять, а не замещать поведение базового класса».
Звучит логично и понятно, расходимся. но блин, как этого добиться? Почему-то многие просто пропускают мимо ушей следующие строки, которые как раз отлично объясняют что нужно делать.
Рассмотрим выражение "Предусловия не могут быть усилены в подклассе"
❗️Другими словами дочерние классы не должны создавать больше предусловий, чем это определено в базовом классе, для выполнения некоторого бизнесового поведения. Вот несложный пример.
❌ Добавление второго условия как раз является усилением. Так делать не надо!
Контравариантность также можно отнести к данному приниципу. Она касается параметров функции, которые может ожидать подкласс. Подкласс может увеличить свой диапазон параметров, но он должен принять все параметры, которые принимает родительский. • Этот пример показывает, как расширение допускается, потому что метод Bar->process() принимает все типы параметров, которые принимает родительский метод.
• В этом примере дочерний класс может принимать более широкий спектр объектов, которые могут быть наследованы от
Money, не только Dollars.Таким образом, мы не добавляем дополнительных проверок, не делаем условия жестче и наш дочерний класс уже ведёт себя более предсказуемо.
В следующих постах мы также рассмотрим постуловия и ковариантность ;)
#PHP #middle #source
Принцип подстановки Барбары Лисков (part 2 — постусловия, ковариантность)
И сразу продолжение предыдущего поста — "Постусловия не могут быть ослаблены в подклассе".
То есть подклассы должны выполнять все постусловия, которые определены в базовом классе. Постусловия проверяют состояние возвращаемого объекта на выходе из функции. Вот такой пример.
❌Условное выражение проверяющее результат является постусловием в базовом классе, а в наследнике его уже нет. Не делай так!
Сюда-же можно отнести и ковариантность, которая позволяет объявлять в методе дочернего класса типом возвращаемого значения подтип того типа (ШО?!), который возвращает родительский метод.
Короче, в данном примере, в методе
❗️Таким образом в дочернем классе мы сузили возвращаемое значение. Не ослабили. А усилили :)
Ставь 🍺, если понял о чем идёт речь или 🐒, если всё это сложно и "давай шото попроще уже".
И сразу продолжение предыдущего поста — "Постусловия не могут быть ослаблены в подклассе".
То есть подклассы должны выполнять все постусловия, которые определены в базовом классе. Постусловия проверяют состояние возвращаемого объекта на выходе из функции. Вот такой пример.
❌Условное выражение проверяющее результат является постусловием в базовом классе, а в наследнике его уже нет. Не делай так!
Сюда-же можно отнести и ковариантность, которая позволяет объявлять в методе дочернего класса типом возвращаемого значения подтип того типа (ШО?!), который возвращает родительский метод.
Короче, в данном примере, в методе
render() дочернего класса, JpgImage объявлен типом возвращаемого значения, который в свою очередь является подтипом Image, который возвращает метод родительского класса Renderer. ❗️Таким образом в дочернем классе мы сузили возвращаемое значение. Не ослабили. А усилили :)
Ставь 🍺, если понял о чем идёт речь или 🐒, если всё это сложно и "давай шото попроще уже".
Канал не быстро, но всё таки растёт ;)
Чтобы я понимал какие темы стоит сюда постить и насколько подробно описывать, проголосуйте плз.
Какой у вас грейд?
Чтобы я понимал какие темы стоит сюда постить и насколько подробно описывать, проголосуйте плз.
Какой у вас грейд?
Anonymous Poll
20%
Trainee / Intern / Junioir 🍺
43%
Middle 🍺🍺
30%
Senior / TL / Arhitect 🍺🍺🍺
7%
Кто здесь? 🐒
Test data builders
В этом посте мы рассматривали паттерн
Как уже было сказано ранее, если тестируемый объект может иметь много разных состояний, то Object Mother сильно раздувается, код внутри методов фабрики будет повторяться, что выглядит не сильно лаконично. Здесь на помощь нам приходит builder. Не используя паттерн у нас получится примерно такой код.
❗️Вроде ничего страшного, но для сравнения код с использованием builder'a.
💡 Код более краток, читабелен и чёток относительно целей теста. Также они носят хороший описательный характер, например если бы мы добавили
Сам подход более гибкий, помогает избежать копипасты, которой в тестах обычно и так хватает. Вы также можете комбинировать оба паттерна (включая
#PHP #testing #junior #source
В этом посте мы рассматривали паттерн
Object Mother. Там я обещал, что расскажу об альтернативном паттерне, который требует больше усилий, но имеет меньше проблем на выходе — Test data builder.Как уже было сказано ранее, если тестируемый объект может иметь много разных состояний, то Object Mother сильно раздувается, код внутри методов фабрики будет повторяться, что выглядит не сильно лаконично. Здесь на помощь нам приходит builder. Не используя паттерн у нас получится примерно такой код.
❗️Вроде ничего страшного, но для сравнения код с использованием builder'a.
💡 Код более краток, читабелен и чёток относительно целей теста. Также они носят хороший описательный характер, например если бы мы добавили
AddressBuilder, то вместо магического числа 21, которое передается в конструктор, написали бы метод ->withHouseNumber(21).Сам подход более гибкий, помогает избежать копипасты, которой в тестах обычно и так хватает. Вы также можете комбинировать оба паттерна (включая
Object Mother) там, где это уместно, таким образом балансируя между гибкостью и количеством затраченного времени.#PHP #testing #junior #source
👍1
Принцип подстановки Барбары Лисков (part 3 — правило истории, инвариантность)
❗️Все условия базового класса - также должны быть сохранены и в подклассе.
Инварианты — это некоторые условия, которые остаются истинными на протяжении всей жизни объекта. Как правило, инварианты передают внутреннее состояние объекта. Например типы свойств базового класса не должны изменяться в дочернем.
❗️Подкласс не должен создавать новых мутаторов свойств базового класса.
Если базовый класс не предусматривал методов для изменения определенных в нем свойств, подтип этого класса так же не должен создавать таких методов. Иными словами, неизменяемые данные базового класса не должны быть изменяемыми в подклассе.
❌ С точки зрения класса
#PHP #middle #source
❗️Все условия базового класса - также должны быть сохранены и в подклассе.
Инварианты — это некоторые условия, которые остаются истинными на протяжении всей жизни объекта. Как правило, инварианты передают внутреннее состояние объекта. Например типы свойств базового класса не должны изменяться в дочернем.
Здесь также стоит упомянуть исторические ограничения («правило истории»):
class Wallet
{
protected float $amount;
// тип данного свойства не должен изменяться в подклассе
}
❗️Подкласс не должен создавать новых мутаторов свойств базового класса.
Если базовый класс не предусматривал методов для изменения определенных в нем свойств, подтип этого класса так же не должен создавать таких методов. Иными словами, неизменяемые данные базового класса не должны быть изменяемыми в подклассе.
❌ С точки зрения класса
Deposit поле не может быть меньше нуля. А вот производный класс VipDeposit, добавляет метод для изменения свойства account, поэтому инвариант класса Deposit нарушается. Такого поведения следует избегать. В таком случае стоит рассмотреть добавление мутатора в базовый класс.#PHP #middle #source
Время, как контекст
Простая и интересная мысль от Matthias Noback:
👉 Рассматривайте время как контекст. Вам следует подумать об определении текущего времени один раз на уровне инфраструктуры (например, в контроллере) и передаче его в качестве значения любым другим объектам, которым нужна эта информация.
А ведь действительно, это поможет сохранить наши функции "чистыми". Поскольку не будет системного вызова для получения даты / времени (в отличие от того, когда мы просто вызываем
#PHP #junior #source
Простая и интересная мысль от Matthias Noback:
👉 Рассматривайте время как контекст. Вам следует подумать об определении текущего времени один раз на уровне инфраструктуры (например, в контроллере) и передаче его в качестве значения любым другим объектам, которым нужна эта информация.
А ведь действительно, это поможет сохранить наши функции "чистыми". Поскольку не будет системного вызова для получения даты / времени (в отличие от того, когда мы просто вызываем
new DateTime ('now') внутри функций), то это позволит нам без проблем писать тесты и mock'aть подобные кейсы.#PHP #junior #source
Matthias Noback
Software Development & Design
Discovering best practices in PHP & Fortran code
Простой способ готовить коллекции
Если вы используете итерируемые объекты (например создаете коллекции) в PHP, то вот маленький лайфхак.
❗️Вместо того, чтобы самостоятельно реализовывать все методы интерфейса
Сложно объяснил, но на примере будет проще ;)
#PHP #junior #advice #source
Если вы используете итерируемые объекты (например создаете коллекции) в PHP, то вот маленький лайфхак.
❗️Вместо того, чтобы самостоятельно реализовывать все методы интерфейса
Iterator (next(), current(), key() и т. д.), вы можете реализовать интерфейс IteratorAggregate с одним единственным методом getIterator(), результатом которого вернуть экземпляр ArrayIterator на основе вашего внутреннего массива.Сложно объяснил, но на примере будет проще ;)
#PHP #junior #advice #source
Массивы (часть 1, использование массивов как списков)
Как бы мы не хотели использовать только объекты, это удобно далеко не всегда, да и часто может являться оверинженирингом. На помощь приходят любимые массивы. Вот некоторые рекомендации, которые могут помочь при использовании массивов в виде списков:
❗️Все элементы массива должны быть одного типа
Это касается как использования объектов внутри списков, так и простых типов (int, string и т.д.).
❗️Старайтесь не использовать индекс вашего списка.
Не стоит полагаться на индексы, которые автоматически генерирует PHP или использовать их напрямую. Вам должно быть достаточно того, что массив итерируемый (
❗️Вместо удаления элементов используйте фильтр.
Это правило вытекает из предыдущего. Вы можете удалить элементы из списка по их индексу (
Эти правила помогут вам избавиться от лишних
#php #array #junior #source
Как бы мы не хотели использовать только объекты, это удобно далеко не всегда, да и часто может являться оверинженирингом. На помощь приходят любимые массивы. Вот некоторые рекомендации, которые могут помочь при использовании массивов в виде списков:
❗️Все элементы массива должны быть одного типа
@var array <TypeOfElement>. Это касается как использования объектов внутри списков, так и простых типов (int, string и т.д.).
❗️Старайтесь не использовать индекс вашего списка.
Не стоит полагаться на индексы, которые автоматически генерирует PHP или использовать их напрямую. Вам должно быть достаточно того, что массив итерируемый (
foreach) и его кол-во элементов можно посчитать (count()). Следовательно нужно стараться избегать конструкции for.❗️Вместо удаления элементов используйте фильтр.
Это правило вытекает из предыдущего. Вы можете удалить элементы из списка по их индексу (
unset()), но вместо этого лучше использовать array_filter() для создания нового списка без нежелательных элементов. Также не нужно использовать флаги, которые позволяют внутри array_filter() работать с индексами.Эти правила помогут вам избавиться от лишних
if, повысят читабельность и предсказуемость, а что нам еще надо то? :)#php #array #junior #source
Где определять ID сущности (entity)?
Здесь мы опустим часть, в которой мы сравниваем автоинкременты из БД и генерируемые
Мы знаем 2 правила:
1. Сущности (Entity) должны иметь идентификатор.
2. Объекты должны создаваться валидными.
❌ Исходя из этого вроде логично добавить генерацию
❌ Отлично, выносим за пределы, на уровень Application, выглядит неплохо. Но есть ощущение, что что-то не так. Код будет повторяться, к тому же лежит он здесь как-то нелогично.
📌 Чтобы не разбрасывать по всему проекту код самой генерации - его можно вынести в репозиторий сущности.
Сразу получим несколько преимуществ:
1. Концептуально логично: Репозиторий управляет сущностями и их id.
2. В случае необходимости - легко подменить реализацию т.к. код генерации в одном месте.
3. Можете даже использовать инкрементные id, если БД поддреживает последовательности (sequences, напр. postgresql).
4. Всё на своих местах, интерфейс и сущность в доменном уровне, uuid в инфраструктуре, как и полагается зависимым от даты / времени и случайных данных строкам.
#php #middle #advice #source
Здесь мы опустим часть, в которой мы сравниваем автоинкременты из БД и генерируемые
id (uuid) и сразу перейдем ко вторым. Если останутся вопросы, то обязательно пробежимся и по теме сравнения.Мы знаем 2 правила:
1. Сущности (Entity) должны иметь идентификатор.
2. Объекты должны создаваться валидными.
❌ Исходя из этого вроде логично добавить генерацию
uuid в конструктор. Но создание uuid основывается на текущей дате / времени и ранее сгенерированных случайных данных, а значит, нам не стоит относить его к уровню домена. Плюс ко всему сущность должна знать только о себе и не должна смотреть за пределы своих границ, чтобы узнать, действительно ли id уникален (а это одна из распространенных проблем). Получается этот процесс должен быть вне сущности.❌ Отлично, выносим за пределы, на уровень Application, выглядит неплохо. Но есть ощущение, что что-то не так. Код будет повторяться, к тому же лежит он здесь как-то нелогично.
📌 Чтобы не разбрасывать по всему проекту код самой генерации - его можно вынести в репозиторий сущности.
Сразу получим несколько преимуществ:
1. Концептуально логично: Репозиторий управляет сущностями и их id.
2. В случае необходимости - легко подменить реализацию т.к. код генерации в одном месте.
3. Можете даже использовать инкрементные id, если БД поддреживает последовательности (sequences, напр. postgresql).
4. Всё на своих местах, интерфейс и сущность в доменном уровне, uuid в инфраструктуре, как и полагается зависимым от даты / времени и случайных данных строкам.
#php #middle #advice #source
Когда следует использовать коллекции?
В этой статье мы говорили о том как быстро приготовить коллекцию, но стоит разобраться в каких именно случаях она нужна. Безусловно, их использование это крутой подход, но в то же время это отнимает больше времени, ведь вам нужно написать больше кода, покрыть его тестами, заниматься его отладкой и т.д.
Поэтому стоит соблюдать некоторый баланс, ведь часто использования простого массива с некоторыми аннотациями вполне достаточно для решения той или иной задачи.
Как понять, что лучше всё таки использовать коллекцию, а не массив?
📌 Если у вас есть дублирующаяся логика, использующая этот массив. Например вы видите, что в клиентском коде, в нескольких местах у вас есть повторяющаяся логика, которая фильтрует/сокращает/выбирает и т.д. данные массива, вы можете вынести её в метод класса коллекции. Плюс данного подхода также в том, что вы даете осознанное имя определенной логике, которую прячете в метод, что сильно повышает читабельность кода.
📌 Когда какой-то объект работает с массивом, перебирает его, извлекает какие-то данные, что-то с ними делает, то объект становится сильно связанным с этим массивом и изменение данных в массиве легко может нарушить работу клиента. Таким образом следует вынести наш массив в коллекцию, дать коллекции выполнить необходимые вычисления внутри и предоставить объекту готовый результат, таким образом снизив связанность.
#PHP #junior #source
В этой статье мы говорили о том как быстро приготовить коллекцию, но стоит разобраться в каких именно случаях она нужна. Безусловно, их использование это крутой подход, но в то же время это отнимает больше времени, ведь вам нужно написать больше кода, покрыть его тестами, заниматься его отладкой и т.д.
Поэтому стоит соблюдать некоторый баланс, ведь часто использования простого массива с некоторыми аннотациями вполне достаточно для решения той или иной задачи.
Как понять, что лучше всё таки использовать коллекцию, а не массив?
📌 Если у вас есть дублирующаяся логика, использующая этот массив. Например вы видите, что в клиентском коде, в нескольких местах у вас есть повторяющаяся логика, которая фильтрует/сокращает/выбирает и т.д. данные массива, вы можете вынести её в метод класса коллекции. Плюс данного подхода также в том, что вы даете осознанное имя определенной логике, которую прячете в метод, что сильно повышает читабельность кода.
📌 Когда какой-то объект работает с массивом, перебирает его, извлекает какие-то данные, что-то с ними делает, то объект становится сильно связанным с этим массивом и изменение данных в массиве легко может нарушить работу клиента. Таким образом следует вынести наш массив в коллекцию, дать коллекции выполнить необходимые вычисления внутри и предоставить объекту готовый результат, таким образом снизив связанность.
#PHP #junior #source
Как сохранять Value Objects (VO) в Doctrine?
📌 Первое, что приходит на ум — использовать
Этот подход может показаться странным, но если присмотреться — он вполне оптимальный, ведь для внешнего мира объект выглядит идеально.
📌 Вариант чуть сложнее - использовать DBAL Types, для этого руками для такого типа нужно описать 2 метода:
📌 Embeddables
Данный подход, позволяет встраивать класс, не являющийся сущностью, внутрь сущности. Кажется она просто предназначена для хранения объектов-значений!
❗️ Конечно, существует еще такой способ, как сериализация. Как и в первом варианте с простыми типами, пока это внутри модели, это не большая проблема для всей бизнесовой логики. Недостатком является то, что вы не можете эффективно запрашивать эти данные. То есть вы можете сериализовать значения в свою базу данных, но только тогда, когда на 100% уверенны, что никогда не захотите выполнять запросы к ним или изменять данные.
🤦♀️ С одной стороны последний метод с использованием Embeddables выглядит очень громоздким, с другой первый метод хоть и простой, но мы внутри сущности добавляем поведение лишь для того, чтобы сохранить state (не криминально, но вызывает двоякие чувства). Кто-то обязательно скажет насчет аннотаций в сущности, которые "смешивают слои" (хотя я считаю, что т.к. аннотации это по сути комментарии, то сущность от этого не страдает), что стоит хранить подобное описание в XML или YAML.
👍 Это нормально использовать симбиоз вышеописанных подходов. Всё зависит от задачи, времени на разработку и ваших договоренностей в команде.
#PHP #middle #source
📌 Первое, что приходит на ум — использовать
VO только на границах (входе и выходе), а внутри самой Entity использовать простые типы.Этот подход может показаться странным, но если присмотреться — он вполне оптимальный, ведь для внешнего мира объект выглядит идеально.
📌 Вариант чуть сложнее - использовать DBAL Types, для этого руками для такого типа нужно описать 2 метода:
convertToDatabaseValue($value, AbstractPlatform $platform)Этот метод отлично подходит, если ваш
convertToPHPValue($value, AbstractPlatform $platform)
VO хранит в себе одно значение, которое вам нужно вписать в одну колонку. Но что, если их несколько?📌 Embeddables
Данный подход, позволяет встраивать класс, не являющийся сущностью, внутрь сущности. Кажется она просто предназначена для хранения объектов-значений!
❗️ Конечно, существует еще такой способ, как сериализация. Как и в первом варианте с простыми типами, пока это внутри модели, это не большая проблема для всей бизнесовой логики. Недостатком является то, что вы не можете эффективно запрашивать эти данные. То есть вы можете сериализовать значения в свою базу данных, но только тогда, когда на 100% уверенны, что никогда не захотите выполнять запросы к ним или изменять данные.
🤦♀️ С одной стороны последний метод с использованием Embeddables выглядит очень громоздким, с другой первый метод хоть и простой, но мы внутри сущности добавляем поведение лишь для того, чтобы сохранить state (не криминально, но вызывает двоякие чувства). Кто-то обязательно скажет насчет аннотаций в сущности, которые "смешивают слои" (хотя я считаю, что т.к. аннотации это по сути комментарии, то сущность от этого не страдает), что стоит хранить подобное описание в XML или YAML.
👍 Это нормально использовать симбиоз вышеописанных подходов. Всё зависит от задачи, времени на разработку и ваших договоренностей в команде.
#PHP #middle #source
Как хранить UUID в MySQL?
К сожалению реалии таковы, что просто написать "никак" не получится, т.к. MySQL всё еще занимает лидирующие позиции по использованию в веб разработке, не смотря на все его недостатки.
🙈 Многие по-умолчанию сохраняют
Хранить как BINARY.
🔨 Благо с выходом 8й версии MySQL часть танцев с бубном была перенесена в коробку и
❗️Для этого можно использовать
📊 Ну и самое интересное — сравнение!
📌 Выходит, что при малом кол-ве записей, наш оптимизированный
Безусловно, генерируемые id — довольно удобный инструмент, но всегда следует хорошо спроектировать решение в голове, прежде чем внедрить.
#PHP #MySQL #middle #source
К сожалению реалии таковы, что просто написать "никак" не получится, т.к. MySQL всё еще занимает лидирующие позиции по использованию в веб разработке, не смотря на все его недостатки.
🙈 Многие по-умолчанию сохраняют
UUID в CHAR(36) и не переживают по этому поводу. Действительно, если кол-во записей в вашей таблице < 50к, то вы скорее всего не заметите никаких проблем. Но что если в какой-то из таблиц их больше полумиллиона?Хранить как BINARY.
🔨 Благо с выходом 8й версии MySQL часть танцев с бубном была перенесена в коробку и
UUID_TO_BIN / BIN_TO_UUID уже делают всё за тебя. Они производят сжатие 32 символов (36 или более с разделителями) до 16-битного формата или обратно до формата, который снова можно прочесть.INSERT INTO t VALUES(UUID_TO_BIN(UUID()));👍 Мы уже получим буст, и очень неплохой, т.к. MySQL отлично индексирует
SELECT BIN_TO_UUID(id) FROM t;
BINARY, даже лучше, чем привычный автоинкрементный INT. И всё бы ничего, если бы не кластерный индекс, который как раз и используется у PRIMARY KEY. Это значит, что для его оптимизации мы должны иметь и упорядоченные UUID в базе. Но как это сделать?❗️Для этого можно использовать
UUID первой версии, в которой содержатся биты связанные со временем. А во время использования UUID_TO_BIN / BIN_TO_UUID использовать второй (необязательный, логический) аргумент:INSERT INTO t VALUES (UUID_TO_BIN (UUID (), true));Именно он переупорядочит биты, связанные со временем, так, чтобы последовательные сгенерированные значения были упорядочены и в индексе.
📊 Ну и самое интересное — сравнение!
📌 Выходит, что при малом кол-ве записей, наш оптимизированный
UUID работает медленнее остальных, при этом при 500к он обгоняет по показателям INT. И здесь возникает совсем другая история, а именно кол-во потребляемой памяти для большого кол-ва записей :) Безусловно, генерируемые id — довольно удобный инструмент, но всегда следует хорошо спроектировать решение в голове, прежде чем внедрить.
#PHP #MySQL #middle #source
Сегодня спешу вас порадовать первым гостевым постом (не рекламой). Надеюсь, что этот эксперимент будет успешным и время от времени здесь будут публиковаться и интересные материалы от подписчиков.
Unit testing (предисловие, part - 0)
❗️ Основная цель юнит тестирования — обеспечение стабильного роста проекта. Не быстрого, не сверхнадёжного, а стабильного.
В начале жизни продукта, писать его очень легко. Фичи пилятся, запускаются и интегрируется в код быстро, пользователи довольны, бизнес считает прибыль. Но со временем, сложность проекта растет, внедрение фич занимает всё больше времени, а руки при релизе начинают дрожать.
🙈 Чем больше вы пишете кода, тем сильнее растет так называемая "программная энтропия" (научный термин между прочим!) — степень беспорядка в системе. Соответственно скорость разработки продукта падает, потому как каждый раз когда вы трогаете код, энтропия увеличивается.
Что делать? Есть 2 варианта.
👉 Можно забить если это:
1. Проверка идеи или MVP продукта, который слеплен из веток и сами знаете чего.
2. Сайт лендинг / визитка / каталог в которому не требуется длительный саппорт.
3. Проект, который существует только у вас на компе и не пойдет в продакшен.
👉 Нужно контролировать если:
1. Продукт прошел стадию MVP и перерос в бизнес.
2. Нужен длительный саппорт.
Причем тут тесты? Они помогают контролировать. Как? Об этом в следующем посте.
#intern #unit #testing #author
Unit testing (предисловие, part - 0)
❗️ Основная цель юнит тестирования — обеспечение стабильного роста проекта. Не быстрого, не сверхнадёжного, а стабильного.
В начале жизни продукта, писать его очень легко. Фичи пилятся, запускаются и интегрируется в код быстро, пользователи довольны, бизнес считает прибыль. Но со временем, сложность проекта растет, внедрение фич занимает всё больше времени, а руки при релизе начинают дрожать.
🙈 Чем больше вы пишете кода, тем сильнее растет так называемая "программная энтропия" (научный термин между прочим!) — степень беспорядка в системе. Соответственно скорость разработки продукта падает, потому как каждый раз когда вы трогаете код, энтропия увеличивается.
Что делать? Есть 2 варианта.
👉 Можно забить если это:
1. Проверка идеи или MVP продукта, который слеплен из веток и сами знаете чего.
2. Сайт лендинг / визитка / каталог в которому не требуется длительный саппорт.
3. Проект, который существует только у вас на компе и не пойдет в продакшен.
👉 Нужно контролировать если:
1. Продукт прошел стадию MVP и перерос в бизнес.
2. Нужен длительный саппорт.
Причем тут тесты? Они помогают контролировать. Как? Об этом в следующем посте.
#intern #unit #testing #author
Сеть — ненадёжна
👉 Давайте договоримся о том, что "Сеть — ненадёжна", примем это как аксиому, как фундаментальное правило, как гравитацию. Сегодня, в эпоху (микро)сервисов, огромного кол-ва сторонних решений вызываемых по API это очень важная аксиома.
• Что делать, если я пошлю команду на изменение и не получу ответ?
• Является ли данный вызов идемпотентным?
• etc.
Что делать, раз вызов по сети может зафейлиться?
👍 Мы должны обеспечить автоматический retry. Для этого отлично подходят системы управления очередями.
Такое взаимодействие называется store and forward, при котором информация попадает в промежуточное хранилище и позднее отправляется в конечный пункт назначения. Он также помогает обеспечить гарантированную доставку, поскольку сообщения сохраняются и никогда не теряются. Если наш получатель находится offline или возникли любые другие сетевые проблемы — система очередей должна попробовать повторить отправку сообщения.
❗️Конечно стоит помнить об особенностях данного подхода. Для конечного пользователя взаимодействие перестанет быть синхронным. С одной стороны, нам не нужно заставлять пользователя ждать, пока выполнятся все наши сетевые вызовы, но с другой мы не можем сразу вывести пользователю конечный результат. То есть мы переходим от привычного Request/Response к Fire and Forget паттерну.
🔨 Получается, что, решая проблему инфраструктуры, асинхронный обмен сообщениями вынуждает нас изменить логическую схему приложения, переосмыслить подход к написанию кода и осмыслить границы в нашей системе.
#middle #architecture #source
👉 Давайте договоримся о том, что "Сеть — ненадёжна", примем это как аксиому, как фундаментальное правило, как гравитацию. Сегодня, в эпоху (микро)сервисов, огромного кол-ва сторонних решений вызываемых по API это очень важная аксиома.
Во время разработки нужно об этом помнить и задавать себе вопросы:• А что если я получу HTTP timeout exception?
• Что делать, если я пошлю команду на изменение и не получу ответ?
• Является ли данный вызов идемпотентным?
• etc.
Что делать, раз вызов по сети может зафейлиться?
👍 Мы должны обеспечить автоматический retry. Для этого отлично подходят системы управления очередями.
Такое взаимодействие называется store and forward, при котором информация попадает в промежуточное хранилище и позднее отправляется в конечный пункт назначения. Он также помогает обеспечить гарантированную доставку, поскольку сообщения сохраняются и никогда не теряются. Если наш получатель находится offline или возникли любые другие сетевые проблемы — система очередей должна попробовать повторить отправку сообщения.
❗️Конечно стоит помнить об особенностях данного подхода. Для конечного пользователя взаимодействие перестанет быть синхронным. С одной стороны, нам не нужно заставлять пользователя ждать, пока выполнятся все наши сетевые вызовы, но с другой мы не можем сразу вывести пользователю конечный результат. То есть мы переходим от привычного Request/Response к Fire and Forget паттерну.
🔨 Получается, что, решая проблему инфраструктуры, асинхронный обмен сообщениями вынуждает нас изменить логическую схему приложения, переосмыслить подход к написанию кода и осмыслить границы в нашей системе.
#middle #architecture #source
Использование индексов в MySQL
Чем больше мы пользуемся ORM, тем меньше задумываемся об оптимизации БД, до тех пор пока не прижмёт. В простых кейсах для ускорения запроса проблем не возникает, но если случай чуть сложнее чем "добавить индекс", то разработчики часто не знают за что хвататься. Здесь хочу оставить пару заметок, которые могут натолкнуть на различные решения в подобной ситуации.
👉 Все мы знаем, что индексы используются для быстрого поиска строк с определенными значениями столбцов. Без индекса MySQL будет начинать поиск с первой строки, а затем читать всю таблицу. Чем больше таблица, тем дороже эта операция.
❓ На что обратить внимание при оптимизации?
• Не исключен случай когда одна колонка используется в нескольких индексах. В таком случае MySQL выбирает индекс который вернет наименьшее кол-во строк (наиболее избирательный).
• При использовании составного (композитного) индекса помните, что он может использоваться и в более простых выборках, но только по столбцам перечисленным слева направо. Например индекс
🤝 Для получения строк из других таблиц при
• MySQL может использовать индексы более эффективно, если они одного и того же типа и размера. В этом контексте
Сравнение столбцов разного типа (например,
🙈 Не слишком очевидное
• Индексы менее важны для маленьких таблиц или для больших, из которых нам нужно извлечь все данные или большую их часть. В таком случае последовательное чтение выполняется быстрее, чем при работе с индексом. Всё потому, что последовательное чтение минимизируют поиск на диске, даже если нам нужны не абсолютно все строки.
• Оптимизатору можно задать подсказку по выбору или игнорированию индекса.
#MySQL #junior #source
Чем больше мы пользуемся ORM, тем меньше задумываемся об оптимизации БД, до тех пор пока не прижмёт. В простых кейсах для ускорения запроса проблем не возникает, но если случай чуть сложнее чем "добавить индекс", то разработчики часто не знают за что хвататься. Здесь хочу оставить пару заметок, которые могут натолкнуть на различные решения в подобной ситуации.
👉 Все мы знаем, что индексы используются для быстрого поиска строк с определенными значениями столбцов. Без индекса MySQL будет начинать поиск с первой строки, а затем читать всю таблицу. Чем больше таблица, тем дороже эта операция.
❓ На что обратить внимание при оптимизации?
• Не исключен случай когда одна колонка используется в нескольких индексах. В таком случае MySQL выбирает индекс который вернет наименьшее кол-во строк (наиболее избирательный).
• При использовании составного (композитного) индекса помните, что он может использоваться и в более простых выборках, но только по столбцам перечисленным слева направо. Например индекс
(col1, col2, col3) будет работать для выборок (col1), (col1, col2), и (col1, col2, col3), но не будет для (col2, col3) или (col3).🤝 Для получения строк из других таблиц при
JOIN
• Для сравнения строковых столбцов оба столбца должны использовать одну и ту же кодировку. Например, сравнение столбца utf8 со столбцом latin1 исключает использование индекса.• MySQL может использовать индексы более эффективно, если они одного и того же типа и размера. В этом контексте
VARCHAR и CHAR считаются одинаковыми, если они объявлены с одинаковым размером. Например, VARCHAR (10) и CHAR (10) имеют одинаковый размер, а VARCHAR (10) и CHAR (15) - нет.Сравнение столбцов разного типа (например,
VARCHAR с DATETIME или INT) может препятствовать использованию индексов, если при этом необходимо преобразование. Например в одной таблице у вас INT 1, а в другой VARCHAR ' 1' или '00001'.🙈 Не слишком очевидное
• Индексы менее важны для маленьких таблиц или для больших, из которых нам нужно извлечь все данные или большую их часть. В таком случае последовательное чтение выполняется быстрее, чем при работе с индексом. Всё потому, что последовательное чтение минимизируют поиск на диске, даже если нам нужны не абсолютно все строки.
• Оптимизатору можно задать подсказку по выбору или игнорированию индекса.
SELECT * FROM table1 USE INDEX (col1_index,col2_index)👍 Ставь 🍺 если было полезно и если хочешь еще заметок по этой теме.
WHERE col1=1 AND col2=2 AND col3=3;
SELECT * FROM table1 IGNORE INDEX (col3_index)
WHERE col1=1 AND col2=2 AND col3=3;
#MySQL #junior #source
Статические анонимные функции 😱
❓ Буквально на днях пришел вопрос от одного из подписчиков касательно контента этого поста. Звучал он так: "
📄 Из документации:
При объявлении в контексте класса, текущий класс будет автоматически связан с ним, делая $this доступным внутри функций класса. Если вы не хотите автоматического связывания с текущим классом, используйте статические анонимные функции.
Выходит, что когда
❗️На первый взгляд "да и чёрт с ним", но стоит копнуть чуть глубже.
Замыкание, содержащее ссылку на
👉 Если подвести короткий итог, то анонимные функции без
#php #middle #memory #source
❓ Буквально на днях пришел вопрос от одного из подписчиков касательно контента этого поста. Звучал он так: "
А зачем делать callback’и в функции сортировки (usort), статическими?" И я подумал, что это действительно хороший вопрос, на который стоит обратить внимание.📄 Из документации:
При объявлении в контексте класса, текущий класс будет автоматически связан с ним, делая $this доступным внутри функций класса. Если вы не хотите автоматического связывания с текущим классом, используйте статические анонимные функции.
Выходит, что когда
closure объявляется в контексте класса, то класс автоматически привязывается к замыканию. Это означает, что $this доступен внутри области анонимной функции. Вот такой вот пример.❗️На первый взгляд "да и чёрт с ним", но стоит копнуть чуть глубже.
Замыкание, содержащее ссылку на
$this, может предотвратить сборку мусора для этого объекта, что, в свою очередь, может существенно повлиять на производительность. Вот примеры с использованием static и без него. Ну и gist, чтобы самостоятельно поиграться.👉 Если подвести короткий итог, то анонимные функции без
static стоит использовать если вам необходимо привязать объект к области видимости выполнения функции. Во всех остальных случаях можно использовать static, как минимум, чтобы случайно не выстрелить себе в ногу.#php #middle #memory #source